diff --git a/pkg/extension/schema_test.go b/pkg/extension/schema_test.go index 20648678a159b6703d05687530ba8e16ded4c8f2..4b4f235ae6a43bbb7e9c39a56b45c51b530d7103 100644 --- a/pkg/extension/schema_test.go +++ b/pkg/extension/schema_test.go @@ -47,6 +47,8 @@ func TestEqualSchema(t *testing.T) { require.NoError(t, err) s2 := schema.New() err = json.Unmarshal(b, s2) + s1.ClearState() + s2.ClearState() require.NoError(t, err) require.Equal(t, s1.Field, s2.Field) } diff --git a/pkg/files/field.go b/pkg/files/field.go index 00659012aac439a835332ae9320fa64f2a03f9f9..ffb315d5522da6995b7a9a81040f47f933310df1 100644 --- a/pkg/files/field.go +++ b/pkg/files/field.go @@ -22,6 +22,27 @@ type FileParameters struct { func (p FileParameters) Type() field.Type { return p.t } func (p *FileParameters) Clone(reset bool) field.Parameters { return p } +func (p FileParameters) GetField(f *field.Field, name string) *field.Field { + var fld *field.Field + switch name { + case "id", "name", "mimeType", "url", "key": + fld = field.String() + case "size": + fld = field.Number(field.NumberFormatInt) + } + return f.SetFieldState(name, fld) +} + +func (p FileParameters) ListFields(f *field.Field, filter ...field.FieldFilterFunc) []*field.Field { + return []*field.Field{ + f.SetFieldState("id", field.String()), + f.SetFieldState("name", field.String()), + f.SetFieldState("mimeType", field.String()), + f.SetFieldState("size", field.Number(field.NumberFormatInt)), + f.SetFieldState("url", field.String()), + f.SetFieldState("key", field.String()), + } +} type FileType struct { fs Files @@ -145,17 +166,6 @@ func (t *FileType) IsEmpty(v interface{}) bool { return !ok || f.ID == "" } -func (p FileParameters) GetField(path string) (fld *field.Field) { - switch path { - case "id", "name", "mimeType", "url", "key": - return field.String() - case "size": - return field.Number(field.NumberFormatInt) - default: - return nil - } -} - func init() { // По умолчанию без FS // Если нужны подписанные URL, и загрузка на FS, нужно зарегистрировать корректный типа diff --git a/pkg/items/item_test.go b/pkg/items/item_test.go index 2cef5e75414f55e09d3e5f42d4ed4ee56f0cd866..05baa60a3c1e8eaae9534fe9026d7456d78ab36c 100644 --- a/pkg/items/item_test.go +++ b/pkg/items/item_test.go @@ -72,6 +72,7 @@ func TestGetField(t *testing.T) { ), "arr", field.Array(field.Object("a", field.Time())), ) + sch.ClearState() tests := []struct { name string diff --git a/pkg/references/field.go b/pkg/references/field.go index 85482b9e526c502f24005329522e3690d803c283..905c2d43a1b05f5e19b00a99fcc8fadddd718b61 100644 --- a/pkg/references/field.go +++ b/pkg/references/field.go @@ -18,7 +18,6 @@ type ReferenceParameters struct { } func (p ReferenceParameters) Type() field.Type { return &ReferenceType{} } - func (p ReferenceParameters) Clone(reset bool) field.Parameters { if p.AllowedCollections != nil { cols := make([]string, 0, len(p.AllowedCollections)) @@ -27,6 +26,10 @@ func (p ReferenceParameters) Clone(reset bool) field.Parameters { } return &p } +func (p ReferenceParameters) GetField(f *field.Field, name string) *field.Field { return nil } +func (p ReferenceParameters) ListFields(f *field.Field, filter ...field.FieldFilterFunc) []*field.Field { + return nil +} type ReferenceType struct{} diff --git a/pkg/schema/field/array.go b/pkg/schema/field/array.go index 32d6f72f5e9260857dbabb4d46e6e498242fab4d..bf1a4819dd075fc5e3f46fc4d1e289c8bd75cf90 100644 --- a/pkg/schema/field/array.go +++ b/pkg/schema/field/array.go @@ -22,6 +22,22 @@ func (p ArrayParameters) Clone(reset bool) Parameters { return &ArrayParameters{Item: p.Item.Clone(reset)} } +func (a ArrayParameters) GetField(f *Field, name string) *Field { + f.SetFieldState("Item", a.Item) + + if name == "" || name == "Item" { + return a.Item + } + + return a.Item.GetField(name) +} + +func (a ArrayParameters) ListFields(f *Field, filterFunc ...FieldFilterFunc) []*Field { + f.SetFieldState("Item", a.Item) + + return []*Field{a.Item} +} + type ArrayType struct{} func (ArrayType) Name() string { diff --git a/pkg/schema/field/boolean.go b/pkg/schema/field/boolean.go index 94580541f8e33f9930adad94bff6a5c18915d05b..e06b704d0bb792d02a2f95343d037760fffb6fc7 100644 --- a/pkg/schema/field/boolean.go +++ b/pkg/schema/field/boolean.go @@ -10,8 +10,10 @@ var boolType = &BoolType{} type BoolParameters struct{} -func (b BoolParameters) Type() Type { return boolType } -func (b *BoolParameters) Clone(reset bool) Parameters { return b } +func (b BoolParameters) Type() Type { return boolType } +func (b *BoolParameters) Clone(reset bool) Parameters { return b } +func (b BoolParameters) GetField(f *Field, name string) *Field { return nil } +func (b BoolParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } type BoolType struct{} diff --git a/pkg/schema/field/field.go b/pkg/schema/field/field.go index 6edf18f57ce36bc1569446502cc3ba05332252c8..4d2a6771bbe956f07698e52835aca53a2853bd58 100644 --- a/pkg/schema/field/field.go +++ b/pkg/schema/field/field.go @@ -49,6 +49,17 @@ type Include struct { Optional bool `json:"optional,omitempty"` } +// State - состояние поля времени выполнения +type State struct { + Name string + DataPath string + SchemaPath string + SingleLocale bool + Parent *Field + Inlined bool + HasInline bool +} + type Field struct { Title string `json:"title,omitempty"` // Название поля (Например: name) Description string `json:"description,omitempty"` // Описание поле (Например: User name) @@ -63,6 +74,7 @@ type Field struct { Options Options `json:"options,omitempty"` // Дополнительные опции Condition string `json:"condition,omitempty"` // Условие отображения поля AdditionalValues bool `json:"additional_values,omitempty"` // Разрешает дополнительные значения вне ограничений правил + State *State `json:"-"` // Состояние поля времени выполнения } // TODO: Replace with Named field??? @@ -84,6 +96,32 @@ func NewField(params Parameters, opts ...interface{}) *Field { return f } +// GetState возвращает состояние поля времени выполнения +func (f Field) GetState() *State { + return f.State +} + +// ClearState очищает состояние поля и всех вложенных полей +// +// Схемы нельзя сравнивать с активным состоянием с помощью `reflect.DeepEqual` или `assert.Equal`. +// Предварительно нужно сделать `ClearState`. +// После очистки состояния полей не будут рассчитываться. Для повторного включения состояния используйте `EnableState` +func (f *Field) ClearState() *Field { + f.State = nil + for _, fld := range f.ListFields() { + fld.ClearState() + } + return f +} + +// EnableState включает расчет состояния поля и всех вложенных полей +// +// Без включения состояния поля, невозможно получить доступ к данным времени выполнения +// schema.New включает состояние для схемы при создании +func (f *Field) EnableState() { + f.State = &State{} +} + func (f Field) GetType() Type { return f.Params.Type() } @@ -164,6 +202,10 @@ func (f Field) SetSingleLocale(r bool) *Field { return &f } +func (f Field) IsSingleLocale() bool { + return f.SingleLocale || (f.State != nil && f.State.SingleLocale) +} + func (f Field) SetIndexed(r bool) *Field { f.Indexed = r return &f @@ -257,46 +299,91 @@ func (f *Field) Prepare() error { return nil } -// GetField возвращает поле по строковому пути -func (f *Field) GetField(path string) *Field { - if path == "" { - switch params := f.Params.(type) { - case *ArrayParameters: - // Возвращаем поле Item если путь указан как "arr." - return params.Item - } - return nil +func (f *Field) SetFieldState(name string, fld *Field) *Field { + if f != nil && fld != nil && f.State != nil && fld.State == nil { + fld.State = f.getFieldState(name, fld) } + return fld +} - switch params := f.Params.(type) { - case *ObjectParameters: - pp := strings.SplitN(path, FieldSeparator, 2) +// GetFieldState возвращает состояние вложенного поля +func (f *Field) getFieldState(name string, fld *Field) *State { + if f.State == nil { + return nil + } - for k, v := range params.Fields { + state := State{ + SchemaPath: name, + DataPath: name, + Name: name, + } - p, ok := v.Params.(*ObjectParameters) - if ok && p.Inline { - f := v.GetField(path) - if f != nil { - return f - } - } + dataPath := f.State.DataPath - if k == pp[0] { - if len(pp) == 1 { - return v - } - return v.GetField(pp[1]) + switch params := f.Params.(type) { + case *ObjectParameters: + if params.Inline { + last := strings.LastIndex(dataPath, ".") + if last > 0 { + dataPath = dataPath[:last] + } else { + dataPath = "" } + state.Inlined = true + } + if dataPath != "" { + state.DataPath = dataPath + FieldSeparator + state.DataPath } - case Fielder: - return params.GetField(path) case *ArrayParameters: - return params.Item.GetField(path) + state.DataPath = dataPath // Remove item from path } - return nil + state.SingleLocale = f.IsSingleLocale() || fld.SingleLocale + + if f.State.SchemaPath != "" { + state.SchemaPath = f.State.SchemaPath + FieldSeparator + state.SchemaPath + } + state.Parent = f + state.HasInline = f.State.HasInline || state.Inlined + return &state +} + +func (f *Field) GetFieldByName(name string) *Field { + return f.Params.GetField(f, name) +} + +// GetField возвращает поле по строковому пути +func (f *Field) GetField(path string) *Field { + name := "" + parts := strings.SplitN(path, FieldSeparator, 2) + + if len(parts) > 0 { + name = parts[0] + } + + fld := f.GetFieldByName(name) + + if fld != nil && len(parts) > 1 { + return fld.GetField(parts[1]) + } + + return fld +} + +// ListFields возвращает массив вложенных полей данного поля +func (f *Field) ListFields(filter ...FieldFilterFunc) []*Field { + fields := f.Params.ListFields(f, filter...) + return fields +} + +// ListFieldsRecursive возвращает массив всех вложенных полей рекурсивно +func (f *Field) ListFieldsRecursive(filter ...FieldFilterFunc) []*Field { + fields := f.ListFields(filter...) + for _, fld := range fields { + fields = append(fields, fld.ListFieldsRecursive(filter...)...) + } + return fields } // GetFieldsPath возвращает полный путь для массива полей @@ -311,6 +398,8 @@ type FilterFunc func(*Field, string) bool func GetAll(field *Field, path string) bool { return true } +// GetFields возвращает массив полей с путем??? +// DEPRECATED: использовать ListFields или ListFieldsRecursive func (f *Field) GetFields(filterFunc FilterFunc, pathPrefix ...string) (res []PathField) { var path string @@ -382,22 +471,26 @@ func getFieldsObject(path string, params *ObjectParameters, filterFunc FilterFun return res } +// GetNestedFields возвращает вложенные поля +// DEPRECATED: использовать ListFields func (f *Field) GetNestedFields() []*Field { - switch params := f.Params.(type) { - case *ObjectParameters: - flds := make([]*Field, 0, len(params.Fields)) - for _, v := range params.Fields { - if v == nil { - continue - } - flds = append(flds, v) - } - return flds - case *ArrayParameters: - return []*Field{params.Item} - } - - return nil + return f.ListFields() + + //switch params := f.Params.(type) { + //case *ObjectParameters: + // flds := make([]*Field, 0, len(params.Fields)) + // for _, v := range params.Fields { + // if v == nil { + // continue + // } + // flds = append(flds, v) + // } + // return flds + //case *ArrayParameters: + // return []*Field{params.Item} + //} + // + //return nil } // Clone создает копию поля diff --git a/pkg/schema/field/field_json.go b/pkg/schema/field/field_json.go index dfd7f85aebafe2f6c1008db1c45b40df65c27b9d..170e3d161f87300955b06d21e8cf79e2140f9eb8 100644 --- a/pkg/schema/field/field_json.go +++ b/pkg/schema/field/field_json.go @@ -61,6 +61,8 @@ func (f *Field) UnmarshalJSON(b []byte) error { } } + j.FieldData.State = f.State + *f = Field(j.FieldData) f.Params = params _ = f.Prepare() diff --git a/pkg/schema/field/field_test.go b/pkg/schema/field/field_test.go new file mode 100644 index 0000000000000000000000000000000000000000..d27d74bb473a3303c95d5fb63e927d5b314d9a42 --- /dev/null +++ b/pkg/schema/field/field_test.go @@ -0,0 +1,101 @@ +package field + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestField_GetField(t *testing.T) { + sch := Object( + "f1", Object( + "a", String(), + "b", String().SetSingleLocale(true), + ), + "f3", Object( // inline object + true, + "a", String(), + "b", Object(true, "c", String()), + ).SetSingleLocale(true), + "f4", Array(Object("a", String())), + "f5", Array(String()), + "f6", Object(true, "f6", Object("a", String())), + ) + + sch.EnableState() + + tests := []struct { + name string + path string + want *State + }{ + {"Object", "f1", &State{Name: "f1", DataPath: "f1", SchemaPath: "f1", Parent: sch}}, + {"Object field", "f1.a", &State{Name: "a", DataPath: "f1.a", SchemaPath: "f1.a", Parent: sch.GetField("f1")}}, + {"Field with SingleLocale", "f1.b", &State{Name: "b", DataPath: "f1.b", SchemaPath: "f1.b", Parent: sch.GetField("f1"), SingleLocale: true}}, + {"Object with SingleLocale", "f3", &State{Name: "f3", DataPath: "f3", SchemaPath: "f3", Parent: sch, SingleLocale: true}}, + {"Inline", "a", &State{Name: "a", DataPath: "a", SchemaPath: "f3.a", Parent: sch.GetField("f3"), SingleLocale: true, Inlined: true, HasInline: true}}, + {"Inline of inline", "c", &State{Name: "c", DataPath: "c", SchemaPath: "f3.b.c", Parent: sch.GetField("f3.b"), SingleLocale: true, Inlined: true, HasInline: true}}, + {"Inline of inline (direct)", "f3.b.c", &State{Name: "c", DataPath: "c", SchemaPath: "f3.b.c", Parent: sch.GetField("f3.b"), SingleLocale: true, Inlined: true, HasInline: true}}, + {"Array of Objects", "f4", &State{Name: "f4", DataPath: "f4", SchemaPath: "f4", Parent: sch}}, + {"Array of Objects (Item)", "f4.Item", &State{Name: "Item", DataPath: "f4", SchemaPath: "f4.Item", Parent: sch.GetField("f4")}}, + {"Array of Objects (Item field)", "f4.Item.a", &State{Name: "a", DataPath: "f4.a", SchemaPath: "f4.Item.a", Parent: sch.GetField("f4.Item")}}, + {"Array of Objects (Item field direct)", "f4.a", &State{Name: "a", DataPath: "f4.a", SchemaPath: "f4.Item.a", Parent: sch.GetField("f4.Item")}}, + {"Array of Strings", "f5", &State{Name: "f5", DataPath: "f5", SchemaPath: "f5", Parent: sch}}, + {"Array of Strings (Item)", "f5.Item", &State{Name: "Item", DataPath: "f5", SchemaPath: "f5.Item", Parent: sch.GetField("f5")}}, + {"Inline Same name not found", "f6.a", nil}, + {"Inline Same name (direct)", "f6.f6.a", &State{Name: "a", DataPath: "f6.a", SchemaPath: "f6.f6.a", Parent: sch.GetField("f6.f6"), HasInline: true}}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := sch.GetField(tt.path) + var st *State + if got != nil { + st = got.State + } + + assert.Equal(t, tt.want, st) + sch.ClearState() + sch.EnableState() + if got != nil { + assert.Nil(t, got.State) + } + }) + } +} + +func TestField_ListFieldsRecursive(t *testing.T) { + sch := Object( + "f1", Object( + "f1a", String(), + "f1b", String().SetSingleLocale(true), + ), + "f2", String(), + "f3", Object( // inline object + true, + "f3a", String(), + "f3b", Object(true, "f3bc", String()), + ).SetSingleLocale(true), + "f4", Array(Object("f4a", String())), + "f5", Array(String()), + "f6", Object(true, "f6", Object("f6a", String())), + ) + + sch.EnableState() + + fields := sch.ListFieldsRecursive() + assert.Len(t, fields, 16) + for _, f := range fields { + assert.NotNil(t, f.State) + assert.NotEmpty(t, f.State.Name) + } +} + +func TestField_CloneWithState(t *testing.T) { + f := Object("a", String()) + fld := f.Clone(false) + assert.Nil(t, fld.State) + f.EnableState() + fld = f.Clone(false) + assert.NotNil(t, fld.State) +} diff --git a/pkg/schema/field/location.go b/pkg/schema/field/location.go index d89b3206d091971a123e8842330f4b850838ccdf..97aea3ea8aecbf206325bbd1b49363669ce0436d 100644 --- a/pkg/schema/field/location.go +++ b/pkg/schema/field/location.go @@ -14,8 +14,10 @@ var locationType = &LocationType{} type LocationParameters struct{} -func (p LocationParameters) Type() Type { return locationType } -func (p LocationParameters) Clone(reset bool) Parameters { return &LocationParameters{} } +func (p LocationParameters) Type() Type { return locationType } +func (p LocationParameters) Clone(reset bool) Parameters { return &LocationParameters{} } +func (p LocationParameters) GetField(f *Field, name string) *Field { return nil } +func (p LocationParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } func (p LocationParameters) GetMongoIndexes(path string, f *Field) []mongo.IndexModel { var add, geo mongo.IndexModel diff --git a/pkg/schema/field/number.go b/pkg/schema/field/number.go index 40d9c8167f54d23faaa40666ec1934b451d7a516..dfc8a6635cc0e719bd55e9f680ba23688674edd0 100644 --- a/pkg/schema/field/number.go +++ b/pkg/schema/field/number.go @@ -22,8 +22,10 @@ type NumberParameters struct { Format string `json:"format,omitempty"` } -func (NumberParameters) Type() Type { return numberType } -func (p NumberParameters) Clone(reset bool) Parameters { return &p } +func (NumberParameters) Type() Type { return numberType } +func (p NumberParameters) Clone(reset bool) Parameters { return &p } +func (p NumberParameters) GetField(f *Field, name string) *Field { return nil } +func (p NumberParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } type NumberType struct{} diff --git a/pkg/schema/field/object.go b/pkg/schema/field/object.go index 1f995cceba1816c67454d94771ef3287e86d948c..45bc3e61d835cca2b8ba1a8866636ccd740c48c4 100644 --- a/pkg/schema/field/object.go +++ b/pkg/schema/field/object.go @@ -36,6 +36,39 @@ func (p ObjectParameters) Clone(reset bool) Parameters { return &p } +func (p ObjectParameters) GetField(f *Field, name string) *Field { + // Поиск поля в текущем объекте + if fld, ok := p.Fields[name]; ok { + return f.SetFieldState(name, fld) + } + + // Поиск поля во вложенных Inline объектах + for k, v := range p.Fields { + if p, ok := v.Params.(*ObjectParameters); ok { + if p.Inline { + v = f.SetFieldState(k, v) + if fld := v.GetFieldByName(name); fld != nil { + return fld + } + } + } + } + + return nil +} + +func (p ObjectParameters) ListFields(f *Field, filterFunc ...FieldFilterFunc) []*Field { + var fields []*Field + for k, fld := range p.Fields { + f.SetFieldState(k, fld) + if !ApplyFilterFunc(filterFunc, fld) { + continue + } + fields = append(fields, fld) + } + return fields +} + // IsInlineObject определяет являться ли поле name инлайн объектом func (p ObjectParameters) IsInlineObject(name string) bool { fld, ok := p.Fields[name] diff --git a/pkg/schema/field/primary_key.go b/pkg/schema/field/primary_key.go index b0b26e16a91cd506231ba46307485290a138bfb8..da40f97eb65c8551b3ca2710a26812ececea616f 100644 --- a/pkg/schema/field/primary_key.go +++ b/pkg/schema/field/primary_key.go @@ -12,8 +12,10 @@ var primaryKeyType = &PrimaryKeyType{} type PrimaryKeyParameters struct{} -func (p PrimaryKeyParameters) Type() Type { return primaryKeyType } -func (p *PrimaryKeyParameters) Clone(reset bool) Parameters { return p } +func (p PrimaryKeyParameters) Type() Type { return primaryKeyType } +func (p *PrimaryKeyParameters) Clone(reset bool) Parameters { return p } +func (p PrimaryKeyParameters) GetField(f *Field, name string) *Field { return nil } +func (p PrimaryKeyParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } type PrimaryKeyType struct{} diff --git a/pkg/schema/field/string.go b/pkg/schema/field/string.go index b7e548b65f5c1572cc515cad19192899843ab62f..17b5e7ae086d3fac1b25d8c332c33d83fecedfb8 100644 --- a/pkg/schema/field/string.go +++ b/pkg/schema/field/string.go @@ -10,8 +10,10 @@ var stringType = &StringType{} type StringParameters struct{} -func (s StringParameters) Type() Type { return stringType } -func (s *StringParameters) Clone(reset bool) Parameters { return s } +func (s StringParameters) Type() Type { return stringType } +func (s *StringParameters) Clone(reset bool) Parameters { return s } +func (s StringParameters) GetField(f *Field, name string) *Field { return nil } +func (s StringParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } type StringType struct{} diff --git a/pkg/schema/field/time.go b/pkg/schema/field/time.go index 064906f236371d2914a0544c3f6abef83fd77f65..cdb50a4cafc500ea56dd56436faa5babd4793d31 100644 --- a/pkg/schema/field/time.go +++ b/pkg/schema/field/time.go @@ -17,8 +17,10 @@ type TimeParameters struct { Layout string `json:"layout,omitempty"` } -func (p TimeParameters) Type() Type { return timeType } -func (p TimeParameters) Clone(reset bool) Parameters { return &p } +func (p TimeParameters) Type() Type { return timeType } +func (p TimeParameters) Clone(reset bool) Parameters { return &p } +func (p TimeParameters) GetField(f *Field, name string) *Field { return nil } +func (p TimeParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } func (p TimeParameters) GetLayout() string { if p.Layout != "" { diff --git a/pkg/schema/field/timestamp.go b/pkg/schema/field/timestamp.go index 694d477e0c6a55e5140c4e7d78d26381a31686ae..593218c645c016751d082d1e938d0c966134ebb9 100644 --- a/pkg/schema/field/timestamp.go +++ b/pkg/schema/field/timestamp.go @@ -13,8 +13,10 @@ var ( type TimestampParameters struct{} -func (t TimestampParameters) Type() Type { return timestampType } -func (t *TimestampParameters) Clone(reset bool) Parameters { return t } +func (t TimestampParameters) Type() Type { return timestampType } +func (t *TimestampParameters) Clone(reset bool) Parameters { return t } +func (t TimestampParameters) GetField(f *Field, name string) *Field { return nil } +func (t TimestampParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } type TimestampType struct{} diff --git a/pkg/schema/field/type.go b/pkg/schema/field/type.go index edae76734d562d2546d3b68e89e125c7a87744a6..984cd59187b8cd1f1380bcc0762d66366d4715ab 100644 --- a/pkg/schema/field/type.go +++ b/pkg/schema/field/type.go @@ -10,10 +10,23 @@ var ( registry sync.Map ) +type FieldFilterFunc func(f *Field) bool + +func ApplyFilterFunc(filterFunc []FieldFilterFunc, fld *Field) bool { + for _, f := range filterFunc { + if !f(fld) { + return false + } + } + return true +} + // Parameters - интерфейс который должен реализовывать параметр конкретного типа type Parameters interface { Type() Type Clone(reset bool) Parameters + GetField(f *Field, name string) *Field + ListFields(f *Field, filter ...FieldFilterFunc) []*Field } // Type - тип поля, отвечает за получение, кодирование и декодирование параметров для данного типа diff --git a/pkg/schema/field/unknown.go b/pkg/schema/field/unknown.go index ffe412265c18209cbcce8bc442bd6094e0eecf1d..36d0ba97fcd4b917af6e8986074d7f3f40e8654a 100644 --- a/pkg/schema/field/unknown.go +++ b/pkg/schema/field/unknown.go @@ -14,8 +14,10 @@ type UnknownParameters struct { Params json.RawMessage `json:"params,omitempty"` } -func (UnknownParameters) Type() Type { return unknownType } -func (p UnknownParameters) Clone(reset bool) Parameters { return &p } +func (UnknownParameters) Type() Type { return unknownType } +func (p UnknownParameters) Clone(reset bool) Parameters { return &p } +func (p UnknownParameters) GetField(f *Field, name string) *Field { return nil } +func (p UnknownParameters) ListFields(f *Field, filter ...FieldFilterFunc) []*Field { return nil } type UnknownType struct{} diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 71d872763e886ad91555717d7b1ba86804c58cc5..fdb402b412de15b3ba8aa9d2b0244ea3d22e8f09 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -19,7 +19,9 @@ type Schema struct { } func New(kv ...interface{}) *Schema { - return &Schema{Field: *field.Object(kv...)} + s := &Schema{Field: *field.Object(kv...)} + s.Field.EnableState() + return s } func NewFromField(f *field.Field) *Schema { @@ -46,6 +48,11 @@ func (s *Schema) Clone(reset bool) *Schema { } } +func (s *Schema) ClearState() *Schema { + s.Field.ClearState() + return s +} + func (s *Schema) Equal(sch *Schema) bool { if s == sch { return true @@ -86,7 +93,10 @@ func (s *Schema) ConvertTypes() error { if err != nil { return errors.Wrap(err, "marshal schema") } + // сохраняем состояние cхемы + state := s.Field.State *s = *New() + s.Field.State = state return errors.Wrap(s.UnmarshalJSON(b), "unmarshal schema") } diff --git a/pkg/schema/schema_json_test.go b/pkg/schema/schema_json_test.go index f9e6be1becbaa336b6aaf6e8ee177042b2cb22ff..31b475168e9e0179328295044a7201517212d4ec 100644 --- a/pkg/schema/schema_json_test.go +++ b/pkg/schema/schema_json_test.go @@ -70,6 +70,8 @@ func TestSchema_UnmarshalJSON(t *testing.T) { if err := schema.UnmarshalJSON(tt.b); (err != nil) != tt.wantErr { t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) } + schema.ClearState() + tt.want.ClearState() assert.Equal(t, tt.want, schema) }) } diff --git a/pkg/schema/test/convert_test.go b/pkg/schema/test/convert_test.go index 170bee8320b510244923d9c3905c20c17dc1d3f7..5e791570ebfc12c9efc9a4c9295ce5dacced4af5 100644 --- a/pkg/schema/test/convert_test.go +++ b/pkg/schema/test/convert_test.go @@ -23,6 +23,9 @@ func TestFromFiles(t *testing.T) { t.Run("Success", func(t *testing.T) { schemas, err := schema.FromFS(os.DirFS("assets")) + for _, s := range schemas { + s.ClearState() + } require.NoError(t, err) require.Len(t, schemas, 2, "В директории хранятся две корректные схемы") require.ElementsMatch(t, []*schema.Schema{getPagesSchema(), getPagesSchema()}, schemas, "Cхемы должны соответствовать объекту из функции") @@ -146,6 +149,7 @@ func getPagesSchema() *schema.Schema { page.Field.UI.Options["collection_icon"] = "ApartmentOutlined/FileTextOutlined" _ = page.ConvertTypes() + page.ClearState() return page } diff --git a/pkg/schema/test/object_test.go b/pkg/schema/test/object_test.go index f17b64511668ad8b4a86c5d91cb908a781384493..8bf3f82022e8501932e6474a985bcfc9f195e693 100644 --- a/pkg/schema/test/object_test.go +++ b/pkg/schema/test/object_test.go @@ -193,6 +193,9 @@ func TestSchema_JSON(t *testing.T) { err = json.Unmarshal(b, res) require.NoError(t, err) + sch.ClearState() + res.ClearState() + assert.Equal(t, sch, res) } @@ -213,6 +216,7 @@ func TestSchemaUI_UnmarshalJSON(t *testing.T) { "name", field.String().WithUI(ui), ) schm.UI = ui + schm.ClearState() j := `{ "ui": { @@ -288,6 +292,7 @@ func TestSchemaUI_UnmarshalJSON(t *testing.T) { sch := schema.New() err := sch.UnmarshalJSON([]byte(j)) + sch.ClearState() require.NoError(t, err) assert.Equal(t, sch, schm) } @@ -364,7 +369,7 @@ func TestSchema_GetField_WithInline(t *testing.T) { "a", field.String(), "b", field.String(), ), - "zz", field.Object( + "zzz", field.Object( true, "zz", field.Array(field.Object( "str3", field.String(), @@ -857,6 +862,7 @@ func TestSchema_UnknownJSON(t *testing.T) { "times", field.Number("int"), "dates", field.Array(field.Time()), ) + sch.ClearState() b, err := json.Marshal(sch) require.NoError(t, err) @@ -881,6 +887,8 @@ func TestSchema_UnknownJSON(t *testing.T) { require.NoError(t, err) b, err = json.Marshal(s2) require.NoError(t, err) + s1.ClearState() + s2.ClearState() assert.Equal(t, "unknown", s2.GetType().Name(), "Схема неизвестного типа должна определяться как unknown") assert.Equal(t, s1, s2, "Схема не должна меняться при повторном маршалинге") @@ -888,6 +896,7 @@ func TestSchema_UnknownJSON(t *testing.T) { s3 := schema.New() err = json.Unmarshal(b, s3) require.NoError(t, err) + s3.ClearState() assert.Equal(t, "object", s3.GetType().Name(), "Схема должна восстановить тип object при восстановление регистрации типа") assert.Equal(t, sch, s3, "Схема должна восстановиться при восстановление регистрации типа") } diff --git a/pkg/setup/collection_test.go b/pkg/setup/collection_test.go index 43b450119a0892865486517df1d43a7e92d3dc2d..788314ca8aa15aa9e4bba63ae6c090f0e5662e69 100644 --- a/pkg/setup/collection_test.go +++ b/pkg/setup/collection_test.go @@ -35,12 +35,19 @@ func TestSetup_InstallCollections(t *testing.T) { }, }, { - name: "Install one collection success", - collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}}, + name: "Install one collection success", + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", + Schema: schema.New("name", field.String()).ClearState()}}, collectionsCall: func(svc *mockscollections.Collections) { svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once() - svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once() - svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once() + svc.On("Create", mock.Anything, + &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", + Schema: schema.New("name", field.String()).ClearState()}). + Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", + Schema: schema.New("name", field.String()).ClearState()}, nil).Once() + svc.On("SetSchema", mock.Anything, "sp", "env", "1", + schema.New("name", field.String()).ClearState()). + Return(nil).Once() }, envsCall: func(svc *envmocks.Environments) { svc.On("Migrate", mock.Anything, "sp", "env").Return(nil).Once() @@ -123,11 +130,15 @@ func TestSetup_InstallCollections(t *testing.T) { }, { name: "Fail to install collection on migrate", - collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}}, + collections: []*collections.Collection{{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String()).ClearState()}}, collectionsCall: func(svc *mockscollections.Collections) { svc.On("Get", mock.Anything, "sp", "env", "1").Return(nil, errors.New("not found")).Once() - svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}).Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", Schema: schema.New("name", field.String())}, nil).Once() - svc.On("SetSchema", mock.Anything, "sp", "env", "1", schema.New("name", field.String())).Return(nil).Once() + svc.On("Create", mock.Anything, &collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", + Schema: schema.New("name", field.String()).ClearState()}). + Return(&collections.Collection{ID: "1", SpaceID: "sp", Name: "space", EnvID: "env", + Schema: schema.New("name", field.String()).ClearState()}, nil).Once() + svc.On("SetSchema", mock.Anything, "sp", "env", "1", + schema.New("name", field.String()).ClearState()).Return(nil).Once() }, envsCall: func(svc *envmocks.Environments) { svc.On("Migrate", mock.Anything, "sp", "env").Return(errors.New("migrate error")).Once()