diff --git a/pkg/items/item.go b/pkg/items/item.go index dc56fe63284be6885cf4560578df0bf77729e359..9554d85eae19b156ce4246ff375a1f7f1f239558 100644 --- a/pkg/items/item.go +++ b/pkg/items/item.go @@ -293,11 +293,11 @@ func (i *Item) Encode(ctx context.Context, s *schema.Schema) (*Item, error) { return &res, nil } -func (i *Item) Decode(ctx context.Context, s *schema.Schema) (*Item, error) { +func (i *Item) Decode(ctx context.Context, s *schema.Schema, opts ...field.EncodeOption) (*Item, error) { res := *i var err error if i.Data != nil { - res.Data, err = s.Decode(ctx, i.Data) + res.Data, err = s.Decode(ctx, i.Data, opts...) if err != nil { return nil, err } @@ -309,7 +309,7 @@ func (i *Item) Decode(ctx context.Context, s *schema.Schema) (*Item, error) { res.Translations[l] = v continue } - dt, err := schema.Decode(ctx, s, v) + dt, err := schema.Decode(ctx, s, v, opts...) if err != nil { return nil, err } diff --git a/pkg/schema/field/array.go b/pkg/schema/field/array.go index 682b6e5e7774cbf98e93fc31261e114b574049e7..1dc44679bb4a75805f8c07e648d231b165bc0093 100644 --- a/pkg/schema/field/array.go +++ b/pkg/schema/field/array.go @@ -120,6 +120,19 @@ func (ArrayType) IsEmpty(v interface{}) bool { // return nil //} +func (ArrayType) NonStrictConverter(_ context.Context, _ *Field, v interface{}) interface{} { + if v == nil { + return nil + } + + // Если в данных хранится не слайс или массив, преобразуем в слайс из одного элемента + if arr := reflect.ValueOf(v); arr.Kind() != reflect.Slice && arr.Kind() != reflect.Array { + return []interface{}{v} + } + + return v +} + func (ArrayType) Walk(ctx context.Context, field *Field, v interface{}, fn WalkFunc, opts *WalkOptions) (interface{}, bool, error) { var changed bool params, ok := field.Params.(*ArrayParameters) diff --git a/pkg/schema/field/encode.go b/pkg/schema/field/encode.go index d891b41228e4ff70142cff9bdf3bca8cc99506dd..4fedee2b08dfcb4bbef053c8402da75d7eeb07eb 100644 --- a/pkg/schema/field/encode.go +++ b/pkg/schema/field/encode.go @@ -14,18 +14,42 @@ type Encoder interface { Encode(ctx context.Context, field *Field, v interface{}) (interface{}, error) } -func Decode(ctx context.Context, w Walker, v interface{}) (interface{}, error) { - var err error - //if ctx == nil { - // ctx = NewContext() - //} - // - //if m, ok := v.(map[string]interface{}); ok { - // ctx = ctx.ExtendEnv(m) - // ctx.DisableConditions = true - //} +// NonStrictConverter определяет метод для преобразования данных в нестрогом режиме. +type NonStrictConverter interface { + NonStrictConverter(ctx context.Context, field *Field, v interface{}) interface{} +} + +type EncodeOptions struct { + NonStrictMode bool +} + +type EncodeOption func(opts *EncodeOptions) + +// NonStrictMode указывает, что декодирование необходимо провести в нестрогом режиме. +func NonStrictMode() EncodeOption { + return func(opts *EncodeOptions) { + opts.NonStrictMode = true + } +} + +func NewEncodeOptions(opt ...EncodeOption) *EncodeOptions { + opts := &EncodeOptions{} + for _, o := range opt { + o(opts) + } + return opts +} + +func Decode(ctx context.Context, w Walker, v interface{}, opts ...EncodeOption) (interface{}, error) { + opt := NewEncodeOptions(opts...) val, _, err := w.Walk(ctx, v, func(ctx context.Context, f *Field, v interface{}) (res WalkFuncResult, err error) { + if opt.NonStrictMode { + if converter, ok := f.GetType().(NonStrictConverter); ok { + v = converter.NonStrictConverter(ctx, f, v) + } + } + if decoder, ok := f.GetType().(Decoder); ok { if v, err = decoder.Decode(ctx, f, v); err != nil { return diff --git a/pkg/schema/field/number.go b/pkg/schema/field/number.go index 16673917895c4967c5b9ca65c5b6d1a5b397b045..1e782af179da9269b2200b7dd4c378a1b97a4280 100644 --- a/pkg/schema/field/number.go +++ b/pkg/schema/field/number.go @@ -4,6 +4,7 @@ import ( "context" "math" "reflect" + "strconv" "github.com/pkg/errors" ) @@ -138,6 +139,25 @@ func (n NumberType) Encode(ctx context.Context, field *Field, v interface{}) (in return n.decode(ctx, field, v) } +func (NumberType) NonStrictConverter(_ context.Context, _ *Field, v interface{}) interface{} { + if v == nil { + return nil + } + + // Если строка, пробуем преобразовать в число + if s, ok := v.(string); ok { + if i, err := strconv.ParseInt(s, 0, 64); err == nil { + return i + } + + if f, err := strconv.ParseFloat(s, 64); err == nil { + return f + } + } + + return v +} + func Number(format string, o ...interface{}) *Field { return NewField(&NumberParameters{Format: format}, o...) } diff --git a/pkg/schema/field/string.go b/pkg/schema/field/string.go index 96b4e8dd4d4d0d193a91718698906dda15c0cc85..8fa4076eb32e327716a7bf65ed0ec1135ff4d088 100644 --- a/pkg/schema/field/string.go +++ b/pkg/schema/field/string.go @@ -58,6 +58,17 @@ func (StringType) Encode(_ context.Context, _ *Field, v interface{}) (interface{ return nil, fmt.Errorf("StringField encode error: unsupported value type : \"%s\"", reflect.ValueOf(v).Kind()) } +func (StringType) NonStrictConverter(_ context.Context, _ *Field, v interface{}) interface{} { + if v == nil { + return nil + } + if _, ok := v.(string); ok { + return v + } + + return fmt.Sprintf("%v", v) +} + func String(o ...interface{}) *Field { return NewField(&StringParameters{}, o...) } diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 7b337f8ab68f732fb3ab2ac804fe89e082c58c6a..57dd8f9beee9021ef02f6164c67dc3554ada1963 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -148,16 +148,17 @@ func (s *Schema) Evaluate(ctx context.Context, data map[string]interface{}) (res return } -func (s *Schema) Decode(ctx context.Context, v interface{}) (res map[string]interface{}, err error) { - if err = s.Load(ctx); err != nil { +func (s *Schema) Decode(ctx context.Context, v interface{}, opt ...field.EncodeOption) (map[string]interface{}, error) { + if err := s.Load(ctx); err != nil { return nil, err } - if v, err = Decode(ctx, s, v); err != nil { + v, err := Decode(ctx, s, v, opt...) + if err != nil { return nil, err } - res, _ = v.(map[string]interface{}) - return + res, _ := v.(map[string]interface{}) + return res, err } func (s *Schema) Encode(ctx context.Context, v interface{}) (interface{}, error) {