diff --git a/pkg/references/reference_test.go b/pkg/references/reference_test.go index 5e79de47ab96cc9e48b8ba0eb644f9519e9ee914..c44064921c1f291f5d627fc5a1a7bdff0477e1a9 100644 --- a/pkg/references/reference_test.go +++ b/pkg/references/reference_test.go @@ -2,9 +2,11 @@ package references import ( "context" + "fmt" "testing" "git.perx.ru/perxis/perxis-go/pkg/expr" + "github.com/mitchellh/mapstructure" "github.com/stretchr/testify/require" ) @@ -39,3 +41,18 @@ func TestReference_InExpr(t *testing.T) { }) } } + +func TestReference_encode(t *testing.T) { + var result map[string]any + dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{TagName: "json", Result: &result}) + require.NoError(t, err) + + err = dec.Decode(&Reference{ + ID: "ref_id", + CollectionID: "coll_id", + Disabled: false, + }) + require.NoError(t, err) + + fmt.Println(result) +} diff --git a/template/builder.go b/template/builder.go index 64530be62337de21dc5b93956df261ce5211a302..21d35d3497c7167e066ef1dedecb9cb3751a6d42 100644 --- a/template/builder.go +++ b/template/builder.go @@ -3,7 +3,9 @@ package template import ( "bytes" "context" - "text/template" + templhtml "html/template" + "io" + templtext "text/template" "git.perx.ru/perxis/perxis-go/pkg/collections" "git.perx.ru/perxis/perxis-go/pkg/content" @@ -11,13 +13,20 @@ import ( "git.perx.ru/perxis/perxis-go/pkg/spaces" ) -type Builder struct { +type Executor[T any] interface { + Parse(text string) (T, error) + Execute(w io.Writer, data any) error +} + +type Builder[T Executor[T]] struct { + template func(string) T + ctx context.Context cnt *content.Content SpaceID string EnvID string CollID string - data map[string]interface{} + data map[string]any // Для кеширования запросов space *spaces.Space @@ -25,30 +34,48 @@ type Builder struct { collection *collections.Collection } -func NewBuilder(cnt *content.Content, space, env, col string) *Builder { - return &Builder{ +func NewBuilder(cnt *content.Content, space, env, col string) Builder[*templtext.Template] { + b := Builder[*templtext.Template]{ ctx: context.Background(), cnt: cnt, SpaceID: space, EnvID: env, CollID: col, } + b.template = func(name string) *templtext.Template { + return templtext.New(name).Funcs(b.getFuncs()) + } + return b } -func (b *Builder) getFuncs() template.FuncMap { - return template.FuncMap{ +func NewHTMLBuilder(cnt *content.Content, space, env, col string) Builder[*templhtml.Template] { + b := Builder[*templhtml.Template]{ + ctx: context.Background(), + cnt: cnt, + SpaceID: space, + EnvID: env, + CollID: col, + } + b.template = func(name string) *templhtml.Template { + return templhtml.New(name).Funcs(b.getFuncs()) + } + return b +} + +func (b *Builder[T]) getFuncs() map[string]any { + return map[string]any{ "lookup": getLookup(b), "system": getSystem(b), } } -func (b *Builder) WithData(data map[string]interface{}) *Builder { +func (b *Builder[T]) WithData(data map[string]interface{}) *Builder[T] { bld := *b bld.data = data return &bld } -func (b *Builder) WithKV(kv ...any) *Builder { +func (b *Builder[T]) WithKV(kv ...any) *Builder[T] { bld := *b if bld.data == nil { bld.data = make(map[string]interface{}, 10) @@ -63,33 +90,29 @@ func (b *Builder) WithKV(kv ...any) *Builder { return &bld } -func (b *Builder) GetData() map[string]interface{} { +func (b *Builder[T]) GetData() map[string]interface{} { return b.data } -func (b *Builder) WithSpace(space, env string) *Builder { +func (b *Builder[T]) WithSpace(space, env string) *Builder[T] { bld := *b bld.SpaceID = space bld.EnvID = env return &bld } -func (b *Builder) WithContext(ctx context.Context) *Builder { +func (b *Builder[T]) WithContext(ctx context.Context) *Builder[T] { bld := *b bld.ctx = ctx return &bld } -func (b *Builder) Context() context.Context { +func (b *Builder[T]) Context() context.Context { return b.ctx } -func (b *Builder) Template() *template.Template { - return template.New("main").Funcs(b.getFuncs()) -} - -func (b *Builder) Execute(str string, data ...any) (string, error) { - t := b.Template() +func (b *Builder[T]) Execute(str string, data ...any) (string, error) { + t := b.template("main") buf := new(bytes.Buffer) t, err := t.Parse(str) if err != nil { @@ -101,8 +124,8 @@ func (b *Builder) Execute(str string, data ...any) (string, error) { return buf.String(), nil } -func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) { - t := b.Template() +func (b *Builder[T]) ExecuteList(str []string, data ...any) ([]string, error) { + t := b.template("main") result := make([]string, len(str)) buffer := new(bytes.Buffer) for i, tmpl := range str { @@ -122,7 +145,7 @@ func (b *Builder) ExecuteList(str []string, data ...any) ([]string, error) { return result, nil } -func (b *Builder) ExecuteMap(str map[string]interface{}, data ...any) (map[string]interface{}, error) { +func (b *Builder[T]) ExecuteMap(str map[string]interface{}, data ...any) (map[string]interface{}, error) { result := make(map[string]interface{}, len(str)) for k, v := range str { switch t := v.(type) { @@ -150,7 +173,7 @@ func (b *Builder) ExecuteMap(str map[string]interface{}, data ...any) (map[strin return result, nil } -func (b *Builder) getData(data ...any) any { +func (b *Builder[T]) getData(data ...any) any { if len(data) == 0 { return b.data } diff --git a/template/builder_test.go b/template/builder_test.go index 128f706423f3ecc67097a76b8926fbed619eb1ce..2a259cde0f81bd1f8c438905d55ad6b9cfcd9e08 100644 --- a/template/builder_test.go +++ b/template/builder_test.go @@ -19,14 +19,15 @@ import ( func TestBuilder_Execute(t *testing.T) { tests := []struct { - name string - SpaceID string - EnvID string - CollID string - str string - data any - want any - wantErr bool + name string + SpaceID string + EnvID string + CollID string + str string + data any + want any + htmlBuilder bool + wantErr bool getCnt func() (cnt *content.Content, assertExpectations func(t *testing.T)) }{ @@ -112,6 +113,7 @@ func TestBuilder_Execute(t *testing.T) { return &content.Content{Collections: collsSvc}, func(t *testing.T) { collsSvc.AssertExpectations(t) } }, str: "{{ system.Collection.Name }}", want: "cars", wantErr: false}, {name: "system without account", SpaceID: "space", str: "hello {{ system.Organization.Name }}", want: "", wantErr: true}, + {name: "with html builder", str: "{{ . }}", data: "<script>alert(localStorage.secret)</script>", want: "<script>alert(localStorage.secret)</script>", htmlBuilder: true, wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -122,15 +124,18 @@ func TestBuilder_Execute(t *testing.T) { defer checkFn(t) } - b := &Builder{ - ctx: context.Background(), - cnt: cnt, - SpaceID: tt.SpaceID, - EnvID: tt.EnvID, - CollID: tt.CollID, + var ( + got string + err error + ) + if tt.htmlBuilder { + b := NewHTMLBuilder(cnt, tt.SpaceID, tt.EnvID, tt.CollID) + got, err = b.Execute(tt.str, tt.data) + } else { + b := NewBuilder(cnt, tt.SpaceID, tt.EnvID, tt.CollID) + got, err = b.Execute(tt.str, tt.data) } - got, err := b.Execute(tt.str, tt.data) if tt.wantErr == true { assert.Error(t, err) } else { @@ -191,12 +196,7 @@ func TestBuilder_ExecuteList(t *testing.T) { if tt.itemsCall != nil { tt.itemsCall(itemsSvc) } - b := &Builder{ - ctx: context.Background(), - cnt: &content.Content{Items: itemsSvc}, - SpaceID: tt.SpaceID, - EnvID: tt.EnvID, - } + b := NewBuilder(&content.Content{Items: itemsSvc}, tt.SpaceID, tt.EnvID, "") got, err := b.ExecuteList(tt.str, tt.data) if tt.wantErr == true { @@ -260,12 +260,7 @@ func TestBuilder_ExecuteMap(t *testing.T) { if tt.itemsCall != nil { tt.itemsCall(itemsSvc) } - b := &Builder{ - ctx: context.Background(), - cnt: &content.Content{Items: itemsSvc}, - SpaceID: tt.SpaceID, - EnvID: tt.EnvID, - } + b := NewBuilder(&content.Content{Items: itemsSvc}, tt.SpaceID, tt.EnvID, "") got, err := b.ExecuteMap(tt.str, tt.data) if tt.wantErr == true { diff --git a/template/funcs.go b/template/funcs.go index 0c320ad139e964f002b691ce097b24e70e6cfaf3..3d69e45d0f4a4127341b757e8d8d8ddedcc61fb0 100644 --- a/template/funcs.go +++ b/template/funcs.go @@ -9,7 +9,7 @@ import ( // getLookup возвращает функцию для шаблонизатора для получения значений из записи коллекции // name указывается в виде "<collection id>.<item id>.<field>" // Использование в шаблонах: {{ lookup "secrets.key.value" }} -func getLookup(b *Builder) any { +func getLookup[T Executor[T]](b *Builder[T]) any { return func(name string) (any, error) { parsedName := strings.Split(name, ".") if len(parsedName) < 3 { @@ -36,8 +36,8 @@ func getLookup(b *Builder) any { // getSys возвращает функцию получения System // Использование в шаблонах: {{ system.SpaceID }} -func getSystem(b *Builder) any { - return func() *System { - return &System{builder: b} +func getSystem[T Executor[T]](b *Builder[T]) any { + return func() *System[T] { + return &System[T]{builder: b} } } diff --git a/template/system.go b/template/system.go index c7dda43f08c852f590cd7cfa16ec00709423c7a3..d6e66616e7dd0e2fae251dbc58b8ff0e83998a7a 100644 --- a/template/system.go +++ b/template/system.go @@ -6,23 +6,23 @@ import ( "git.perx.ru/perxis/perxis-go/pkg/spaces" ) -type System struct { - builder *Builder +type System[T Executor[T]] struct { + builder *Builder[T] } -func (s *System) SpaceID() string { +func (s *System[T]) SpaceID() string { return s.builder.SpaceID } -func (s *System) EnvID() string { +func (s *System[T]) EnvID() string { return s.builder.EnvID } -func (s *System) CollectionID() string { +func (s *System[T]) CollectionID() string { return s.builder.CollID } -func (s *System) Space() (*spaces.Space, error) { +func (s *System[T]) Space() (*spaces.Space, error) { if s.builder.space != nil { return s.builder.space, nil } @@ -32,7 +32,7 @@ func (s *System) Space() (*spaces.Space, error) { return s.builder.space, err } -func (s *System) Environment() (*environments.Environment, error) { +func (s *System[T]) Environment() (*environments.Environment, error) { if s.builder.environment != nil { return s.builder.environment, nil } @@ -42,7 +42,7 @@ func (s *System) Environment() (*environments.Environment, error) { return s.builder.environment, err } -func (s *System) Collection() (*collections.Collection, error) { +func (s *System[T]) Collection() (*collections.Collection, error) { if s.builder.collection != nil { return s.builder.collection, nil }