diff --git a/perxis-proto b/perxis-proto index 95aca241a0cb17f5e1e9f584b1993bf7b933588e..0627c9f829178bc6de2623a0b6d42964c44de496 160000 --- a/perxis-proto +++ b/perxis-proto @@ -1 +1 @@ -Subproject commit 95aca241a0cb17f5e1e9f584b1993bf7b933588e +Subproject commit 0627c9f829178bc6de2623a0b6d42964c44de496 diff --git a/pkg/schema/executor/executor.go b/pkg/schema/executor/executor.go deleted file mode 100644 index 73db9730709ec22ba48c11c01f590470cfd1e235..0000000000000000000000000000000000000000 --- a/pkg/schema/executor/executor.go +++ /dev/null @@ -1,77 +0,0 @@ -package executor - -import ( - "context" - "strings" - - "git.perx.ru/perxis/perxis-go/pkg/schema/field" -) - -type Executor interface { - // Execute выполняет подстановку значений. - Execute(ctx context.Context, input string) (string, error) - - // IsCanceled проверяет, было ли выполнение отменено. - IsCanceled() bool -} - -// Executable определяет опции полей, использующие Executor для подстановки значений. -type Executable interface { - Execute(ctx context.Context, exec Executor, field *field.Field) error -} - -// ExecuteAll последовательно выполняет Execute для каждого элемента inputs. -// Возвращает собранные непустые результаты (игнорируя пустые строки) или первую возникшую ошибку. -func ExecuteAll(ctx context.Context, exec Executor, inputs []string) ([]string, error) { - var result []string - for _, input := range inputs { - output, err := exec.Execute(ctx, input) - if err != nil { - return nil, err - } - if strings.TrimSpace(output) == "" { - continue - } - result = append(result, output) - } - return result, nil -} - -func Execute(ctx context.Context, w field.Walker, exec Executor) error { - _, _, err := w.Walk(ctx, - nil, - func(ctx context.Context, fld *field.Field, _ any) (field.WalkFuncResult, error) { - var ( - result field.WalkFuncResult - err error - ) - enabled, _ := fld.IsEnabled(ctx) - if !enabled { - result.Stop = true - return result, err - } - - for _, op := range fld.Options { - executable, ok := op.(Executable) - if !ok { - continue - } - err = executable.Execute(ctx, exec, fld) - if err != nil { - return result, err - } - } - - return result, err - }, - field.WalkSchema(), - ) - - return err -} - -//nolint:gochecknoinits // init нужен для регистрации опций -func init() { - field.RegisterOption(CollectionFilter{}) - field.RegisterOption(ItemFilter{}) -} diff --git a/pkg/schema/executor/template_executor.go b/pkg/schema/executor/template_executor.go deleted file mode 100644 index 98878e64987489105530d2edf5b5db8c962f369d..0000000000000000000000000000000000000000 --- a/pkg/schema/executor/template_executor.go +++ /dev/null @@ -1,89 +0,0 @@ -package executor - -import ( - "bytes" - "context" - "sync" - "text/template" - - "git.perx.ru/perxis/perxis-go/pkg/errors" -) - -type TemplateExecutor struct { - data map[string]any - funcMap template.FuncMap -} - -func NewTemplateExecutor(data map[string]any) *TemplateExecutor { - te := &TemplateExecutor{ - data: data, - } - te.init() - return te -} - -func (exec *TemplateExecutor) init() { - if exec.data == nil { - exec.data = make(map[string]any) - } - delete(exec.data, "Error") - - exec.funcMap = template.FuncMap{ - "error": exec.errorFunc, - } -} - -func (exec *TemplateExecutor) Execute(_ context.Context, input string) (string, error) { - templ, err := template.New("template_executor").Funcs(exec.funcMap).Parse(input) - if err != nil { - return "", err - } - - buf := getBuffer() - defer releaseBuffer(buf) - - err = templ.Execute(buf, exec.data) - if err != nil { - return "", err - } - - output := buf.String() - if output == "<no value>" { - return "", nil - } - - return output, nil -} - -// IsCanceled возвращает true, если в процессе выполнения шаблона была вызвана -// шаблонная функция error. -func (exec *TemplateExecutor) IsCanceled() bool { - _, ok := exec.data["Error"] - return ok -} - -func (exec *TemplateExecutor) errorFunc(text string) (string, error) { - exec.data["Error"] = text - return "", errors.New(text) -} - -//nolint:gochecknoglobals // Доступ к пулу необходим на глобальном уровне. -var bufferPool = &sync.Pool{ - New: func() any { - return new(bytes.Buffer) - }, -} - -func getBuffer() *bytes.Buffer { - buf, _ := bufferPool.Get().(*bytes.Buffer) - return buf -} - -func releaseBuffer(buf *bytes.Buffer) { - const maxBufCap = 1024 - if buf.Cap() > maxBufCap { - return - } - buf.Reset() - bufferPool.Put(buf) -} diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 7929b5ebd2fae325f4c295bbff67e42ebd18ed38..8f9c3543a529209c98a617388e2a75016490d097 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -6,9 +6,9 @@ import ( "git.perx.ru/perxis/perxis-go/pkg/errors" "git.perx.ru/perxis/perxis-go/pkg/expr" - "git.perx.ru/perxis/perxis-go/pkg/schema/executor" "git.perx.ru/perxis/perxis-go/pkg/schema/field" "git.perx.ru/perxis/perxis-go/pkg/schema/modify" + "git.perx.ru/perxis/perxis-go/pkg/schema/template" "git.perx.ru/perxis/perxis-go/pkg/schema/validate" ) @@ -196,6 +196,42 @@ func (s *Schema) ToValue(ctx context.Context, data map[string]interface{}) (res return data, err } +func (s *Schema) ResolveFieldsTemplates(ctx context.Context, exec template.Executor) (*Schema, error) { + resolved := s.Clone(false) + + _, _, err := resolved.Walk(ctx, + nil, + func(ctx context.Context, fld *field.Field, _ any) (field.WalkFuncResult, error) { + var result field.WalkFuncResult + + enabled, _ := fld.IsEnabled(ctx) + if !enabled { + result.Stop = true + return result, nil + } + + for _, op := range fld.Options { + executable, ok := op.(template.Executable) + if !ok { + continue + } + err := executable.Execute(exec, fld) + if err != nil { + return result, err + } + } + + return result, nil + }, + field.WalkSchema(), + ) + if err != nil { + return nil, err + } + + return resolved, nil +} + type parentFieldCtxKey struct{} func (s *Schema) Introspect(ctx context.Context, data map[string]interface{}) (map[string]interface{}, *Schema, error) { @@ -267,20 +303,6 @@ func (s *Schema) Introspect(ctx context.Context, data map[string]interface{}) (m return val, mutatedSchema, nil } -func (s *Schema) Execute(ctx context.Context, exec executor.Executor) (*Schema, error) { - if err := s.Load(ctx); err != nil { - return nil, err - } - - clone := s.Clone(false) - err := executor.Execute(ctx, clone, exec) - if err != nil { - return nil, err - } - - return clone, nil -} - // GetEnum возвращает список опций перечисления для поля func (s *Schema) GetEnum(fieldPath string) []validate.EnumOpt { f := s.Field.GetField(fieldPath) diff --git a/pkg/schema/schema_test.go b/pkg/schema/schema_test.go index 6779ca60802fd6624f06a4aeb74fa1d901ec95d0..dbedf48f9667c348c0898b73a3d308312aede6c6 100644 --- a/pkg/schema/schema_test.go +++ b/pkg/schema/schema_test.go @@ -1,13 +1,10 @@ package schema import ( - "context" "testing" - "git.perx.ru/perxis/perxis-go/pkg/schema/executor" "git.perx.ru/perxis/perxis-go/pkg/schema/field" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestSchema_Clone(t *testing.T) { @@ -19,47 +16,3 @@ func TestSchema_Clone(t *testing.T) { fld = f.Clone(false) assert.NotNil(t, fld.State) } - -func TestSchema_Execute(t *testing.T) { - sch := New("layout", field.String().AddOptions( - executor.ItemFilter{ - Query: []string{ - `icontains(group, "{{ .Collection.ID }}")`, - `{{ if .Item.Data.name -}} name == "{{ .Item.Data.name }}" {{- else }} {{ error "Пожалуйста, укажите значение для поля \"name\"" }} {{ end }}`, - `age >= {{ .Item.Data.age }}`, - `{{ if ge .Item.Data.age 18 -}} adult == true {{- end }}`, - }, - QueryError: "{{ .Error }}", - }, - )) - - executed, err := sch.Execute(context.Background(), executor.NewTemplateExecutor(map[string]any{ - "Item": map[string]any{ - "Data": map[string]any{ - "age": 18, - "name": "John", - }, - }, - "Collection": map[string]any{ - "ID": "coll_id", - }, - })) - require.NoError(t, err) - executed = executed.ClearState() - - want := New("layout", field.String().AddOptions( - executor.ItemFilter{ - Query: []string{ - `icontains(group, "coll_id")`, - `name == "John"`, - `age >= 18`, - `adult == true`, - }, - }, - )) - err = want.Load(context.Background()) - want = want.ClearState() - - require.NoError(t, err) - assert.Equal(t, want, executed) -} diff --git a/pkg/schema/executor/collection_filter.go b/pkg/schema/template/collection_filter.go similarity index 77% rename from pkg/schema/executor/collection_filter.go rename to pkg/schema/template/collection_filter.go index 283a92c52dadf692e1efdad98774a02336c34bf7..0ab7178f66e8d67d2bf9a21d78b5cce129214561 100644 --- a/pkg/schema/executor/collection_filter.go +++ b/pkg/schema/template/collection_filter.go @@ -1,8 +1,6 @@ -package executor +package template import ( - "context" - "git.perx.ru/perxis/perxis-go/pkg/schema/field" ) @@ -28,20 +26,19 @@ func (opt CollectionFilter) GetName() string { return "collection_filter" } -func (opt CollectionFilter) Execute(ctx context.Context, exec Executor, fld *field.Field) error { +func (opt CollectionFilter) Execute(exec Executor, fld *field.Field) error { var err error - - opt.ID, err = ExecuteAll(ctx, exec, opt.ID) + opt.ID, err = exec.ExecuteList(opt.ID) if err != nil && !exec.IsCanceled() { return err } - opt.Name, err = ExecuteAll(ctx, exec, opt.Name) + opt.Name, err = exec.ExecuteList(opt.Name) if err != nil && !exec.IsCanceled() { return err } - opt.Tag, err = ExecuteAll(ctx, exec, opt.Tag) + opt.Tag, err = exec.ExecuteList(opt.Tag) if err != nil && !exec.IsCanceled() { return err } diff --git a/pkg/schema/executor/collection_filter_test.go b/pkg/schema/template/collection_filter_test.go similarity index 66% rename from pkg/schema/executor/collection_filter_test.go rename to pkg/schema/template/collection_filter_test.go index 470dd5132aa120e6dea48c6857c24df9431a64a6..4af61b2295d205e1076ebc1237a79dd2fc0afee6 100644 --- a/pkg/schema/executor/collection_filter_test.go +++ b/pkg/schema/template/collection_filter_test.go @@ -1,7 +1,6 @@ -package executor +package template import ( - "context" "testing" "git.perx.ru/perxis/perxis-go/pkg/schema/field" @@ -12,13 +11,17 @@ func TestCollectionFilterExecute(t *testing.T) { tests := []struct { name string input CollectionFilter - want CollectionFilter + want *CollectionFilter assertErr assert.ErrorAssertionFunc }{ { - name: "empty", - input: CollectionFilter{}, - want: CollectionFilter{}, + name: "empty", + input: CollectionFilter{}, + want: &CollectionFilter{ + ID: []string{}, + Name: []string{}, + Tag: []string{}, + }, assertErr: assert.NoError, }, { @@ -28,10 +31,12 @@ func TestCollectionFilterExecute(t *testing.T) { "{{ .data.id }}", }, }, - want: CollectionFilter{ + want: &CollectionFilter{ ID: []string{ "adebcfg", }, + Name: []string{}, + Tag: []string{}, }, assertErr: assert.NoError, }, @@ -42,10 +47,12 @@ func TestCollectionFilterExecute(t *testing.T) { "{{ .data.name }}", }, }, - want: CollectionFilter{ + want: &CollectionFilter{ Name: []string{ "John", }, + ID: []string{}, + Tag: []string{}, }, assertErr: assert.NoError, }, @@ -56,10 +63,12 @@ func TestCollectionFilterExecute(t *testing.T) { "{{ .data.layout }}", }, }, - want: CollectionFilter{ + want: &CollectionFilter{ Tag: []string{ "post", }, + ID: []string{}, + Name: []string{}, }, assertErr: assert.NoError, }, @@ -70,10 +79,12 @@ func TestCollectionFilterExecute(t *testing.T) { "tag", }, }, - want: CollectionFilter{ + want: &CollectionFilter{ Tag: []string{ "tag", }, + ID: []string{}, + Name: []string{}, }, assertErr: assert.NoError, }, @@ -84,14 +95,17 @@ func TestCollectionFilterExecute(t *testing.T) { `{{ error "some error" }}`, }, }, - want: CollectionFilter{}, + want: &CollectionFilter{ + ID: []string{}, + Name: []string{}, + }, assertErr: assert.NoError, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - exec := NewTemplateExecutor(map[string]any{ + exec := newTestExecutor(map[string]any{ "data": map[string]any{ "id": "adebcfg", "name": "John", @@ -99,11 +113,14 @@ func TestCollectionFilterExecute(t *testing.T) { }, }) - fld := field.String().AddOptions(tc.input) - err := Execute(context.Background(), fld, exec) + fld := field.String() + err := tc.input.Execute(exec, fld) tc.assertErr(t, err) - want := field.String().AddOptions(tc.want) + want := field.String() + if tc.want != nil { + want = want.AddOptions(*tc.want) + } assert.Equal(t, want, fld) }) } diff --git a/pkg/schema/executor/item_filter.go b/pkg/schema/template/item_filter.go similarity index 78% rename from pkg/schema/executor/item_filter.go rename to pkg/schema/template/item_filter.go index bde2499b6fd99f6ca911bf702ff29eb7d09dc953..713ca1c2c4a5005103d541db1dfb27a923e04020 100644 --- a/pkg/schema/executor/item_filter.go +++ b/pkg/schema/template/item_filter.go @@ -1,8 +1,6 @@ -package executor +package template import ( - "context" - "git.perx.ru/perxis/perxis-go/pkg/schema/field" ) @@ -26,15 +24,14 @@ func (opt ItemFilter) GetName() string { return "item_filter" } -func (opt ItemFilter) Execute(ctx context.Context, exec Executor, fld *field.Field) error { +func (opt ItemFilter) Execute(exec Executor, fld *field.Field) error { var err error - - opt.Query, err = ExecuteAll(ctx, exec, opt.Query) + opt.Query, err = exec.ExecuteList(opt.Query) if err != nil && !exec.IsCanceled() { return err } - opt.QueryError, err = exec.Execute(ctx, opt.QueryError) + opt.QueryError, err = exec.Execute(opt.QueryError) if err != nil { return err } diff --git a/pkg/schema/executor/item_filter_test.go b/pkg/schema/template/item_filter_test.go similarity index 80% rename from pkg/schema/executor/item_filter_test.go rename to pkg/schema/template/item_filter_test.go index f4f631598f196492660f60334c4ba826c8312b1c..701b4fa900a9a18907cca01b2897734d1932fad4 100644 --- a/pkg/schema/executor/item_filter_test.go +++ b/pkg/schema/template/item_filter_test.go @@ -1,7 +1,6 @@ -package executor +package template import ( - "context" "testing" "git.perx.ru/perxis/perxis-go/pkg/schema/field" @@ -12,13 +11,15 @@ func TestItemFilter_Execute(t *testing.T) { tests := []struct { name string input ItemFilter - want ItemFilter + want *ItemFilter assertErr assert.ErrorAssertionFunc }{ { - name: "empty", - input: ItemFilter{}, - want: ItemFilter{}, + name: "empty", + input: ItemFilter{}, + want: &ItemFilter{ + Query: []string{}, + }, assertErr: assert.NoError, }, { @@ -31,7 +32,7 @@ func TestItemFilter_Execute(t *testing.T) { ``, }, }, - want: ItemFilter{ + want: &ItemFilter{ Query: []string{ `adebcfg`, `first_name == "John"`, @@ -47,7 +48,7 @@ func TestItemFilter_Execute(t *testing.T) { `{{ if .data.last_name -}} last_name == "{{ .data.last_name }}" {{- else -}} first_name == "{{ .data.first_name }}" {{- end }}`, }, }, - want: ItemFilter{ + want: &ItemFilter{ Query: []string{ `first_name == "John"`, `last_name == "Smith"`, @@ -64,7 +65,7 @@ func TestItemFilter_Execute(t *testing.T) { }, QueryError: `{{ .Error }}`, }, - want: ItemFilter{ + want: &ItemFilter{ QueryError: `Пожалуйста, укажите значение для поля "login"`, }, assertErr: assert.NoError, @@ -76,18 +77,13 @@ func TestItemFilter_Execute(t *testing.T) { `{{ if `, }, }, - want: ItemFilter{ - Query: []string{ - `{{ if `, - }, - }, assertErr: assert.Error, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - exec := NewTemplateExecutor(map[string]any{ + exec := newTestExecutor(map[string]any{ "data": map[string]any{ "id": "adebcfg", "first_name": "John", @@ -95,11 +91,14 @@ func TestItemFilter_Execute(t *testing.T) { }, }) - fld := field.String().AddOptions(tc.input) - err := Execute(context.Background(), fld, exec) + fld := field.String() + err := tc.input.Execute(exec, fld) tc.assertErr(t, err) - want := field.String().AddOptions(tc.want) + want := field.String() + if tc.want != nil { + want = want.AddOptions(*tc.want) + } assert.Equal(t, want, fld) }) } diff --git a/pkg/schema/template/template.go b/pkg/schema/template/template.go new file mode 100644 index 0000000000000000000000000000000000000000..bfd9c277f4dea6d913187158e84d75b854b0996e --- /dev/null +++ b/pkg/schema/template/template.go @@ -0,0 +1,21 @@ +package template + +import ( + "git.perx.ru/perxis/perxis-go/pkg/schema/field" +) + +type Executor interface { + Execute(input string, data ...any) (string, error) + ExecuteList(inputs []string, data ...any) ([]string, error) + IsCanceled() bool +} + +type Executable interface { + Execute(exec Executor, fld *field.Field) error +} + +//nolint:gochecknoinits // init нужен для регистрации опций +func init() { + field.RegisterOption(CollectionFilter{}) + field.RegisterOption(ItemFilter{}) +} diff --git a/pkg/schema/template/template_test.go b/pkg/schema/template/template_test.go new file mode 100644 index 0000000000000000000000000000000000000000..b753fab2d29fc3a8db3eb9ac96fb1de39e1e638e --- /dev/null +++ b/pkg/schema/template/template_test.go @@ -0,0 +1,84 @@ +package template + +import ( + "bytes" + "text/template" + + "git.perx.ru/perxis/perxis-go/pkg/errors" +) + +type testExecutor struct { + data map[string]any +} + +func newTestExecutor(data map[string]any) *testExecutor { + return &testExecutor{ + data: data, + } +} + +func (exec *testExecutor) Execute(input string, _ ...any) (string, error) { + templ, err := exec.template().Parse(input) + if err != nil { + return "", err + } + + var buf bytes.Buffer + err = templ.Execute(&buf, exec.data) + if err != nil { + return "", err + } + + output := buf.String() + if output == "<no value>" { + return "", nil + } + + return output, nil +} + +func (exec *testExecutor) ExecuteList(inputs []string, _ ...any) ([]string, error) { + templ := exec.template() + + result := make([]string, 0, len(inputs)) + + var buf bytes.Buffer + for _, input := range inputs { + t, err := templ.Parse(input) + if err != nil { + return nil, err + } + + buf.Reset() + err = t.Execute(&buf, exec.data) + if err != nil { + return nil, err + } + + output := buf.String() + if output == "" || output == "<no value>" { + continue + } + + result = append(result, output) + } + + return result, nil +} + +func (exec *testExecutor) IsCanceled() bool { + _, ok := exec.data["Error"] + return ok +} + +func (exec *testExecutor) template() *template.Template { + return template.New("test_executor").Funcs(template.FuncMap{ + "error": func(text string) (string, error) { + if exec.data == nil { + exec.data = make(map[string]any) + } + exec.data["Error"] = text + return "", errors.New(text) + }, + }) +} diff --git a/template/builder.go b/template/builder.go index 64530be62337de21dc5b93956df261ce5211a302..34faf1b05a94469b0de1061e189f40423eb8c8e3 100644 --- a/template/builder.go +++ b/template/builder.go @@ -3,6 +3,7 @@ package template import ( "bytes" "context" + "sync" "text/template" "git.perx.ru/perxis/perxis-go/pkg/collections" @@ -11,6 +12,16 @@ import ( "git.perx.ru/perxis/perxis-go/pkg/spaces" ) +type Option func(*Builder) + +// WithStripEmptyValue заменяет "<no value>" на пустую строку при одиночной обработке, +// при множественной обработке удаляет строки с "<no value>" вместе с пустыми строками из результата. +func WithStripEmptyValue() Option { + return func(builder *Builder) { + builder.StripEmptyValue = true + } +} + type Builder struct { ctx context.Context cnt *content.Content @@ -19,26 +30,36 @@ type Builder struct { CollID string data map[string]interface{} + // Опции билдера + StripEmptyValue bool + // Для кеширования запросов space *spaces.Space environment *environments.Environment collection *collections.Collection } -func NewBuilder(cnt *content.Content, space, env, col string) *Builder { - return &Builder{ +func NewBuilder(cnt *content.Content, space, env, col string, opts ...Option) *Builder { + b := &Builder{ ctx: context.Background(), cnt: cnt, SpaceID: space, EnvID: env, CollID: col, } + + for _, opt := range opts { + opt(b) + } + + return b } func (b *Builder) getFuncs() template.FuncMap { return template.FuncMap{ "lookup": getLookup(b), "system": getSystem(b), + "error": getErrorFunc(b), } } @@ -89,23 +110,34 @@ func (b *Builder) Template() *template.Template { } func (b *Builder) Execute(str string, data ...any) (string, error) { - t := b.Template() - buf := new(bytes.Buffer) - t, err := t.Parse(str) + t, err := b.Template().Parse(str) if err != nil { return "", err } + + buf := getBuffer() + defer releaseBuffer(buf) + if err = t.Execute(buf, b.getData(data...)); err != nil { return "", err } - return buf.String(), nil + + output := buf.String() + if b.StripEmptyValue && output == "<no value>" { + output = "" + } + + return output, nil } func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) { t := b.Template() - result := make([]string, len(str)) - buffer := new(bytes.Buffer) - for i, tmpl := range str { + result := make([]string, 0, len(str)) + + buf := getBuffer() + defer releaseBuffer(buf) + + for _, tmpl := range str { if tmpl == "" { continue } @@ -113,12 +145,20 @@ func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) { if err != nil { return []string{}, err } - if err = t.Execute(buffer, b.getData(data...)); err != nil { + + buf.Reset() + if err = t.Execute(buf, b.getData(data...)); err != nil { return []string{}, err } - result[i] = buffer.String() - buffer.Reset() + + output := buf.String() + if b.StripEmptyValue && (output == "" || output == "<no value>") { + continue + } + + result = append(result, output) } + return result, nil } @@ -150,6 +190,13 @@ func (b *Builder) ExecuteMap(str map[string]interface{}, data ...any) (map[strin return result, nil } +// IsCanceled возвращает true, если в процессе выполнения шаблона была вызвана +// шаблонная функция error. +func (b *Builder) IsCanceled() bool { + _, ok := b.data["Error"] + return ok +} + func (b *Builder) getData(data ...any) any { if len(data) == 0 { return b.data @@ -177,3 +224,20 @@ func mergeMaps(in ...map[string]interface{}) map[string]interface{} { } return out } + +//nolint:gochecknoglobals // Доступ к пулу необходим на глобальном уровне. +var bufferPool = &sync.Pool{ + New: func() any { + return new(bytes.Buffer) + }, +} + +func getBuffer() *bytes.Buffer { + buf, _ := bufferPool.Get().(*bytes.Buffer) + return buf +} + +func releaseBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} diff --git a/template/funcs.go b/template/funcs.go index 0c320ad139e964f002b691ce097b24e70e6cfaf3..b1cf830e305788ee511029d262c8822e7e62ce93 100644 --- a/template/funcs.go +++ b/template/funcs.go @@ -41,3 +41,15 @@ func getSystem(b *Builder) any { return &System{builder: b} } } + +// getErrorFunc возвращает функцию, которая устанавливает сообщение об ошибке с заданным текстом. +// Текст ошибки можно использовать в других шаблонах с помощью конструкции {{ .Error }}. +func getErrorFunc(b *Builder) func(string) (string, error) { + return func(text string) (string, error) { + if b.data == nil { + b.data = make(map[string]any) + } + b.data["Error"] = text + return "", errors.New(text) + } +}