From 8f81db4f538f303dfc3bba64e9a5add52cab15c0 Mon Sep 17 00:00:00 2001 From: Eugeny <15173395+ebirukov@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:56:18 +0300 Subject: [PATCH] #22 Custom mutators (#23) * #22 https://github.com/mailru/activerecord/issues/22 --------- Co-authored-by: e.birukov --- Makefile | 2 +- docs/manual.md | 57 ++++- internal/app/argen.go | 5 +- internal/app/argen_w_test.go | 24 +- internal/pkg/arerror/parse.go | 23 ++ internal/pkg/checker/checker.go | 34 ++- internal/pkg/checker/checker_b_test.go | 8 +- internal/pkg/checker/checker_w_test.go | 81 ++++--- internal/pkg/ds/app.go | 139 +++++++----- internal/pkg/ds/package.go | 28 ++- internal/pkg/ds/package_b_test.go | 90 +------- internal/pkg/generator/fixture.go | 1 + internal/pkg/generator/fixture_test.go | 13 +- internal/pkg/generator/generator.go | 3 + internal/pkg/generator/generator_b_test.go | 6 +- internal/pkg/generator/octopus.go | 4 +- internal/pkg/generator/octopus_b_test.go | 44 +++- .../generator/tmpl/octopus/fixturestore.tmpl | 26 +++ internal/pkg/generator/tmpl/octopus/main.tmpl | 202 +++++++++++++++-- internal/pkg/generator/tmpl/octopus/mock.tmpl | 29 +++ internal/pkg/parser/field.go | 13 +- internal/pkg/parser/field_b_test.go | 8 +- internal/pkg/parser/fieldobject_w_test.go | 4 +- internal/pkg/parser/import.go | 2 +- internal/pkg/parser/import_w_test.go | 23 +- internal/pkg/parser/mutator.go | 65 ++++++ internal/pkg/parser/mutator_b_test.go | 198 ++++++++++++++++ internal/pkg/parser/parser.go | 5 +- internal/pkg/parser/parser_b_test.go | 52 +++-- internal/pkg/parser/parser_w_test.go | 70 +++--- internal/pkg/parser/partialstruct.go | 211 ++++++++++++++++++ internal/pkg/parser/partialstruct_test.go | 61 +++++ internal/pkg/parser/testdata/ds/info.go | 10 + internal/pkg/parser/testdata/foo/foo.go | 12 + internal/pkg/parser/utils.go | 1 + pkg/octopus/types.go | 7 + pkg/serializer/mapstructure.go | 4 +- 37 files changed, 1258 insertions(+), 307 deletions(-) create mode 100644 internal/pkg/parser/mutator.go create mode 100644 internal/pkg/parser/mutator_b_test.go create mode 100644 internal/pkg/parser/partialstruct.go create mode 100644 internal/pkg/parser/partialstruct_test.go create mode 100644 internal/pkg/parser/testdata/ds/info.go create mode 100644 internal/pkg/parser/testdata/foo/foo.go diff --git a/Makefile b/Makefile index 4d80385..6069ed5 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LAST_COMMIT_HASH = $(shell git rev-parse HEAD | cut -c -8) # Таймаут для тестов TEST_TIMEOUT?=30s # Тег golang-ci -GOLANGCI_TAG:=1.52.2 +GOLANGCI_TAG:=1.54.2 # Путь до бинарников LOCAL_BIN:=$(CURDIR)/bin # Путь до бинарника golang-ci diff --git a/docs/manual.md b/docs/manual.md index 09f8112..efba082 100644 --- a/docs/manual.md +++ b/docs/manual.md @@ -107,9 +107,8 @@ IP адрес или имя хоста, где запущена БД - `selector` - имя метода-селектора, который нужно создать для соответствующего индекса; - `serializer` - позволяет навесить дополнительную сериализацию на поле; Формат: `Name[,params]`. Параметры необязательные, но если их указать то они будут переданы в функции `marshal`, `unmarshal` - `size` - длина поля в байтах (для числовых полей вычисляется автоматически). Используется при десериализации и при прогнозировании потребляемого объёма. -- `mutators` - список методов-модификаторов, которые нужно создать для поля для выполнения спец-операций, допустимо: `inc`, `dec`, `and`, `or`, `xor`, `set_bit`, `clear_bit`; - -!Внимание! Поля с сериализацией не попадают в список на обновление при изменении внутреннего состояния десериализованной структуры и как следствие не уходит в БД при Update. +- `mutators` - список методов-модификаторов, которые нужно создать для поля для выполнения спец-операций. Предопределены типы мутаторов: `inc`, `dec`, `and`, `or`, `xor`, `set_bit`, `clear_bit`. Но также возможно описать пользовательский тип мутатора. У каждого поля может быть несколько преопределенных мутаторов, но не более одного пользовательского +!Внимание! По умолчанию поля с сериализацией не попадают в список на обновление при изменении внутреннего состояния десериализованной структуры и как следствие не уходит в БД при Update. Но можно описать подобное поведение с помощью пользовательского типа мутатора #### Для octopus @@ -194,6 +193,58 @@ IP адрес или имя хоста, где запущена БД - `Printf` - позволяет хранить в БД строку в определённом формате подобном `printf`, обязательно указывать формат в определении поля, см. `serializer` в структуре `Fields` - `Mapstructure` - позволяет хранить в БД строку и десериализовывать ее в кастомный тип пользователя с возможностями библиотеки mapstructure см. https://pkg.go.dev/github.com/mitchellh/mapstructure +### Mutators* + +Объявление пользовательских мутаторов для полей. Когда необходима особая логика модификации полей, которую нельзя реализовать с помощью стандартных операций, например при изменении внутреннего состояния десериализованной структуры +Данная логика модификации реализуется на стороне БД с помощью функций/процедур. Сигнатура вызова процедуры следующая: + +- значение первичного ключа +- список значений возвращаемых функцией преобразования (см. ниже) или в случае отсутствии функции преобразования - значение модифицированного поля + +При указании мутатора для сериализуемого (составного) поля, для каждого поля сериализуемой структуры генерируется дополнительный метод установки значения. +Для формирования параметров вызова функций/процедур нужно реализовать функцию преобразования. Функция принимает на вход 2 параметра: значение поля до модификации и набор модифицированных значений полей составной структуры +В случае сериализуемого второй второй параметр содержит значения полей сериализуемой структуры внутри `map[string]any` + +- `pkg` - Для сериализаемых(составных) полей указывает на пакет в котором находится функция преобразования в параметры. В `octopus` для простых(несериализуемых) полей не требуется. +- `update` - имя функции/процедуры БД которая будет использоваться модификации данных. +- `replace` - имя функции/процедуры БД которая будет использоваться модификации данных. В `octopus` не реализована + + +Пример описания мутатора для сериализуемого поля +```golang +package ds + +type Bar struct { + Name string +} + +package model + +type FieldsFoo struct { + Bar string `ar:"serializer:Bar;mutators:BarPart;"` +} + +type SerializersFoo struct { + Bar *ds.Bar `ar:"pkg:......./model/serializer;object:BarS"` +} + +type MutatorsFoo struct { + BarPart bool `ar:"update:updateBar;pkg:......./model/conv;"` +} + +package conv + +func FooBarPart(bar *ds.Bar, partBar map[string]any) ([]string, error) { + values, err := serializer.Marshal(partBar) + if err != nil { + return nil, err + } + + return values +} +``` + + ### Triggers* Триггеры срабатывают на определённые исключительные случаи при запаковке/распаковке данных и/или при ошибках работы с БД. В каждом конкретном случае в триггер приходят свой набор данных. diff --git a/internal/app/argen.go b/internal/app/argen.go index 42f2e83..479c1ef 100644 --- a/internal/app/argen.go +++ b/internal/app/argen.go @@ -103,7 +103,8 @@ func (a *ArGen) addRecordPackage(pkgName string) (*ds.RecordPackage, error) { return nil, &arerror.ErrParseGenDecl{Name: pkgName, Err: arerror.ErrRedefined} } - a.packagesParsed[pkgName] = ds.NewRecordPackage() + rp := ds.NewRecordPackage() + a.packagesParsed[pkgName] = rp return a.packagesParsed[pkgName], nil } @@ -362,6 +363,8 @@ func (a *ArGen) parse() error { return fmt.Errorf("error model(%s) parse: %s", srcFileName, err) } + rc.Namespace.ModuleName = a.modName + // Запускаем процесс парсинга if err := parser.Parse(srcFileName, rc); err != nil { return fmt.Errorf("error parse declaration: %w", err) diff --git a/internal/app/argen_w_test.go b/internal/app/argen_w_test.go index 089421d..fcbf466 100644 --- a/internal/app/argen_w_test.go +++ b/internal/app/argen_w_test.go @@ -363,7 +363,7 @@ func TestArGen_preparePackage(t *testing.T) { Name: "ID", Format: octopus.Int, PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", @@ -377,7 +377,7 @@ func TestArGen_preparePackage(t *testing.T) { Name: "BarID", Format: octopus.Int, PrimaryKey: false, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "Bar", @@ -407,7 +407,7 @@ func TestArGen_preparePackage(t *testing.T) { Name: "ID", Format: octopus.Int, PrimaryKey: false, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", @@ -671,11 +671,11 @@ type TriggersFoo struct { wantErr: false, want: map[string]*ds.RecordPackage{ "foo": { - Namespace: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Foo", PackageName: "foo"}, + Namespace: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Foo", PackageName: "foo", ModuleName: "testmod"}, Server: ds.ServerDeclaration{Timeout: 500, Host: "127.0.0.1", Port: "11111"}, Fields: []ds.FieldDeclaration{ - {Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}}, - {Name: "Field2", Format: "string", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}}, + {Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}}, + {Name: "Field2", Format: "string", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}}, }, FieldsMap: map[string]int{"Field1": 0, "Field2": 1}, FieldsObjectMap: map[string]ds.FieldObject{}, @@ -710,14 +710,14 @@ type TriggersFoo struct { SelectorMap: map[string]int{"SelectByField1": 1, "SelectByField1Field2": 0}, Backends: []string{"octopus"}, SerializerMap: map[string]ds.SerializerDeclaration{}, - Imports: []ds.ImportDeclaration{ + ImportPackage: ds.ImportPackage{Imports: []ds.ImportDeclaration{ { Path: "github.com/mailru/activerecord-cookbook.git/example/model/repository/repair", ImportName: "triggerRepairTuple", }, }, - ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/repository/repair": 0}, - ImportPkgMap: map[string]int{"triggerRepairTuple": 0}, + ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/repository/repair": 0}, + ImportPkgMap: map[string]int{"triggerRepairTuple": 0}}, TriggerMap: map[string]ds.TriggerDeclaration{ "RepairTuple": { Name: "RepairTuple", @@ -727,7 +727,11 @@ type TriggersFoo struct { Params: map[string]bool{"Defaults": true}, }, }, - FlagMap: map[string]ds.FlagDeclaration{}}, + FlagMap: map[string]ds.FlagDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{}, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{}, + }, }, }, } diff --git a/internal/pkg/arerror/parse.go b/internal/pkg/arerror/parse.go index c8ce9cc..c35a276 100644 --- a/internal/pkg/arerror/parse.go +++ b/internal/pkg/arerror/parse.go @@ -129,6 +129,7 @@ var ErrParseFieldBinary = errors.New("binary format not implemented") var ErrParseFieldMutatorInvalid = errors.New("invalid mutator") var ErrParseFieldSizeInvalid = errors.New("error parse size") var ErrParseFieldNameInvalid = errors.New("invalid declaration name") +var ErrParseFieldMutatorTypeHasNotSerializer = errors.New("mutator type must have serializer") // Описание ошибки парсинга флагов поля сущности type ErrParseFlagTagDecl struct { @@ -204,6 +205,16 @@ func (e *ErrParseSerializerDecl) Error() string { return ErrorBase(e) } +// ErrParseMutatorDecl Описание ошибки парсинга мутаторов +type ErrParseMutatorDecl struct { + Name string + Err error +} + +func (e *ErrParseMutatorDecl) Error() string { + return ErrorBase(e) +} + // Описание ошибки парсинга тегов сериализатора type ErrParseSerializerTagDecl struct { Name string @@ -216,6 +227,18 @@ func (e *ErrParseSerializerTagDecl) Error() string { return ErrorBase(e) } +// ErrParseMutatorTagDecl Описание ошибки парсинга тегов сериализатора +type ErrParseMutatorTagDecl struct { + Name string + TagName string + TagValue string + Err error +} + +func (e *ErrParseMutatorTagDecl) Error() string { + return ErrorBase(e) +} + // Описание ошибки парсинга типов сериализатора type ErrParseSerializerTypeDecl struct { Name string diff --git a/internal/pkg/checker/checker.go b/internal/pkg/checker/checker.go index d795fd1..16ea6b0 100644 --- a/internal/pkg/checker/checker.go +++ b/internal/pkg/checker/checker.go @@ -49,7 +49,7 @@ func checkLinkedObject(cl *ds.RecordPackage, linkedObjects map[string]string) er } // checkNamespace проверка правильного описания неймспейса у сущности -func checkNamespace(ns *ds.NamespaceDeclaration) error { +func checkNamespace(ns ds.NamespaceDeclaration) error { if ns.PackageName == "" || ns.PublicName == "" { return &arerror.ErrCheckPackageNamespaceDecl{Pkg: ns.PackageName, Name: ns.PublicName, Err: arerror.ErrCheckEmptyNamespace} } @@ -90,18 +90,38 @@ func checkFields(cl *ds.RecordPackage) error { } if len(fld.Serializer) > 0 { - if _, ex := cl.SerializerMap[fld.Serializer[0]]; !ex { + if _, ex := cl.SerializerMap[fld.Serializer[0]]; len(cl.SerializerMap) == 0 || !ex { return &arerror.ErrCheckPackageFieldDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Err: arerror.ErrCheckFieldSerializerNotFound} } + } + customMutCnt := 0 if len(fld.Mutators) > 0 { - if fld.PrimaryKey { - return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictPK} + fieldMutatorsChecker := ds.GetFieldMutatorsChecker() + + for _, m := range fld.Mutators { + _, ex := fieldMutatorsChecker[m] + + md, ok := cl.MutatorMap[m] + if ok { + customMutCnt++ + if customMutCnt > 1 { + return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorInvalid} + } + } + + if !ok && !ex { + return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorInvalid} + } + + if len(md.PartialFields) > 0 && len(fld.Serializer) == 0 { + return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: m, Err: arerror.ErrParseFieldMutatorTypeHasNotSerializer} + } } - if len(fld.Serializer) > 0 { - return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictSerializer} + if fld.PrimaryKey { + return &arerror.ErrCheckPackageFieldMutatorDecl{Pkg: cl.Namespace.PackageName, Field: fld.Name, Mutator: string(fld.Mutators[0]), Err: arerror.ErrCheckFieldMutatorConflictPK} } if fld.ObjectLink != "" { @@ -178,7 +198,7 @@ func Check(files map[string]*ds.RecordPackage, linkedObjects map[string]string) return err } - if err := checkNamespace(&cl.Namespace); err != nil { + if err := checkNamespace(cl.Namespace); err != nil { return err } diff --git a/internal/pkg/checker/checker_b_test.go b/internal/pkg/checker/checker_b_test.go index 1ac71db..2ad6253 100644 --- a/internal/pkg/checker/checker_b_test.go +++ b/internal/pkg/checker/checker_b_test.go @@ -18,7 +18,7 @@ func TestCheck(t *testing.T) { Name: "ID", Format: octopus.Int, PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", @@ -32,7 +32,7 @@ func TestCheck(t *testing.T) { Name: "BarID", Format: octopus.Int, PrimaryKey: false, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "Bar", @@ -63,7 +63,7 @@ func TestCheck(t *testing.T) { Name: "ID", Format: "byte", PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", @@ -82,7 +82,7 @@ func TestCheck(t *testing.T) { Name: "ID", Format: "byte", PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", diff --git a/internal/pkg/checker/checker_w_test.go b/internal/pkg/checker/checker_w_test.go index 9e509ad..e2c884a 100644 --- a/internal/pkg/checker/checker_w_test.go +++ b/internal/pkg/checker/checker_w_test.go @@ -74,7 +74,7 @@ func Test_checkLinkedObject(t *testing.T) { func Test_checkNamespace(t *testing.T) { type args struct { - ns *ds.NamespaceDeclaration + ns ds.NamespaceDeclaration } tests := []struct { name string @@ -84,7 +84,7 @@ func Test_checkNamespace(t *testing.T) { { name: "normal namespace", args: args{ - ns: &ds.NamespaceDeclaration{ + ns: ds.NamespaceDeclaration{ ObjectName: "0", PublicName: "Foo", PackageName: "foo", @@ -95,7 +95,7 @@ func Test_checkNamespace(t *testing.T) { { name: "empty name", args: args{ - ns: &ds.NamespaceDeclaration{ + ns: ds.NamespaceDeclaration{ ObjectName: "0", PublicName: "", PackageName: "foo", @@ -106,7 +106,7 @@ func Test_checkNamespace(t *testing.T) { { name: "empty package", args: args{ - ns: &ds.NamespaceDeclaration{ + ns: ds.NamespaceDeclaration{ ObjectName: "0", PublicName: "Foo", PackageName: "", @@ -225,7 +225,7 @@ func Test_checkFields(t *testing.T) { Name: "Foo", Format: "int", PrimaryKey: true, - Mutators: []ds.FieldMutator{ + Mutators: []string{ "fmut", }, }, @@ -247,7 +247,7 @@ func Test_checkFields(t *testing.T) { { Name: "Foo", Format: "int", - Mutators: []ds.FieldMutator{ + Mutators: []string{ "fmut", }, Serializer: []string{ @@ -255,15 +255,13 @@ func Test_checkFields(t *testing.T) { }, }, }, - SerializerMap: map[string]ds.SerializerDeclaration{ - "fser": {}, - }, + SerializerMap: map[string]ds.SerializerDeclaration{}, }, }, wantErr: true, }, { - name: "serializer and mutators", + name: "mutators and links", args: args{ cl: ds.RecordPackage{ Fields: []ds.FieldDeclaration{ @@ -275,12 +273,33 @@ func Test_checkFields(t *testing.T) { { Name: "Foo", Format: "int", - Mutators: []ds.FieldMutator{ + Mutators: []string{ "fmut", }, + ObjectLink: "Bar", + }, + }, + }, + }, + wantErr: true, + }, + { + name: "serializer and links", + args: args{ + cl: ds.RecordPackage{ + Fields: []ds.FieldDeclaration{ + { + Name: "Foo", + Format: "int", + PrimaryKey: true, + }, + { + Name: "Foo", + Format: "int", Serializer: []string{ "fser", }, + ObjectLink: "Bar", }, }, SerializerMap: map[string]ds.SerializerDeclaration{ @@ -291,22 +310,28 @@ func Test_checkFields(t *testing.T) { wantErr: true, }, { - name: "mutators and links", + name: "custom mutator without serializer", args: args{ cl: ds.RecordPackage{ Fields: []ds.FieldDeclaration{ { - Name: "Foo", + Name: "Pk", Format: "int", PrimaryKey: true, }, { Name: "Foo", - Format: "int", - Mutators: []ds.FieldMutator{ - "fmut", + Format: "string", + Mutators: []string{ + "cmut", }, - ObjectLink: "Bar", + }, + }, + MutatorMap: map[string]ds.MutatorDeclaration{ + "cmut": { + Name: "cmut", + Type: "pkg.Bar", + PartialFields: make([]ds.PartialFieldDeclaration, 1), }, }, }, @@ -314,26 +339,32 @@ func Test_checkFields(t *testing.T) { wantErr: true, }, { - name: "serializer and links", + name: "few custom mutator on field", args: args{ cl: ds.RecordPackage{ Fields: []ds.FieldDeclaration{ { - Name: "Foo", + Name: "Pk", Format: "int", PrimaryKey: true, }, { Name: "Foo", - Format: "int", - Serializer: []string{ - "fser", + Format: "string", + Mutators: []string{ + "dec", "cmut", "cmut2", }, - ObjectLink: "Bar", }, }, - SerializerMap: map[string]ds.SerializerDeclaration{ - "fser": {}, + MutatorMap: map[string]ds.MutatorDeclaration{ + "cmut": { + Name: "cmut", + Type: "string", + }, + "cmut2": { + Name: "cmut2", + Type: "string", + }, }, }, }, diff --git a/internal/pkg/ds/app.go b/internal/pkg/ds/app.go index 4c25b15..861415d 100644 --- a/internal/pkg/ds/app.go +++ b/internal/pkg/ds/app.go @@ -70,48 +70,68 @@ type ServerDeclaration struct { Host, Port, Conf string } +type ImportPackage struct { + Imports []ImportDeclaration // Список необходимых дополнительных импортов, формируется из директивы import + ImportMap map[string]int // Обратный индекс от имен по импортам + ImportPkgMap map[string]int // Обратный индекс от пакетов к импортам +} + // Структура описывающая отдельную сущность представленную в декларативном файле type RecordPackage struct { - Server ServerDeclaration // Описание сервера - Namespace NamespaceDeclaration // Описание неймспейса/таблицы - Fields []FieldDeclaration // Описание полей, важна последовательность для некоторых хранилищ - FieldsMap map[string]int // Обратный индекс от имен к полям - FieldsObjectMap map[string]FieldObject // Обратный индекс по имени для ссылок на другие сущности - Indexes []IndexDeclaration // Список индексов, важна последовательность для некоторых хранилищ - IndexMap map[string]int // Обратный индекс от имён для индексов - SelectorMap map[string]int // Список селекторов, используется для контроля дублей - Backends []string // Список бекендов для которых надо сгенерировать пакеты (сейчас допустим один и только один) - SerializerMap map[string]SerializerDeclaration // Список сериализаторов используемых в этой сущности - Imports []ImportDeclaration // Список необходимых дополнительных импортов, формируется из директивы import - ImportMap map[string]int // Обратный индекс от имен по импортам - ImportPkgMap map[string]int // Обратный индекс от пакетов к импортам - TriggerMap map[string]TriggerDeclaration // Список триггеров используемых в сущности - FlagMap map[string]FlagDeclaration // Список флагов используемых в полях сущности - ProcInFields []ProcFieldDeclaration // Описание входных параметров процедуры, важна последовательность - ProcOutFields ProcFieldDeclarations // Описание выходных параметров процедуры, важна последовательность - ProcFieldsMap map[string]int // Обратный индекс от имен + ImportPackage + Server ServerDeclaration // Описание сервера + Namespace NamespaceDeclaration // Описание неймспейса/таблицы + Fields []FieldDeclaration // Описание полей, важна последовательность для некоторых хранилищ + FieldsMap map[string]int // Обратный индекс от имен к полям + FieldsObjectMap map[string]FieldObject // Обратный индекс по имени для ссылок на другие сущности + Indexes []IndexDeclaration // Список индексов, важна последовательность для некоторых хранилищ + IndexMap map[string]int // Обратный индекс от имён для индексов + SelectorMap map[string]int // Список селекторов, используется для контроля дублей + Backends []string // Список бекендов для которых надо сгенерировать пакеты (сейчас допустим один и только один) + SerializerMap map[string]SerializerDeclaration // Список сериализаторов используемых в этой сущности + MutatorMap map[string]MutatorDeclaration // Список мутаторов используемых в этой сущности + TriggerMap map[string]TriggerDeclaration // Список триггеров используемых в сущности + FlagMap map[string]FlagDeclaration // Список флагов используемых в полях сущности + ProcInFields []ProcFieldDeclaration // Описание входных параметров процедуры, важна последовательность + ProcOutFields ProcFieldDeclarations // Описание выходных параметров процедуры, важна последовательность + ProcFieldsMap map[string]int // Обратный индекс от имен + LinkedStructsMap map[string]LinkedPackageDeclaration // Описание пакетов связанных типов + ImportStructFieldsMap map[string][]PartialFieldDeclaration // Описаний структур импортируемых полей сущности +} + +func NewImportPackage() ImportPackage { + return ImportPackage{ + Imports: []ImportDeclaration{}, + ImportMap: map[string]int{}, + ImportPkgMap: map[string]int{}, + } } // Конструктор для RecordPackage, инициализирует ссылочные типы func NewRecordPackage() *RecordPackage { return &RecordPackage{ - Server: ServerDeclaration{}, - Namespace: NamespaceDeclaration{}, - Fields: []FieldDeclaration{}, - FieldsMap: map[string]int{}, - FieldsObjectMap: map[string]FieldObject{}, - Indexes: []IndexDeclaration{}, - IndexMap: map[string]int{}, - SelectorMap: map[string]int{}, - Imports: []ImportDeclaration{}, - ImportMap: map[string]int{}, - ImportPkgMap: map[string]int{}, - Backends: []string{}, - SerializerMap: map[string]SerializerDeclaration{}, - TriggerMap: map[string]TriggerDeclaration{}, - FlagMap: map[string]FlagDeclaration{}, - ProcFieldsMap: map[string]int{}, - ProcOutFields: map[int]ProcFieldDeclaration{}, + ImportPackage: ImportPackage{ + Imports: []ImportDeclaration{}, + ImportMap: map[string]int{}, + ImportPkgMap: map[string]int{}, + }, + Server: ServerDeclaration{}, + Namespace: NamespaceDeclaration{}, + Fields: []FieldDeclaration{}, + FieldsMap: map[string]int{}, + FieldsObjectMap: map[string]FieldObject{}, + Indexes: []IndexDeclaration{}, + IndexMap: map[string]int{}, + SelectorMap: map[string]int{}, + Backends: []string{}, + SerializerMap: map[string]SerializerDeclaration{}, + MutatorMap: map[string]MutatorDeclaration{}, + TriggerMap: map[string]TriggerDeclaration{}, + FlagMap: map[string]FlagDeclaration{}, + ProcFieldsMap: map[string]int{}, + ProcOutFields: map[int]ProcFieldDeclaration{}, + LinkedStructsMap: map[string]LinkedPackageDeclaration{}, + ImportStructFieldsMap: map[string][]PartialFieldDeclaration{}, } } @@ -150,7 +170,7 @@ type FieldDeclaration struct { Name string // Название поля Format octopus.Format // формат поля PrimaryKey bool // участвует ли поле в первичном ключе (при изменении таких полей необходимо делать delete + insert вместо update) - Mutators []FieldMutator // список мутаторов (атомарных действий на уровне БД) + Mutators []string // список мутаторов (атомарных действий на уровне БД) Size int64 // Размер поля, используется только для строковых значений Serializer Serializer // Сериализаторы для поля ObjectLink string // является ли поле ссылкой на другую сущность @@ -255,20 +275,18 @@ func (pfd ProcFieldDeclarations) Validate() bool { return maxIdx < len(pfd) } -// Тип и константы описывающие мутаторы для поля -type FieldMutator string - +// Константы описывающие мутаторы для поля const ( - IncMutator FieldMutator = "inc" // инкремент (только для числовых типов) - DecMutator FieldMutator = "dec" // декремент (только для числовых типов) - SetBitMutator FieldMutator = "set_bit" // установка бита (только для целочисленных типов) - ClearBitMutator FieldMutator = "clear_bit" // снятие бита (только для целочисленных типов) - AndMutator FieldMutator = "and" // дизъюнкция (только для целочисленных типов) - OrMutator FieldMutator = "or" // конъюнкция (только для целочисленных типов) - XorMutator FieldMutator = "xor" // xor (только для целочисленных типов) + IncMutator string = "inc" // инкремент (только для числовых типов) + DecMutator string = "dec" // декремент (только для числовых типов) + SetBitMutator string = "set_bit" // установка бита (только для целочисленных типов) + ClearBitMutator string = "clear_bit" // снятие бита (только для целочисленных типов) + AndMutator string = "and" // дизъюнкция (только для целочисленных типов) + OrMutator string = "or" // конъюнкция (только для целочисленных типов) + XorMutator string = "xor" // xor (только для целочисленных типов) ) -var FieldMutators = [...]FieldMutator{ +var FieldMutators = [...]string{ IncMutator, DecMutator, SetBitMutator, @@ -277,13 +295,13 @@ var FieldMutators = [...]FieldMutator{ OrMutator, XorMutator} -var fieldMutatorsChecker = map[string]FieldMutator{} +var fieldMutatorsChecker = map[string]string{} var FieldMutatorsCheckerOnce sync.Once -func GetFieldMutatorsChecker() map[string]FieldMutator { +func GetFieldMutatorsChecker() map[string]string { FieldMutatorsCheckerOnce.Do(func() { for _, f := range FieldMutators { - fieldMutatorsChecker[string(f)] = f + fieldMutatorsChecker[f] = f } }) @@ -309,6 +327,17 @@ type SerializerDeclaration struct { Unmarshaler string // Имя функции анмаршаллера } +// MutatorDeclaration Структура описывающая мутатор +type MutatorDeclaration struct { + Name string // имя + Pkg string // Пакет для импорта + Type string // Тип данных + ImportName string // Симлинк для импорта + Update string // Имя функции для параметров обновления + Replace string // Имя функции для параметров замены + PartialFields []PartialFieldDeclaration +} + // Структура описывающая дополнительный импорты type ImportDeclaration struct { Path string // Путь к пакету @@ -329,3 +358,13 @@ type FlagDeclaration struct { Name string // Имя Flags []string // Список имён флагов } + +type PartialFieldDeclaration struct { + Name string // Имя части поля + Type string // Тип части поля +} + +type LinkedPackageDeclaration struct { + Types map[string]struct{} // Имена типов связанных структур + Import ImportPackage // Описание импорта пакета связанных структур +} diff --git a/internal/pkg/ds/package.go b/internal/pkg/ds/package.go index 35b4013..4a9f98b 100644 --- a/internal/pkg/ds/package.go +++ b/internal/pkg/ds/package.go @@ -100,7 +100,7 @@ func (rc *RecordPackage) AddIndex(ind IndexDeclaration) error { return nil } -func (rc *RecordPackage) AddImport(path string, reqImportName ...string) (ImportDeclaration, error) { +func (rc *ImportPackage) AddImport(path string, reqImportName ...string) (ImportDeclaration, error) { if len(reqImportName) > 1 { return ImportDeclaration{}, &arerror.ErrParseImportDecl{Path: path, Err: arerror.ErrInvalidParams} } @@ -146,7 +146,7 @@ func (rc *RecordPackage) AddImport(path string, reqImportName ...string) (Import return newImport, nil } -func (rc *RecordPackage) FindImport(path string) (ImportDeclaration, error) { +func (rc *ImportPackage) FindImport(path string) (ImportDeclaration, error) { if impNum, ex := rc.ImportMap[path]; ex { return rc.Imports[impNum], nil } @@ -154,7 +154,7 @@ func (rc *RecordPackage) FindImport(path string) (ImportDeclaration, error) { return ImportDeclaration{}, &arerror.ErrParseImportDecl{Path: path, Err: arerror.ErrParseImportNotFound} } -func (rc *RecordPackage) FindImportByPkg(pkg string) (*ImportDeclaration, error) { +func (rc *ImportPackage) FindImportByPkg(pkg string) (*ImportDeclaration, error) { if impNum, ex := rc.ImportPkgMap[pkg]; ex { return &rc.Imports[impNum], nil } @@ -162,7 +162,7 @@ func (rc *RecordPackage) FindImportByPkg(pkg string) (*ImportDeclaration, error) return nil, &arerror.ErrParseImportDecl{Name: pkg, Err: arerror.ErrParseImportNotFound} } -func (rc *RecordPackage) FindOrAddImport(path, importName string) (ImportDeclaration, error) { +func (rc *ImportPackage) FindOrAddImport(path, importName string) (ImportDeclaration, error) { imp, err := rc.FindImport(path) if err != nil { var impErr *arerror.ErrParseImportDecl @@ -211,3 +211,23 @@ func (rc *RecordPackage) AddFlag(f FlagDeclaration) error { return nil } + +func (rc *RecordPackage) AddMutator(m MutatorDeclaration) error { + if _, ex := rc.MutatorMap[m.Name]; ex { + return &arerror.ErrParseMutatorDecl{Name: m.Name, Err: arerror.ErrRedefined} + } + + rc.MutatorMap[m.Name] = m + + return nil +} + +func (rc *RecordPackage) AddPartialField(m MutatorDeclaration) error { + if _, ex := rc.MutatorMap[m.Name]; ex { + return &arerror.ErrParseMutatorDecl{Name: m.Name, Err: arerror.ErrRedefined} + } + + rc.MutatorMap[m.Name] = m + + return nil +} diff --git a/internal/pkg/ds/package_b_test.go b/internal/pkg/ds/package_b_test.go index 819e8e2..e3cb371 100644 --- a/internal/pkg/ds/package_b_test.go +++ b/internal/pkg/ds/package_b_test.go @@ -247,23 +247,7 @@ func TestRecordClass_AddField(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rc := &ds.RecordPackage{ - Server: tt.fields.Server, - Namespace: tt.fields.Namespace, - Fields: tt.fields.Fields, - FieldsMap: tt.fields.FieldsMap, - FieldsObjectMap: tt.fields.FieldsObjectMap, - Indexes: tt.fields.Indexes, - IndexMap: tt.fields.IndexMap, - SelectorMap: tt.fields.SelectorMap, - Backends: tt.fields.Backends, - SerializerMap: tt.fields.SerializerMap, - Imports: tt.fields.Imports, - ImportMap: tt.fields.ImportMap, - TriggerMap: tt.fields.TriggerMap, - FlagMap: tt.fields.FlagMap, - } - if err := rc.AddField(tt.args.f); (err != nil) != tt.wantErr { + if err := tt.fields.AddField(tt.args.f); (err != nil) != tt.wantErr { t.Errorf("RecordClass.AddField() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -290,23 +274,7 @@ func TestRecordClass_AddFieldObject(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rc := &ds.RecordPackage{ - Server: tt.fields.Server, - Namespace: tt.fields.Namespace, - Fields: tt.fields.Fields, - FieldsMap: tt.fields.FieldsMap, - FieldsObjectMap: tt.fields.FieldsObjectMap, - Indexes: tt.fields.Indexes, - IndexMap: tt.fields.IndexMap, - SelectorMap: tt.fields.SelectorMap, - Backends: tt.fields.Backends, - SerializerMap: tt.fields.SerializerMap, - Imports: tt.fields.Imports, - ImportMap: tt.fields.ImportMap, - TriggerMap: tt.fields.TriggerMap, - FlagMap: tt.fields.FlagMap, - } - if err := rc.AddFieldObject(tt.args.f); (err != nil) != tt.wantErr { + if err := tt.fields.AddFieldObject(tt.args.f); (err != nil) != tt.wantErr { t.Errorf("RecordClass.AddFieldObject() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -332,23 +300,7 @@ func TestRecordClass_AddTrigger(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rc := &ds.RecordPackage{ - Server: tt.fields.Server, - Namespace: tt.fields.Namespace, - Fields: tt.fields.Fields, - FieldsMap: tt.fields.FieldsMap, - FieldsObjectMap: tt.fields.FieldsObjectMap, - Indexes: tt.fields.Indexes, - IndexMap: tt.fields.IndexMap, - SelectorMap: tt.fields.SelectorMap, - Backends: tt.fields.Backends, - SerializerMap: tt.fields.SerializerMap, - Imports: tt.fields.Imports, - ImportMap: tt.fields.ImportMap, - TriggerMap: tt.fields.TriggerMap, - FlagMap: tt.fields.FlagMap, - } - if err := rc.AddTrigger(tt.args.f); (err != nil) != tt.wantErr { + if err := tt.fields.AddTrigger(tt.args.f); (err != nil) != tt.wantErr { t.Errorf("RecordClass.AddTrigger() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -374,23 +326,7 @@ func TestRecordClass_AddSerializer(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rc := &ds.RecordPackage{ - Server: tt.fields.Server, - Namespace: tt.fields.Namespace, - Fields: tt.fields.Fields, - FieldsMap: tt.fields.FieldsMap, - FieldsObjectMap: tt.fields.FieldsObjectMap, - Indexes: tt.fields.Indexes, - IndexMap: tt.fields.IndexMap, - SelectorMap: tt.fields.SelectorMap, - Backends: tt.fields.Backends, - SerializerMap: tt.fields.SerializerMap, - Imports: tt.fields.Imports, - ImportMap: tt.fields.ImportMap, - TriggerMap: tt.fields.TriggerMap, - FlagMap: tt.fields.FlagMap, - } - if err := rc.AddSerializer(tt.args.f); (err != nil) != tt.wantErr { + if err := tt.fields.AddSerializer(tt.args.f); (err != nil) != tt.wantErr { t.Errorf("RecordClass.AddSerializer() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -416,23 +352,7 @@ func TestRecordClass_AddFlag(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - rc := &ds.RecordPackage{ - Server: tt.fields.Server, - Namespace: tt.fields.Namespace, - Fields: tt.fields.Fields, - FieldsMap: tt.fields.FieldsMap, - FieldsObjectMap: tt.fields.FieldsObjectMap, - Indexes: tt.fields.Indexes, - IndexMap: tt.fields.IndexMap, - SelectorMap: tt.fields.SelectorMap, - Backends: tt.fields.Backends, - SerializerMap: tt.fields.SerializerMap, - Imports: tt.fields.Imports, - ImportMap: tt.fields.ImportMap, - TriggerMap: tt.fields.TriggerMap, - FlagMap: tt.fields.FlagMap, - } - if err := rc.AddFlag(tt.args.f); (err != nil) != tt.wantErr { + if err := tt.fields.AddFlag(tt.args.f); (err != nil) != tt.wantErr { t.Errorf("RecordClass.AddFlag() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/internal/pkg/generator/fixture.go b/internal/pkg/generator/fixture.go index d622ebd..5b3a2c3 100644 --- a/internal/pkg/generator/fixture.go +++ b/internal/pkg/generator/fixture.go @@ -25,6 +25,7 @@ type FixturePkgData struct { Container ds.NamespaceDeclaration Indexes []ds.IndexDeclaration Serializers map[string]ds.SerializerDeclaration + Mutators map[string]ds.MutatorDeclaration Imports []ds.ImportDeclaration AppInfo string } diff --git a/internal/pkg/generator/fixture_test.go b/internal/pkg/generator/fixture_test.go index ad7f3be..05e01f9 100644 --- a/internal/pkg/generator/fixture_test.go +++ b/internal/pkg/generator/fixture_test.go @@ -60,21 +60,21 @@ func TestGenerateFixture(t *testing.T) { Name: "Id", Format: "string", PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Serializer: []string{}, ObjectLink: "", }, { Name: "Code", Format: "string", - Mutators: []ds.FieldMutator{}, + Mutators: []string{"Any"}, Serializer: []string{}, ObjectLink: "", }, { Name: "Inv", Format: "bool", - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Serializer: []string{}, ObjectLink: "", }, @@ -82,6 +82,12 @@ func TestGenerateFixture(t *testing.T) { FieldObject: map[string]ds.FieldObject{}, Container: ds.NamespaceDeclaration{ObjectName: "0", PublicName: "Testmodel", PackageName: "testmodel"}, Serializers: map[string]ds.SerializerDeclaration{}, + Mutators: map[string]ds.MutatorDeclaration{ + "Any": { + Name: "Any", + Update: "any", + }, + }, Imports: []ds.ImportDeclaration{ { ImportName: "obj", @@ -99,6 +105,7 @@ func TestGenerateFixture(t *testing.T) { `type GiftBySelectByInvMocker struct {`, `var giftStore map[string]*gift.Gift`, `func initGift() {`, + `func GetUpdateMutatorAnyFixtureById(ctx context.Context, Id string) (fxt octopus.FixtureType) {`, }, }, }, diff --git a/internal/pkg/generator/generator.go b/internal/pkg/generator/generator.go index d9ca327..37ce6a9 100644 --- a/internal/pkg/generator/generator.go +++ b/internal/pkg/generator/generator.go @@ -41,6 +41,7 @@ type PkgData struct { Container ds.NamespaceDeclaration Indexes []ds.IndexDeclaration Serializers map[string]ds.SerializerDeclaration + Mutators map[string]ds.MutatorDeclaration Imports []ds.ImportDeclaration Triggers map[string]ds.TriggerDeclaration Flags map[string]ds.FlagDeclaration @@ -60,6 +61,7 @@ func NewPkgData(appInfo string, cl ds.RecordPackage) PkgData { Server: cl.Server, Container: cl.Namespace, Serializers: cl.SerializerMap, + Mutators: cl.MutatorMap, Imports: cl.Imports, Triggers: cl.TriggerMap, Flags: cl.FlagMap, @@ -238,6 +240,7 @@ func GenerateFixture(appInfo string, cl ds.RecordPackage, pkg string, pkgFixture Container: cl.Namespace, Indexes: cl.Indexes, Serializers: cl.SerializerMap, + Mutators: cl.MutatorMap, Imports: cl.Imports, AppInfo: appInfo, } diff --git a/internal/pkg/generator/generator_b_test.go b/internal/pkg/generator/generator_b_test.go index 5617335..4a71b7e 100644 --- a/internal/pkg/generator/generator_b_test.go +++ b/internal/pkg/generator/generator_b_test.go @@ -37,7 +37,7 @@ func TestGenerate(t *testing.T) { }, Backends: []string{"octopus"}, Fields: []ds.FieldDeclaration{ - {Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}}, + {Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}}, }, FieldsMap: map[string]int{"Field1": 0}, FieldsObjectMap: map[string]ds.FieldObject{}, @@ -57,9 +57,7 @@ func TestGenerate(t *testing.T) { }, IndexMap: map[string]int{"Field1": 0}, SelectorMap: map[string]int{"SelectByField1": 0}, - Imports: []ds.ImportDeclaration{}, - ImportMap: map[string]int{}, - ImportPkgMap: map[string]int{}, + ImportPackage: ds.NewImportPackage(), SerializerMap: map[string]ds.SerializerDeclaration{}, TriggerMap: map[string]ds.TriggerDeclaration{}, FlagMap: map[string]ds.FlagDeclaration{}, diff --git a/internal/pkg/generator/octopus.go b/internal/pkg/generator/octopus.go index c8a5ebd..ef30e34 100644 --- a/internal/pkg/generator/octopus.go +++ b/internal/pkg/generator/octopus.go @@ -81,7 +81,7 @@ var OctopusTemplateFuncs = template.FuncMap{ return ret }, - "mutatorParam": func(mut ds.FieldMutator, format octopus.Format) OctopusMutatorParam { + "mutatorParam": func(mut string, format octopus.Format) OctopusMutatorParam { ret, ex := OctopusMutatorMapper[mut] if !ex { log.Fatalf("mutator packer for type `%s` not found", format) @@ -252,7 +252,7 @@ var OctopusFormatMapper = map[octopus.Format]OctopusFormatParam{ octopus.Float64: {Name: "Uint64", len: 9, convstr: "strconv.FormatFloat(%%, 64)", packConvFunc: "math.Float64bits", UnpackConvFunc: "math.Float64frombits", unpackType: "uint64", minValue: "math.MinFloat64", maxValue: "math.MaxFloat64"}, octopus.String: {Name: "String", convstr: " %% ", lenFunc: octopus.ByteLen, packFunc: "octopus.PackString", unpackFunc: "octopus.UnpackString", minValue: "0", maxValue: "4096", unpackType: "string"}} -var OctopusMutatorMapper = map[ds.FieldMutator]OctopusMutatorParam{ +var OctopusMutatorMapper = map[string]OctopusMutatorParam{ ds.IncMutator: {Name: "Inc", AvailableType: octopus.NumericFormat}, ds.DecMutator: {Name: "Dec", AvailableType: octopus.NumericFormat}, ds.AndMutator: {Name: "And", AvailableType: octopus.UnsignedFormat}, diff --git a/internal/pkg/generator/octopus_b_test.go b/internal/pkg/generator/octopus_b_test.go index d2be1d1..a502571 100644 --- a/internal/pkg/generator/octopus_b_test.go +++ b/internal/pkg/generator/octopus_b_test.go @@ -24,7 +24,7 @@ func TestGenerateOctopus(t *testing.T) { wantStr map[string][]string }{ { - name: "simplePkg", + name: "fieldsPkg", want: nil, args: args{ params: PkgData{ @@ -59,7 +59,7 @@ func TestGenerateOctopus(t *testing.T) { Name: "Field1", Format: "int", PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{ds.IncMutator}, Serializer: []string{}, ObjectLink: "", }, @@ -67,7 +67,15 @@ func TestGenerateOctopus(t *testing.T) { Name: "Field2", Format: "bool", PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, + Serializer: []string{}, + ObjectLink: "", + }, + { + Name: "Fs", + Format: "string", + PrimaryKey: true, + Mutators: []string{"FsMutator"}, Serializer: []string{}, ObjectLink: "", }, @@ -76,9 +84,24 @@ func TestGenerateOctopus(t *testing.T) { Server: ds.ServerDeclaration{Timeout: 500, Host: "127.0.0.1", Port: "11011"}, Container: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Testmodel", PackageName: "testmodel"}, Serializers: map[string]ds.SerializerDeclaration{}, - Imports: []ds.ImportDeclaration{}, - Triggers: map[string]ds.TriggerDeclaration{}, - Flags: map[string]ds.FlagDeclaration{}, + Mutators: map[string]ds.MutatorDeclaration{ + "FsMutator": { + Name: "FsMutator", + Pkg: "github.com/mailru/activerecord/internal/pkg/conv", + Type: "*parser_test.Foo", + ImportName: "mutatorFooMutatorField", + Update: "updateFunc", + Replace: "replaceFunc", + PartialFields: []ds.PartialFieldDeclaration{ + {Name: "Bar", Type: "ds.AppInfo"}, + {Name: "BeerData", Type: "[]Beer"}, + {Name: "MapData", Type: "map[string]any"}, + }, + }, + }, + Imports: []ds.ImportDeclaration{}, + Triggers: map[string]ds.TriggerDeclaration{}, + Flags: map[string]ds.FlagDeclaration{}, }, }, wantStr: map[string][]string{ @@ -96,11 +119,19 @@ func TestGenerateOctopus(t *testing.T) { `func selectBox (ctx context.Context, indexnum uint32, keysPacked [][][]byte, limiter activerecord.SelectorLimiter) ([]*Foo, error) {`, `func (obj *Foo) SetField1(Field1 int) error {`, `func (obj *Foo) GetField1() int {`, + `type Mutators struct {`, + `newObj.FsMutator.OpFunc`, + `newObj.FsMutator.PartialFields`, + `func (obj *Foo) SetFsMutatorBar(Bar ds.AppInfo) error {`, + `func (obj *Foo) SetFsMutatorBeerData(BeerData []Beer) error {`, + `func (obj *Foo) SetFsMutatorMapData(MapData map[string]any) error {`, + `func (obj *Foo) packFsPartialFields(op octopus.OpCode) error {`, `func UnpackField1(r *bytes.Reader) (ret int, errRet error) {`, `func packField1(w []byte, Field1 int) ([]byte, error) {`, `func NewFromBox(ctx context.Context, tuples []octopus.TupleData) ([]*Foo, error) {`, `func TupleToStruct(ctx context.Context, tuple octopus.TupleData) (*Foo, error) {`, `func New(ctx context.Context) *Foo {`, + `func (obj *Foo) IncField1(mutArg int) error {`, `namespace uint32 = ` + namespaceStr, `type Foo struct {`, `package ` + packageName, @@ -115,6 +146,7 @@ func TestGenerateOctopus(t *testing.T) { `func SelectByField1MockerLogger(keys [], res FooList) func() (activerecord.MockerLogger, error) {`, `func (obj *Foo) MockSelectByField1sRequest(ctx context.Context, keys [], ) []byte {`, `func (obj *Foo) MockSelectResponse() ([][]byte, error) {`, + `func (obj *Foo) MockMutatorFsMutatorUpdate(ctx context.Context) [][]byte {`, }, "fixture": { `type FooFT struct {`, diff --git a/internal/pkg/generator/tmpl/octopus/fixturestore.tmpl b/internal/pkg/generator/tmpl/octopus/fixturestore.tmpl index a25ebdc..86b2f7f 100644 --- a/internal/pkg/generator/tmpl/octopus/fixturestore.tmpl +++ b/internal/pkg/generator/tmpl/octopus/fixturestore.tmpl @@ -27,6 +27,8 @@ import ( {{ $procInLen := len .ProcInFieldList }} {{ $typePK := "" -}} {{ $fieldNamePK := "" -}} +{{ $mutators := .Mutators -}} +{{ $mutatorLen := len .Mutators }} {{ if $procfields }} {{ $typePK := "string" -}} @@ -183,6 +185,30 @@ func GetUpdate{{$PublicStructName}}FixtureBy{{ $fieldNamePK }}(ctx context.Conte return octopus.CreateUpdateFixture(obj.MockUpdate(ctx), wrappedTrigger), promiseIsUsed } +{{ range $ind, $fstruct := $fields -}} +{{- range $i, $mut := $fstruct.Mutators -}} +{{ $customMutator := index $mutators $mut -}} +{{ if and (ne $customMutator.Update "") $customMutator.Name }} +// Нужно доработать, т.к. пока из файла репозитория фикстур update.yaml нельзя выборочно устанавливать значения частично обновляемого поля +// Все неустановленные поля будут проинициализированы дефолтными значениями +func GetUpdateMutator{{$customMutator.Name}}FixtureBy{{ $fieldNamePK }}(ctx context.Context, {{ $fieldNamePK }} {{$typePK}}) (fxt octopus.FixtureType) { + obj := GetUpdate{{$PublicStructName}}By{{$fieldNamePK}}({{ $fieldNamePK }}) + + for _, req := range obj.MockMutator{{$customMutator.Name}}Update(ctx) { + ft, _ := octopus.CreateCallFixture( + func(wsubME []octopus.MockEntities) []byte { + return req + }, nil) + // available only one + return ft + } + + return +} +{{end}} +{{end}} +{{end}} + {{ range $_, $mockOperation := split "Insert,Replace,InsertOrReplace" "," }} func Get{{ $mockOperation }}{{$PublicStructName}}FixtureBy{{ $fieldNamePK }}(ctx context.Context, {{ $fieldNamePK }} {{ $typePK }}, trigger func([]octopus.FixtureType) []octopus.FixtureType) (fx octopus.FixtureType, promiseIsUsed func () bool) { obj := GetInsertReplace{{$PublicStructName}}By{{$fieldNamePK}}({{ $fieldNamePK }}) diff --git a/internal/pkg/generator/tmpl/octopus/main.tmpl b/internal/pkg/generator/tmpl/octopus/main.tmpl index 26205f1..17f0135 100644 --- a/internal/pkg/generator/tmpl/octopus/main.tmpl +++ b/internal/pkg/generator/tmpl/octopus/main.tmpl @@ -22,16 +22,30 @@ import ( ) {{ $pkgName := .ARPkg }} {{ $serializers := .Serializers -}} +{{ $mutators := .Mutators -}} {{ $PublicStructName := .ARPkgTitle -}} {{ $LinkedObject := .LinkedObject }} {{ $flags := .Flags }} {{ $fields := .FieldList }} {{ $procfields := .ProcOutFieldList }} {{ $procInLen := len .ProcInFieldList }} +{{ $mutatorLen := len .Mutators }} + + {{ if ne $mutatorLen 0 -}} + type Mutators struct { + {{- range $i, $mut := $mutators }} + {{$mut.Name}} octopus.MutatorField + field{{ $mut.Name }}Original {{$mut.Type}} + {{- end }} + } + {{ end -}} {{ if $fields }} type {{ $PublicStructName }} struct { octopus.BaseField + {{- if ne $mutatorLen 0 }} + Mutators + {{- end -}} {{- range $ind, $fstruct := .FieldList -}} {{ $rtype := $fstruct.Format -}} {{ $serlen := len $fstruct.Serializer -}} @@ -270,7 +284,7 @@ func Unpack{{ $fstruct.Name }}(r *bytes.Reader) (ret {{ $rtype }}, errRet error) {{ $serializer := index $serializers $sname -}} {{ $serparams := $fstruct.Serializer.Params }} - var svar {{ $underlyingType }} + var svar {{$rtype}} err = {{ $serializer.ImportName }}.{{ $serializer.Unmarshaler }}({{ $serparams }}bvar, &svar) if err != nil { @@ -283,7 +297,7 @@ func Unpack{{ $fstruct.Name }}(r *bytes.Reader) (ret {{ $rtype }}, errRet error) {{ end -}} - return {{ if $isPointer }}&svar{{else}}svar{{end}}, nil + return svar, nil } func pack{{ $fstruct.Name }}(w []byte, {{ $fstruct.Name }} {{ $rtype }}) ([]byte, error) { @@ -323,6 +337,17 @@ func New(ctx context.Context) *{{ $PublicStructName }} { newObj.BaseField.UpdateOps = []octopus.Ops{} newObj.BaseField.ExtraFields = [][]byte{} newObj.BaseField.Objects = map[string][]octopus.ModelStruct{} + {{ end }} + {{- if ne $mutatorLen 0 -}} + {{- range $i, $mut := $mutators }} + newObj.{{$mut.Name}}.PartialFields = map[string]any{} + {{ if ne $mut.Update "" -}} + newObj.{{$mut.Name}}.OpFunc = map[octopus.OpCode]string{octopus.OpUpdate: "{{$mut.Update}}"} + {{ end }} + {{ if ne $mut.Replace "" -}} + newObj.{{$mut.Name}}.OpFunc = map[octopus.OpCode]string{octopus.OpInsert: "{{$mut.Replace}}"} + {{ end }} + {{- end }} {{ end }} return &newObj } @@ -367,6 +392,9 @@ func TupleToStruct(ctx context.Context, tuple octopus.TupleData) (*{{ $PublicStr np.BaseField.Exists = true np.BaseField.UpdateOps = []octopus.Ops{} + {{if gt $mutatorLen 0}} + np.ClearMutatorUpdateOpts() + {{end}} if tuple.Cnt > cntFields { logger := activerecord.Logger() @@ -490,7 +518,7 @@ func Unpack{{ $fstruct.Name }}(r *bytes.Reader) (ret {{ $rtype }}, errRet error) {{ $serializer := index $serializers $sname -}} {{ $serparams := $fstruct.Serializer.Params }} - var svar {{ $underlyingType }} + var svar {{ $rtype }} err = {{ $serializer.ImportName }}.{{ $serializer.Unmarshaler }}({{ $serparams }}bvar, &svar) if err != nil { @@ -503,7 +531,7 @@ func Unpack{{ $fstruct.Name }}(r *bytes.Reader) (ret {{ $rtype }}, errRet error) {{ end -}} - return {{ if $isPointer }}&svar{{else}}svar{{end}}, nil + return svar, nil } func (obj *{{ $PublicStructName }}) Get{{ $fstruct.Name }}() {{ $rtype }} { @@ -537,6 +565,34 @@ func (obj *{{ $PublicStructName }}) Set{{ $fstruct.Name }}({{ $fstruct.Name }} { {{- end }} obj.BaseField.UpdateOps = append(obj.BaseField.UpdateOps, octopus.Ops{Field: {{ $ind }}, Op: octopus.OpSet, Value: data}) + + {{- range $i, $mut := $fstruct.Mutators -}} + {{ $customMutator := index $mutators $mut -}} + {{ $pfLen := len $customMutator.PartialFields }} + {{ if and (eq $pfLen 0) (ne $customMutator.Update "") }} + obj.BaseField.UpdateOps = []octopus.Ops{} + data = octopus.PackLua("{{$customMutator.Update}}", obj.PrimaryString(), {{ $fstruct.Name}}) + obj.{{ $customMutator.Name }}.UpdateOps = append(obj.{{ $customMutator.Name }}.UpdateOps, octopus.Ops{Field: {{ $ind }}, Op: octopus.OpUpdate, Value: data}) + {{ else if ne $pfLen 0 }} + {{ $isPointer := hasPrefix (printf "%s" $rtype) "*" }} + {{ if $isPointer }} + if {{ $fstruct.Name }} != nil { + {{- range $i, $pf := $customMutator.PartialFields }} + if obj.Set{{ $customMutator.Name }}{{ $pf.Name }}({{ $fstruct.Name }}.{{$pf.Name}}); err != nil { + return err + } + {{ end -}} + } + {{ else }} + {{- range $i, $pf := $customMutator.PartialFields }} + if obj.Set{{ $customMutator.Name }}{{ $pf.Name }}({{ $fstruct.Name }}.{{$pf.Name}}); err != nil { + return err + } + {{ end }} + {{ end }} + obj.field{{ $customMutator.Name }}Original = obj.field{{ $fstruct.Name }} + {{ end }} + {{ end }} obj.field{{ $fstruct.Name }} = {{ $fstruct.Name}} {{- if ne $fstruct.ObjectLink "" }} @@ -545,7 +601,75 @@ func (obj *{{ $PublicStructName }}) Set{{ $fstruct.Name }}({{ $fstruct.Name }} { return nil } - {{- range $i, $mut := $fstruct.Mutators -}} + {{ range $i, $mut := $fstruct.Mutators -}} + {{ $customMutator := index $mutators $mut -}} + {{ $pfLen := len $customMutator.PartialFields }} + {{- if $customMutator.Name }} + + {{ if ne $pfLen 0 }} +func (obj *{{ $PublicStructName }}) pack{{ $fstruct.Name }}PartialFields(op octopus.OpCode) error { + pfs := obj.Mutators.{{ $customMutator.Name }}.PartialFields + if len(pfs) == 0 { + return nil + } + + var ( + mutatorArgs []string + err error + ) + + switch op { + {{ if ne $customMutator.Update "" -}} + case octopus.OpUpdate: + mutatorArgs, err = {{ $customMutator.ImportName }}.{{ $PublicStructName }}{{ $customMutator.Name }}Update(obj.field{{ $customMutator.Name }}Original, pfs) + {{ end }} + {{ if ne $customMutator.Replace "" -}} + case octopus.OpInsert: + mutatorArgs, err = {{ $customMutator.ImportName }}.{{ $PublicStructName }}{{ $customMutator.Name }}Replace(obj.field{{ $customMutator.Name }}Original, pfs) + {{ end }} + } + + if err != nil { + return err + } + + data := octopus.PackLua(obj.Mutators.{{ $customMutator.Name }}.OpFunc[op], append([]string{obj.PrimaryString()}, mutatorArgs...)...) + + {{- if eq $fstruct.Format "string" "[]byte" -}} + {{- if gt $fstruct.Size 0 }} + + if len(data) > {{ $fstruct.Size }} { + return fmt.Errorf("max length of field '{{ $PublicStructName }}.{{ $fstruct.Name }}' is '%d' (received '%d')", {{ $fstruct.Size }}, len(data)) + } + {{- else }} + + logger := activerecord.Logger() + + logger.Warn(context.TODO(), "{{ $PublicStructName }}", obj.PrimaryString(), fmt.Sprintf("Size for field '{{ $fstruct.Name }}' not set. Cur field size: %d. Object: '{{ $PublicStructName }}'", len(data))) + {{- end }} + {{- end }} + + obj.{{ $customMutator.Name }}.UpdateOps = []octopus.Ops{} + + obj.{{ $customMutator.Name }}.UpdateOps = append(obj.{{ $customMutator.Name }}.UpdateOps, octopus.Ops{Field: {{ $ind }}, Op: octopus.OpUpdate, Value: data}) + + return nil +} + {{ end }} + + {{ range $i, $f := $customMutator.PartialFields }} + +func (obj *{{ $PublicStructName }}) Set{{ $customMutator.Name }}{{ $f.Name }}({{ $f.Name }} {{ $f.Type }}) error { + obj.Mutators.{{ $customMutator.Name }}.PartialFields["{{ $f.Name }}"] = {{ $f.Name }} + + if err := obj.pack{{ $fstruct.Name }}PartialFields(octopus.OpUpdate); err != nil { + return fmt.Errorf("pack {{ $customMutator.Name }}{{ $f.Name }}: %w", err) + } + + return nil +} + {{ end }} + {{else}} {{ $mutatorparam := mutatorParam $mut $fstruct.Format -}} {{ $mtype := $fstruct.Format }} @@ -618,6 +742,7 @@ func (obj *{{ $PublicStructName }}) {{ $mutatorparam.Name }}{{ $fstruct.Name }}( return nil } + {{- end }} {{- end }} {{- $fl := index $flags $fstruct.Name }} {{- if $fl }} @@ -1130,13 +1255,23 @@ func (obj *{{ $PublicStructName }}) Update(ctx context.Context) error { return obj.Replace(ctx) } + connection, err := octopus.Box(ctx, 0, activerecord.MasterInstanceType, "arcfg", nil) + if err != nil { + metricErrCnt.Inc(ctx, "update_preparebox", 1) + logger.Error(ctx, "{{ $PublicStructName }}", obj.PrimaryString(), fmt.Sprintf("Error get box '%s'", err)) + return err + } + +{{if eq $mutatorLen 0}} if len(obj.BaseField.UpdateOps) == 0 { metricStatCnt.Inc(ctx, "update_empty", 1) logger.Debug(ctx, "", obj.PrimaryString(), "Empty update") return nil } - +{{else}} + if len(obj.BaseField.UpdateOps) > 0 { +{{- end }} pk, err := obj.packPk() if err != nil { metricErrCnt.Inc(ctx, "update_packpk", 1) @@ -1147,13 +1282,6 @@ func (obj *{{ $PublicStructName }}) Update(ctx context.Context) error { log.Printf("Update packed tuple: '%X'\n", w) - connection, err := octopus.Box(ctx, 0, activerecord.MasterInstanceType, "arcfg", nil) - if err != nil { - metricErrCnt.Inc(ctx, "update_preparebox", 1) - logger.Error(ctx, "{{ $PublicStructName }}", obj.PrimaryString(), fmt.Sprintf("Error get box '%s'", err)) - return err - } - respBytes, errCall := connection.Call(ctx, octopus.RequestTypeUpdate, w) if errCall != nil { metricErrCnt.Inc(ctx, "update_box", 1) @@ -1172,6 +1300,36 @@ func (obj *{{ $PublicStructName }}) Update(ctx context.Context) error { return err } +{{if gt $mutatorLen 0}} + } +{{end}} +{{if gt $mutatorLen 0}} +{{ range $ind, $fstruct := .FieldList -}} + {{- range $i, $mut := $fstruct.Mutators -}} + {{ $customMutator := index $mutators $mut -}} + {{ $pfLen := len $customMutator.PartialFields }} + {{ if and (ne $pfLen 0) (ne $customMutator.Update "") $customMutator.Name }} + for _, op := range obj.{{$customMutator.Name}}.UpdateOps { + resp, errCall := connection.Call(ctx, octopus.RequestTypeCall, op.Value) + if errCall != nil { + metricErrCnt.Inc(ctx, "call_proc", 1) + logger.Error(ctx, "{{ $PublicStructName }}", obj.PrimaryString(), "Error call proc in a box", errCall, connection.Info()) + return errCall + } + + _, err := octopus.ProcessResp(resp, 0) + if err != nil { + return fmt.Errorf("error unpack lua response: %w", err) + } + } + + obj.{{$customMutator.Name}}.UpdateOps = []octopus.Ops{} + obj.{{$customMutator.Name}}.PartialFields = map[string]any{} + {{end}} + {{end}} +{{end}} +{{end}} + obj.BaseField.UpdateOps = []octopus.Ops{} logger.Debug(ctx, "{{ $PublicStructName }}", obj.PrimaryString(), "Success update") @@ -1307,6 +1465,9 @@ func (obj *{{ $PublicStructName }}) insertReplace(ctx context.Context, insertMod obj.BaseField.Exists = true obj.BaseField.UpdateOps = []octopus.Ops{} + {{- if gt $mutatorLen 0 }} + obj.ClearMutatorUpdateOpts() + {{- end }} obj.BaseField.Repaired = false logger.Debug(ctx, "{{ $PublicStructName }}", obj.PrimaryString(), "Success insert") @@ -1315,4 +1476,17 @@ func (obj *{{ $PublicStructName }}) insertReplace(ctx context.Context, insertMod return nil } -{{ end }} \ No newline at end of file +{{ end }} +{{if gt $mutatorLen 0}} +func (obj *{{ $PublicStructName }}) ClearMutatorUpdateOpts() { +{{- range $ind, $fstruct := .FieldList -}} + {{- range $i, $mut := $fstruct.Mutators -}} + {{ $customMutator := index $mutators $mut -}} + {{ if and (ne $customMutator.Update "") $customMutator.Name -}} + obj.{{$customMutator.Name}}.UpdateOps = []octopus.Ops{} + obj.{{$customMutator.Name}}.PartialFields = map[string]any{} + {{- end }} + {{end}} +{{- end -}} +} +{{end}} \ No newline at end of file diff --git a/internal/pkg/generator/tmpl/octopus/mock.tmpl b/internal/pkg/generator/tmpl/octopus/mock.tmpl index 93d3b47..f90cfec 100644 --- a/internal/pkg/generator/tmpl/octopus/mock.tmpl +++ b/internal/pkg/generator/tmpl/octopus/mock.tmpl @@ -21,6 +21,8 @@ import ( {{ $procInLen := len .ProcInFieldList }} {{ $serializers := .Serializers -}} {{ $fidx := .FieldMap }} +{{ $mutators := .Mutators -}} +{{ $mutatorLen := len .Mutators }} {{ if $procfields }} func (obj *{{ $PublicStructName }}) MockSelectResponse() ([][]byte, error) { @@ -332,6 +334,33 @@ func (obj *{{ $PublicStructName }}) RepoSelector(ctx context.Context) (any, erro return data, err } +{{ range $ind, $fstruct := $fields -}} +{{- range $i, $mut := $fstruct.Mutators -}} +{{ $customMutator := index $mutators $mut -}} +{{ if and (ne $customMutator.Update "") $customMutator.Name }} +func (obj *{{ $PublicStructName }}) MockMutator{{$customMutator.Name}}Update(ctx context.Context) [][]byte { + log := activerecord.Logger() + ctx = log.SetLoggerValueToContext(ctx, map[string]interface{}{"MockMutator{{$customMutator.Name}}Update": obj.Mutators.{{ $customMutator.Name }}.UpdateOps, "Repo": "{{$PublicStructName}}" }) + + updateMutatorOps := make([][]byte, 0, len(obj.Mutators.{{ $customMutator.Name }}.UpdateOps)) + + for _, update := range obj.Mutators.{{ $customMutator.Name }}.UpdateOps { + switch update.Op { + case octopus.OpUpdate: + updateMutatorOps = append(updateMutatorOps, update.Value) + default: + continue + } + } + + log.Debug(ctx, fmt.Sprintf("Update mutator packed tuple: '%X'\n", updateMutatorOps)) + + return updateMutatorOps +} +{{end}} +{{end}} +{{end}} + func (obj *{{ $PublicStructName }}) MockUpdate(ctx context.Context) []byte { log := activerecord.Logger() ctx = log.SetLoggerValueToContext(ctx, map[string]interface{}{"MockUpdate": obj.BaseField.UpdateOps, "Repo": "{{$PublicStructName}}" }) diff --git a/internal/pkg/parser/field.go b/internal/pkg/parser/field.go index d0ad057..4cf93aa 100644 --- a/internal/pkg/parser/field.go +++ b/internal/pkg/parser/field.go @@ -31,16 +31,7 @@ func ParseFieldsTag(field *ast.Field, newfield *ds.FieldDeclaration, newindex *d newindex.Name = newfield.Name newindex.Unique = true case MutatorsTag: - for _, mut := range strings.Split(kv[1], ",") { - fieldMutatorsChecker := ds.GetFieldMutatorsChecker() - - m, ex := fieldMutatorsChecker[mut] - if !ex { - return &arerror.ErrParseTypeFieldTagDecl{Name: newfield.Name, TagName: kv[0], TagValue: mut, Err: arerror.ErrParseFieldMutatorInvalid} - } - - newfield.Mutators = append(newfield.Mutators, m) - } + newfield.Mutators = strings.Split(kv[1], ",") case SizeTag: if kv[1] != "" { size, err := strconv.ParseInt(kv[1], 10, 64) @@ -70,7 +61,7 @@ func ParseFields(dst *ds.RecordPackage, fields []*ast.Field) error { newfield := ds.FieldDeclaration{ Name: field.Names[0].Name, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Serializer: []string{}, } diff --git a/internal/pkg/parser/field_b_test.go b/internal/pkg/parser/field_b_test.go index 5680abb..2c3c299 100644 --- a/internal/pkg/parser/field_b_test.go +++ b/internal/pkg/parser/field_b_test.go @@ -39,8 +39,8 @@ func TestParseFields(t *testing.T) { Server: ds.ServerDeclaration{}, Namespace: ds.NamespaceDeclaration{}, Fields: []ds.FieldDeclaration{ - {Name: "ID", Format: "int", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Serializer: []string{}}, - {Name: "BarID", Format: "int", PrimaryKey: false, Mutators: []ds.FieldMutator{}, Serializer: []string{}}, + {Name: "ID", Format: "int", PrimaryKey: true, Mutators: []string{}, Serializer: []string{}}, + {Name: "BarID", Format: "int", PrimaryKey: false, Mutators: []string{}, Serializer: []string{}}, }, FieldsMap: map[string]int{"ID": 0, "BarID": 1}, FieldsObjectMap: map[string]ds.FieldObject{}, @@ -59,9 +59,7 @@ func TestParseFields(t *testing.T) { }, IndexMap: map[string]int{"ID": 0}, SelectorMap: map[string]int{"SelectByID": 0}, - Imports: []ds.ImportDeclaration{}, - ImportMap: map[string]int{}, - ImportPkgMap: map[string]int{}, + ImportPackage: ds.NewImportPackage(), Backends: []string{}, SerializerMap: map[string]ds.SerializerDeclaration{}, TriggerMap: map[string]ds.TriggerDeclaration{}, diff --git a/internal/pkg/parser/fieldobject_w_test.go b/internal/pkg/parser/fieldobject_w_test.go index 620373c..e73bffd 100644 --- a/internal/pkg/parser/fieldobject_w_test.go +++ b/internal/pkg/parser/fieldobject_w_test.go @@ -16,7 +16,7 @@ func TestParseFieldsObject(t *testing.T) { Name: "BarID", Format: octopus.Int, PrimaryKey: false, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", @@ -32,7 +32,7 @@ func TestParseFieldsObject(t *testing.T) { Name: "BarID", Format: octopus.Int, PrimaryKey: false, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "Bar", diff --git a/internal/pkg/parser/import.go b/internal/pkg/parser/import.go index 9dc6d25..8eb6a78 100644 --- a/internal/pkg/parser/import.go +++ b/internal/pkg/parser/import.go @@ -8,7 +8,7 @@ import ( "github.com/mailru/activerecord/internal/pkg/ds" ) -func ParseImport(dst *ds.RecordPackage, importSpec *ast.ImportSpec) error { +func ParseImport(dst *ds.ImportPackage, importSpec *ast.ImportSpec) error { var pkg string path := strings.Trim(importSpec.Path.Value, `"`) diff --git a/internal/pkg/parser/import_w_test.go b/internal/pkg/parser/import_w_test.go index 5a5a075..ae3d3fc 100644 --- a/internal/pkg/parser/import_w_test.go +++ b/internal/pkg/parser/import_w_test.go @@ -50,23 +50,28 @@ func TestParseImport(t *testing.T) { Indexes: []ds.IndexDeclaration{}, IndexMap: map[string]int{}, SelectorMap: map[string]int{}, - Imports: []ds.ImportDeclaration{ - { - Path: "github.com/mailru/activerecord-cookbook.git/example/model/dictionary", + ImportPackage: ds.ImportPackage{ + Imports: []ds.ImportDeclaration{ + { + Path: "github.com/mailru/activerecord-cookbook.git/example/model/dictionary", + }, }, + ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/dictionary": 0}, + ImportPkgMap: map[string]int{"dictionary": 0}, }, - ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/dictionary": 0}, - ImportPkgMap: map[string]int{"dictionary": 0}, - SerializerMap: map[string]ds.SerializerDeclaration{}, - TriggerMap: map[string]ds.TriggerDeclaration{}, - FlagMap: map[string]ds.FlagDeclaration{}, + SerializerMap: map[string]ds.SerializerDeclaration{}, + TriggerMap: map[string]ds.TriggerDeclaration{}, + FlagMap: map[string]ds.FlagDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{}, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{}, }, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := ParseImport(tt.args.dst, tt.args.importSpec); (err != nil) != tt.wantErr { + if err := ParseImport(&tt.args.dst.ImportPackage, tt.args.importSpec); (err != nil) != tt.wantErr { t.Errorf("ParseImport() error = %v, wantErr %v", err, tt.wantErr) return } diff --git a/internal/pkg/parser/mutator.go b/internal/pkg/parser/mutator.go new file mode 100644 index 0000000..8b77344 --- /dev/null +++ b/internal/pkg/parser/mutator.go @@ -0,0 +1,65 @@ +package parser + +import ( + "go/ast" + "strings" + + "github.com/mailru/activerecord/internal/pkg/arerror" + "github.com/mailru/activerecord/internal/pkg/ds" +) + +func ParseMutators(dst *ds.RecordPackage, fields []*ast.Field) error { + for _, field := range fields { + if field.Names == nil || len(field.Names) != 1 { + return &arerror.ErrParseMutatorDecl{Err: arerror.ErrNameDeclaration} + } + + mutatorDeclaration := ds.MutatorDeclaration{ + Name: field.Names[0].Name, + ImportName: "mutator" + field.Names[0].Name, + } + + tagParam, err := splitTag(field, NoCheckFlag, map[TagNameType]ParamValueRule{}) + if err != nil { + return &arerror.ErrParseMutatorDecl{Name: mutatorDeclaration.Name, Err: err} + } + + for _, kv := range tagParam { + switch kv[0] { + case "pkg": + mutatorDeclaration.Pkg = kv[1] + case "update": + mutatorDeclaration.Update = kv[1] + case "replace": + mutatorDeclaration.Replace = kv[1] + default: + return &arerror.ErrParseMutatorTagDecl{Name: mutatorDeclaration.Name, TagName: kv[0], TagValue: kv[1], Err: arerror.ErrParseTagUnknown} + } + } + + if mutatorDeclaration.Pkg != "" { + imp, e := dst.FindOrAddImport(mutatorDeclaration.Pkg, mutatorDeclaration.ImportName) + if e != nil { + return &arerror.ErrParseMutatorDecl{Name: mutatorDeclaration.Name, Err: e} + } + + mutatorDeclaration.ImportName = imp.ImportName + } + + mutatorDeclaration.Type, err = ParseFieldType(dst, mutatorDeclaration.Name, "", field.Type) + if err != nil { + return &arerror.ErrParseMutatorDecl{Name: mutatorDeclaration.Name, Err: err} + } + + // Ассоциируем указатель на тип с типом + structType := strings.Replace(mutatorDeclaration.Type, "*", "", 1) + + mutatorDeclaration.PartialFields = dst.ImportStructFieldsMap[structType] + + if err = dst.AddMutator(mutatorDeclaration); err != nil { + return err + } + } + + return nil +} diff --git a/internal/pkg/parser/mutator_b_test.go b/internal/pkg/parser/mutator_b_test.go new file mode 100644 index 0000000..135be17 --- /dev/null +++ b/internal/pkg/parser/mutator_b_test.go @@ -0,0 +1,198 @@ +package parser_test + +import ( + "fmt" + "go/ast" + "testing" + + "github.com/stretchr/testify/require" + "gotest.tools/assert" + "gotest.tools/assert/cmp" + + "github.com/mailru/activerecord/internal/pkg/ds" + "github.com/mailru/activerecord/internal/pkg/parser" +) + +func NewRecordPackage(t *testing.T) (*ds.RecordPackage, error) { + dst := ds.NewRecordPackage() + dst.Namespace.ModuleName = "github.com/mailru/activerecord/internal/pkg/parser" + + if _, err := dst.AddImport("github.com/mailru/activerecord/internal/pkg/parser/testdata/foo"); err != nil { + return nil, fmt.Errorf("can't create test package: %w", err) + } + + return dst, nil +} + +func TestParseMutator(t *testing.T) { + type args struct { + fields []*ast.Field + } + tests := []struct { + name string + args args + want *ds.RecordPackage + wantErr bool + }{ + { + name: "parse mutator decl", + args: args{ + fields: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "FooMutatorField"}}, + Tag: &ast.BasicLit{ + Value: "`ar:\"update:updateFunc,param1,param2;replace:replaceFunc;pkg:github.com/mailru/activerecord/internal/pkg/conv\"`", + }, + Type: &ast.StarExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: "foo"}, + Sel: &ast.Ident{Name: "Foo"}, + }, + }, + }, + { + Names: []*ast.Ident{{Name: "SimpleTypeMutatorField"}}, + Tag: &ast.BasicLit{ + Value: "`ar:\"update:updateSimpleTypeFunc\"`", + }, + Type: &ast.Ident{Name: "int"}, + }, + }, + }, + want: &ds.RecordPackage{ + Server: ds.ServerDeclaration{}, + Namespace: ds.NamespaceDeclaration{ + ModuleName: "github.com/mailru/activerecord/internal/pkg/parser", + }, + Fields: []ds.FieldDeclaration{}, + FieldsMap: map[string]int{}, + FieldsObjectMap: map[string]ds.FieldObject{}, + Indexes: []ds.IndexDeclaration{}, + IndexMap: map[string]int{}, + SelectorMap: map[string]int{}, + Backends: []string{}, + SerializerMap: map[string]ds.SerializerDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{ + "FooMutatorField": { + Name: "FooMutatorField", + Pkg: "github.com/mailru/activerecord/internal/pkg/conv", + Type: "*foo.Foo", + ImportName: "mutatorFooMutatorField", + Update: "updateFunc,param1,param2", + Replace: "replaceFunc", + PartialFields: []ds.PartialFieldDeclaration{ + {Name: "Key", Type: "string"}, + {Name: "Bar", Type: "ds.AppInfo"}, + {Name: "BeerData", Type: "[]foo.Beer"}, + {Name: "MapData", Type: "map[string]any"}, + }, + }, + "SimpleTypeMutatorField": { + Name: "SimpleTypeMutatorField", + Type: "int", + ImportName: "mutatorSimpleTypeMutatorField", + Update: "updateSimpleTypeFunc", + }, + }, + ImportPackage: ds.ImportPackage{ + Imports: []ds.ImportDeclaration{ + {Path: "github.com/mailru/activerecord/internal/pkg/parser/testdata/foo"}, + {Path: "github.com/mailru/activerecord/internal/pkg/conv", ImportName: "mutatorFooMutatorField"}, + {Path: "github.com/mailru/activerecord/internal/pkg/parser/testdata/ds"}, + }, + ImportMap: map[string]int{ + "github.com/mailru/activerecord/internal/pkg/conv": 1, + "github.com/mailru/activerecord/internal/pkg/parser/testdata/foo": 0, + "github.com/mailru/activerecord/internal/pkg/parser/testdata/ds": 2, + }, + ImportPkgMap: map[string]int{ + "mutatorFooMutatorField": 1, + "ds": 2, + "foo": 0, + }, + }, + TriggerMap: map[string]ds.TriggerDeclaration{}, + FlagMap: map[string]ds.FlagDeclaration{}, + ProcOutFields: map[int]ds.ProcFieldDeclaration{}, + ProcFieldsMap: map[string]int{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{ + "ds": { + Types: map[string]struct{}{"AppInfo": {}}, + Import: struct { + Imports []ds.ImportDeclaration + ImportMap map[string]int + ImportPkgMap map[string]int + }{Imports: []ds.ImportDeclaration{}, ImportMap: map[string]int{}, ImportPkgMap: map[string]int{}}, + }, + "foo": { + Types: map[string]struct{}{"Beer": {}, "Foo": {}}, + Import: struct { + Imports []ds.ImportDeclaration + ImportMap map[string]int + ImportPkgMap map[string]int + }{ + Imports: []ds.ImportDeclaration{ + {Path: "github.com/mailru/activerecord/internal/pkg/parser/testdata/ds"}, + }, + ImportMap: map[string]int{ + "github.com/mailru/activerecord/internal/pkg/parser/testdata/ds": 0, + }, + ImportPkgMap: map[string]int{ + "ds": 0, + }, + }, + }, + }, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{ + "ds.AppInfo": { + {Name: "appName", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "buildTime", Type: "string"}, + {Name: "buildOS", Type: "string"}, + {Name: "buildCommit", Type: "string"}, + {Name: "generateTime", Type: "string"}, + }, + "foo.Foo": { + {Name: "Key", Type: "string"}, + {Name: "Bar", Type: "ds.AppInfo"}, + {Name: "BeerData", Type: "[]foo.Beer"}, + {Name: "MapData", Type: "map[string]any"}, + }, + }, + }, + wantErr: false, + }, + { + name: "not imported package for mutator type", + args: args{ + fields: []*ast.Field{ + { + Names: []*ast.Ident{{Name: "Foo"}}, + Tag: &ast.BasicLit{Value: "`ar:\"pkg:github.com/mailru/activerecord/notexistsfolder\"`"}, + Type: &ast.StarExpr{ + X: &ast.SelectorExpr{ + X: &ast.Ident{Name: "notimportedpackage"}, + Sel: &ast.Ident{Name: "Bar"}, + }, + }, + }, + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst, err := NewRecordPackage(t) + require.NoError(t, err) + + if err := parser.ParseMutators(dst, tt.args.fields); (err != nil) != tt.wantErr { + t.Errorf("ParseMutators() error = %v, wantErr %v", err, tt.wantErr) + } + + if !tt.wantErr { + assert.Check(t, cmp.DeepEqual(dst, tt.want), "Invalid response package, test `%s`", tt.name) + } + }) + } +} diff --git a/internal/pkg/parser/parser.go b/internal/pkg/parser/parser.go index accfdfa..9e40eb5 100644 --- a/internal/pkg/parser/parser.go +++ b/internal/pkg/parser/parser.go @@ -23,6 +23,7 @@ const ( Serializers StructNameType = "Serializers" Triggers StructNameType = "Triggers" Flags StructNameType = "Flags" + Mutators StructNameType = "Mutators" ) type TagNameType string @@ -106,6 +107,8 @@ func parseStructNameType(dst *ds.RecordPackage, nodeName string, curr *ast.Struc return ParseFlags(dst, curr.Fields.List) case ProcFields: return ParseProcFields(dst, curr.Fields.List) + case Mutators: + return ParseMutators(dst, curr.Fields.List) default: return arerror.ErrUnknown } @@ -172,7 +175,7 @@ func parseTokenImport(dst *ds.RecordPackage, genD *ast.GenDecl) error { return &arerror.ErrParseGenDecl{Name: genD.Tok.String(), Err: arerror.ErrParseCastImportType} } - if impErr := ParseImport(dst, currImport); impErr != nil { + if impErr := ParseImport(&dst.ImportPackage, currImport); impErr != nil { return &arerror.ErrParseGenDecl{Name: genD.Tok.String(), Err: impErr} } } diff --git a/internal/pkg/parser/parser_b_test.go b/internal/pkg/parser/parser_b_test.go index 7b76a11..d31154d 100644 --- a/internal/pkg/parser/parser_b_test.go +++ b/internal/pkg/parser/parser_b_test.go @@ -79,8 +79,8 @@ type TriggersFoo struct { Namespace: ds.NamespaceDeclaration{ObjectName: "2", PublicName: "Foo", PackageName: "foo"}, Server: ds.ServerDeclaration{Timeout: 500, Host: "127.0.0.1", Port: "11111"}, Fields: []ds.FieldDeclaration{ - {Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}}, - {Name: "Field2", Format: "string", PrimaryKey: true, Mutators: []ds.FieldMutator{}, Size: 5, Serializer: []string{}}, + {Name: "Field1", Format: "int", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}}, + {Name: "Field2", Format: "string", PrimaryKey: true, Mutators: []string{}, Size: 5, Serializer: []string{}}, }, FieldsMap: map[string]int{"Field1": 0, "Field2": 1}, FieldsObjectMap: map[string]ds.FieldObject{}, @@ -114,14 +114,16 @@ type TriggersFoo struct { SelectorMap: map[string]int{"SelectByField1": 1, "SelectByField1Field2": 0}, Backends: []string{"octopus"}, SerializerMap: map[string]ds.SerializerDeclaration{}, - Imports: []ds.ImportDeclaration{ - { - Path: "github.com/mailru/activerecord-cookbook.git/example/model/repository/repair", - ImportName: "triggerRepairTuple", + ImportPackage: ds.ImportPackage{ + Imports: []ds.ImportDeclaration{ + { + Path: "github.com/mailru/activerecord-cookbook.git/example/model/repository/repair", + ImportName: "triggerRepairTuple", + }, }, + ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/repository/repair": 0}, + ImportPkgMap: map[string]int{"triggerRepairTuple": 0}, }, - ImportMap: map[string]int{"github.com/mailru/activerecord-cookbook.git/example/model/repository/repair": 0}, - ImportPkgMap: map[string]int{"triggerRepairTuple": 0}, TriggerMap: map[string]ds.TriggerDeclaration{ "RepairTuple": { Name: "RepairTuple", @@ -131,9 +133,12 @@ type TriggersFoo struct { Params: map[string]bool{"Defaults": true}, }, }, - FlagMap: map[string]ds.FlagDeclaration{}, - ProcFieldsMap: map[string]int{}, - ProcOutFields: map[int]ds.ProcFieldDeclaration{}, + FlagMap: map[string]ds.FlagDeclaration{}, + ProcFieldsMap: map[string]int{}, + ProcOutFields: map[int]ds.ProcFieldDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{}, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{}, }, }, } @@ -213,18 +218,19 @@ type ProcFieldsFoo struct { 1: {Name: "InOutParams2", Format: "string", Type: 3, Serializer: []string{}, OrderIndex: 1}, 0: {Name: "Output", Format: "string", Type: 2, Serializer: []string{}, OrderIndex: 0}, }, - ProcFieldsMap: map[string]int{"InOutParams2": 1, "InParams1": 0, "Output": 2}, - FieldsObjectMap: map[string]ds.FieldObject{}, - Indexes: []ds.IndexDeclaration{}, - IndexMap: map[string]int{}, - SelectorMap: map[string]int{}, - Backends: []string{"octopus"}, - SerializerMap: map[string]ds.SerializerDeclaration{}, - Imports: []ds.ImportDeclaration{}, - ImportMap: map[string]int{}, - ImportPkgMap: map[string]int{}, - TriggerMap: map[string]ds.TriggerDeclaration{}, - FlagMap: map[string]ds.FlagDeclaration{}, + ProcFieldsMap: map[string]int{"InOutParams2": 1, "InParams1": 0, "Output": 2}, + FieldsObjectMap: map[string]ds.FieldObject{}, + Indexes: []ds.IndexDeclaration{}, + IndexMap: map[string]int{}, + SelectorMap: map[string]int{}, + Backends: []string{"octopus"}, + SerializerMap: map[string]ds.SerializerDeclaration{}, + ImportPackage: ds.NewImportPackage(), + TriggerMap: map[string]ds.TriggerDeclaration{}, + FlagMap: map[string]ds.FlagDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{}, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{}, }, }, } diff --git a/internal/pkg/parser/parser_w_test.go b/internal/pkg/parser/parser_w_test.go index edf7361..4f5179d 100644 --- a/internal/pkg/parser/parser_w_test.go +++ b/internal/pkg/parser/parser_w_test.go @@ -46,21 +46,22 @@ func Test_parseDoc(t *testing.T) { PublicName: "", PackageName: "", }, - Backends: []string{"octopus"}, - Fields: []ds.FieldDeclaration{}, - FieldsMap: map[string]int{}, - ProcFieldsMap: map[string]int{}, - ProcOutFields: map[int]ds.ProcFieldDeclaration{}, - FieldsObjectMap: map[string]ds.FieldObject{}, - Indexes: []ds.IndexDeclaration{}, - IndexMap: map[string]int{}, - SelectorMap: map[string]int{}, - Imports: []ds.ImportDeclaration{}, - ImportMap: map[string]int{}, - ImportPkgMap: map[string]int{}, - SerializerMap: map[string]ds.SerializerDeclaration{}, - TriggerMap: map[string]ds.TriggerDeclaration{}, - FlagMap: map[string]ds.FlagDeclaration{}, + Backends: []string{"octopus"}, + Fields: []ds.FieldDeclaration{}, + FieldsMap: map[string]int{}, + ProcFieldsMap: map[string]int{}, + ProcOutFields: map[int]ds.ProcFieldDeclaration{}, + FieldsObjectMap: map[string]ds.FieldObject{}, + Indexes: []ds.IndexDeclaration{}, + IndexMap: map[string]int{}, + SelectorMap: map[string]int{}, + ImportPackage: ds.NewImportPackage(), + SerializerMap: map[string]ds.SerializerDeclaration{}, + TriggerMap: map[string]ds.TriggerDeclaration{}, + FlagMap: map[string]ds.FlagDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{}, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{}, }, }, { @@ -139,7 +140,7 @@ func Test_parseGen(t *testing.T) { Name: "ID", Format: octopus.Int, PrimaryKey: true, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "", @@ -149,7 +150,7 @@ func Test_parseGen(t *testing.T) { Name: "BarID", Format: octopus.Int, PrimaryKey: false, - Mutators: []ds.FieldMutator{}, + Mutators: []string{}, Size: 0, Serializer: []string{}, ObjectLink: "Bar", @@ -365,23 +366,24 @@ func Test_parseAst(t *testing.T) { }, wantErr: false, want: &ds.RecordPackage{ - Server: ds.ServerDeclaration{Timeout: 500, Host: "127.0.0.1", Port: "11011"}, - Namespace: ds.NamespaceDeclaration{ObjectName: "5", PublicName: "Baz", PackageName: "baz"}, - ProcFieldsMap: map[string]int{}, - ProcOutFields: map[int]ds.ProcFieldDeclaration{}, - Fields: []ds.FieldDeclaration{}, - FieldsMap: map[string]int{}, - FieldsObjectMap: map[string]ds.FieldObject{}, - Indexes: []ds.IndexDeclaration{}, - IndexMap: map[string]int{}, - SelectorMap: map[string]int{}, - Backends: []string{"octopus"}, - SerializerMap: map[string]ds.SerializerDeclaration{}, - Imports: []ds.ImportDeclaration{}, - ImportMap: map[string]int{}, - ImportPkgMap: map[string]int{}, - TriggerMap: map[string]ds.TriggerDeclaration{}, - FlagMap: map[string]ds.FlagDeclaration{}, + Server: ds.ServerDeclaration{Timeout: 500, Host: "127.0.0.1", Port: "11011"}, + Namespace: ds.NamespaceDeclaration{ObjectName: "5", PublicName: "Baz", PackageName: "baz"}, + ProcFieldsMap: map[string]int{}, + ProcOutFields: map[int]ds.ProcFieldDeclaration{}, + Fields: []ds.FieldDeclaration{}, + FieldsMap: map[string]int{}, + FieldsObjectMap: map[string]ds.FieldObject{}, + Indexes: []ds.IndexDeclaration{}, + IndexMap: map[string]int{}, + SelectorMap: map[string]int{}, + Backends: []string{"octopus"}, + SerializerMap: map[string]ds.SerializerDeclaration{}, + ImportPackage: ds.NewImportPackage(), + TriggerMap: map[string]ds.TriggerDeclaration{}, + FlagMap: map[string]ds.FlagDeclaration{}, + MutatorMap: map[string]ds.MutatorDeclaration{}, + ImportStructFieldsMap: map[string][]ds.PartialFieldDeclaration{}, + LinkedStructsMap: map[string]ds.LinkedPackageDeclaration{}, }, }, } diff --git a/internal/pkg/parser/partialstruct.go b/internal/pkg/parser/partialstruct.go new file mode 100644 index 0000000..67e55ca --- /dev/null +++ b/internal/pkg/parser/partialstruct.go @@ -0,0 +1,211 @@ +package parser + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "path/filepath" + + "github.com/mailru/activerecord/internal/pkg/arerror" + "github.com/mailru/activerecord/internal/pkg/ds" +) + +func parseStructFields(dst *ds.RecordPackage, gen *ast.GenDecl, name, pkgName string) ([]ds.PartialFieldDeclaration, error) { + for _, spec := range gen.Specs { + currType, ok := spec.(*ast.TypeSpec) + if !ok { + continue + } + + switch curr := currType.Type.(type) { + case *ast.StructType: + if currType.Name.Name != name { + continue + } + + if curr.Fields == nil { + return nil, &arerror.ErrParseTypeStructDecl{Name: currType.Name.Name, Err: arerror.ErrParseStructureEmpty} + } + + partialFields := make([]ds.PartialFieldDeclaration, 0, len(curr.Fields.List)) + + for _, field := range curr.Fields.List { + if len(field.Names) == 0 { + continue + } + + t, err := ParseFieldType(dst, name, pkgName, field.Type) + if err != nil { + return nil, &arerror.ErrParseTypeFieldStructDecl{Name: name, FieldType: field.Names[0].Name, Err: err} + } + + field := ds.PartialFieldDeclaration{ + Name: field.Names[0].Name, + Type: t, + } + + partialFields = append(partialFields, field) + } + + return partialFields, nil + } + } + + return nil, nil +} + +func ParsePartialStructFields(dst *ds.RecordPackage, name, pkgName, path string) ([]ds.PartialFieldDeclaration, error) { + relPath, err := filepath.Rel(dst.Namespace.ModuleName, path) + if err != nil { + return nil, fmt.Errorf("can't extract rel path of `%s` for module `%s`: %w", path, dst.Namespace.ModuleName, err) + } + + pkgs, err := parser.ParseDir(token.NewFileSet(), relPath, nil, parser.DeclarationErrors) + if err != nil { + return nil, fmt.Errorf("error parse file `%s`: %w", path, err) + } + + files := make(map[string]*ast.File) + for _, f := range pkgs[pkgName].Files { + for name, object := range f.Scope.Objects { + if object.Kind == ast.Typ { + files[name] = f + } + } + } + + file, ok := files[name] + if !ok { + return nil, fmt.Errorf("can't find struct `%s` in package `%s`: %w", name, pkgName, err) + } + + importPkg := ds.NewImportPackage() + for _, spec := range file.Imports { + if err = ParseImport(&importPkg, spec); err != nil { + return nil, fmt.Errorf("can't parse import from package file `%s`: %w", file.Name, err) + } + } + + pkgDecl := ds.LinkedPackageDeclaration{ + Types: make(map[string]struct{}), + Import: importPkg, + } + + for t := range files { + pkgDecl.Types[t] = struct{}{} + } + + dst.LinkedStructsMap[pkgName] = pkgDecl + + for _, decl := range file.Decls { + switch gen := decl.(type) { + case *ast.GenDecl: + if gen.Tok != token.TYPE { + continue + } + partialFields, genErr := parseStructFields(dst, gen, name, pkgName) + if genErr != nil { + return nil, &arerror.ErrParseGenDecl{Name: pkgName, Err: fmt.Errorf("error parse struct `%s` in package `%s`: %w", name, pkgName, genErr)} + } + + if len(partialFields) == 0 { + continue + } + + return partialFields, nil + } + } + + return nil, nil +} + +//nolint:gocognit +func ParseFieldType(dst *ds.RecordPackage, name, pName string, t interface{}) (string, error) { + switch tv := t.(type) { + case *ast.Ident: + v := tv.String() + + if ls, ok := dst.LinkedStructsMap[pName]; ok { + if _, ok := ls.Types[v]; ok { + return pName + "." + v, nil + } + + // если импорта нет, то это простой тип + imp, err := ls.Import.FindImportByPkg(v) + if err != nil { + return v, nil + } + + _, _ = dst.FindOrAddImport(imp.Path, imp.ImportName) + } + + return v, nil + case *ast.ArrayType: + var err error + + len := "" + if tv.Len != nil { + len, err = ParseFieldType(dst, name, "", tv.Len) + if err != nil { + return "", err + } + } + + t, err := ParseFieldType(dst, name, pName, tv.Elt) + if err != nil { + return "", err + } + + return "[" + len + "]" + t, nil + case *ast.InterfaceType: + return "interface{}", nil + case *ast.StarExpr: + t, err := ParseFieldType(dst, name, pName, tv.X) + if err != nil { + return "", err + } + + return "*" + t, nil + case *ast.MapType: + k, err := ParseFieldType(dst, name, pName, tv.Key) + if err != nil { + return "", nil + } + + v, err := ParseFieldType(dst, name, pName, tv.Value) + if err != nil { + return "", nil + } + + return "map[" + k + "]" + v, nil + case *ast.SelectorExpr: + pkgName, err := ParseFieldType(dst, name, pName, tv.X) + if err != nil { + return "", err + } + + imp, err := dst.FindImportByPkg(pkgName) + if err != nil { + return "", &arerror.ErrParseTypeStructDecl{Name: name, Err: err} + } + + reqImportName := imp.ImportName + if reqImportName == "" { + reqImportName = pkgName + } + + if _, ok := dst.ImportStructFieldsMap[reqImportName+"."+tv.Sel.Name]; !ok { + fieldDeclarations, err := ParsePartialStructFields(dst, tv.Sel.Name, pkgName, imp.Path) + if err != nil { + return "", &arerror.ErrParseTypeStructDecl{Name: name, Err: err} + } + + dst.ImportStructFieldsMap[reqImportName+"."+tv.Sel.Name] = fieldDeclarations + } + + return reqImportName + "." + tv.Sel.Name, nil + default: + return "", &arerror.ErrParseTypeStructDecl{Name: name, Err: arerror.ErrUnknown} + } +} diff --git a/internal/pkg/parser/partialstruct_test.go b/internal/pkg/parser/partialstruct_test.go new file mode 100644 index 0000000..d5a0542 --- /dev/null +++ b/internal/pkg/parser/partialstruct_test.go @@ -0,0 +1,61 @@ +package parser + +import ( + "reflect" + "testing" + + "github.com/mailru/activerecord/internal/pkg/ds" +) + +type Foo struct { + Bar int +} + +func TestParsePartialStructFields(t *testing.T) { + type args struct { + dst *ds.RecordPackage + name string + pkgName string + path string + } + + dst := ds.NewRecordPackage() + + if _, err := dst.AddImport("github.com/mailru/activerecord/internal/pkg/parser"); err != nil { + t.Errorf("can't prepare test data: %s", err) + return + } + + tests := []struct { + name string + args args + want []ds.PartialFieldDeclaration + wantErr bool + }{ + { + name: "parse fields of parser.Foo struct", + args: args{ + dst: dst, + name: "Foo", + pkgName: "parser", + path: ".", + }, + want: []ds.PartialFieldDeclaration{ + {Name: "Bar", Type: "int"}, + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParsePartialStructFields(tt.args.dst, tt.args.name, tt.args.pkgName, tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("ParsePartialStructFields() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParsePartialStructFields() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/pkg/parser/testdata/ds/info.go b/internal/pkg/parser/testdata/ds/info.go new file mode 100644 index 0000000..8951e87 --- /dev/null +++ b/internal/pkg/parser/testdata/ds/info.go @@ -0,0 +1,10 @@ +package ds + +type AppInfo struct { + appName string + version string + buildTime string + buildOS string + buildCommit string + generateTime string +} diff --git a/internal/pkg/parser/testdata/foo/foo.go b/internal/pkg/parser/testdata/foo/foo.go new file mode 100644 index 0000000..f706749 --- /dev/null +++ b/internal/pkg/parser/testdata/foo/foo.go @@ -0,0 +1,12 @@ +package foo + +import "github.com/mailru/activerecord/internal/pkg/parser/testdata/ds" + +type Beer struct{} + +type Foo struct { + Key string + Bar ds.AppInfo + BeerData []Beer + MapData map[string]any +} diff --git a/internal/pkg/parser/utils.go b/internal/pkg/parser/utils.go index 1ced297..dfaa226 100644 --- a/internal/pkg/parser/utils.go +++ b/internal/pkg/parser/utils.go @@ -21,6 +21,7 @@ var availableNodeName = []StructNameType{ Serializers, Triggers, Flags, + Mutators, } func getNodeName(node string) (name string, publicName string, packageName string, err error) { diff --git a/pkg/octopus/types.go b/pkg/octopus/types.go index 5be6bb8..64767ab 100644 --- a/pkg/octopus/types.go +++ b/pkg/octopus/types.go @@ -43,6 +43,12 @@ type BaseField struct { Repaired bool } +type MutatorField struct { + OpFunc map[OpCode]string + PartialFields map[string]any + UpdateOps []Ops +} + type RequetsTypeType uint8 const ( @@ -136,6 +142,7 @@ const ( OpSplice OpDelete OpInsert + OpUpdate ) const ( diff --git a/pkg/serializer/mapstructure.go b/pkg/serializer/mapstructure.go index e870095..9bef395 100644 --- a/pkg/serializer/mapstructure.go +++ b/pkg/serializer/mapstructure.go @@ -10,7 +10,7 @@ import ( ) func MapstructureUnmarshal(data string, v any) error { - var m map[string]interface{} + m := make(map[string]interface{}) err := json.Unmarshal([]byte(data), &m) if err != nil { @@ -72,7 +72,7 @@ func MapstructureWeakUnmarshal(data string, v any) error { } func MapstructureMarshal(v any) (string, error) { - var m map[string]interface{} + m := make(map[string]interface{}) err := mapstructure.Decode(v, &m) if err != nil {