From cfde23afddafdfe28f041308d45ed8c82df068c3 Mon Sep 17 00:00:00 2001 From: Alena Petraki <a.petraki@perx.ru> Date: Wed, 14 Aug 2024 16:35:20 +0000 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F,=20=D1=8F=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BF=D0=BE=D0=BB=D0=B5=20SingleLocale?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/extension/schema_test.go | 2 + pkg/files/field.go | 32 ++++-- pkg/items/item_test.go | 1 + pkg/references/field.go | 5 +- pkg/schema/field/array.go | 16 +++ pkg/schema/field/boolean.go | 6 +- pkg/schema/field/field.go | 181 ++++++++++++++++++++++++-------- pkg/schema/field/field_json.go | 2 + pkg/schema/field/field_test.go | 101 ++++++++++++++++++ pkg/schema/field/location.go | 6 +- pkg/schema/field/number.go | 6 +- pkg/schema/field/object.go | 33 ++++++ pkg/schema/field/primary_key.go | 6 +- pkg/schema/field/string.go | 6 +- pkg/schema/field/time.go | 6 +- pkg/schema/field/timestamp.go | 6 +- pkg/schema/field/type.go | 13 +++ pkg/schema/field/unknown.go | 6 +- pkg/schema/schema.go | 12 ++- pkg/schema/schema_json_test.go | 2 + pkg/schema/test/convert_test.go | 4 + pkg/schema/test/object_test.go | 11 +- pkg/setup/collection_test.go | 25 +++-- 23 files changed, 407 insertions(+), 81 deletions(-) create mode 100644 pkg/schema/field/field_test.go diff --git a/pkg/extension/schema_test.go b/pkg/extension/schema_test.go index 20648678..4b4f235a 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 00659012..ffb315d5 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 2cef5e75..05baa60a 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 85482b9e..905c2d43 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 32d6f72f..bf1a4819 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 94580541..e06b704d 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 6edf18f5..4d2a6771 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 dfd7f85a..170e3d16 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 00000000..d27d74bb --- /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 d89b3206..97aea3ea 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 40d9c816..dfc8a663 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 1f995cce..45bc3e61 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 b0b26e16..da40f97e 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 b7e548b6..17b5e7ae 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 064906f2..cdb50a4c 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 694d477e..593218c6 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 edae7673..984cd591 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 ffe41226..36d0ba97 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 71d87276..fdb402b4 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 f9e6be1b..31b47516 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 170bee83..5e791570 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 f17b6451..8bf3f820 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 43b45011..788314ca 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() -- GitLab