Skip to content

Commit

Permalink
introduce ability to register types for extensions
Browse files Browse the repository at this point in the history
Signed-off-by: Nicolas De Loof <[email protected]>
  • Loading branch information
ndeloof authored and glours committed Feb 6, 2024
1 parent 209346c commit 6dbfecb
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 6 deletions.
13 changes: 13 additions & 0 deletions cli/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,19 @@ func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn {
}
}

// WithExtension register a know extension `x-*` with the go struct type to decode into
func WithExtension(name string, typ any) ProjectOptionsFn {
return func(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
if options.KnownExtensions == nil {
options.KnownExtensions = map[string]any{}
}
options.KnownExtensions[name] = typ
})
return nil
}
}

// WithoutEnvironmentResolution disable environment resolution
func WithoutEnvironmentResolution(o *ProjectOptions) error {
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
Expand Down
36 changes: 30 additions & 6 deletions loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ type Options struct {
Profiles []string
// ResourceLoaders manages support for remote resources
ResourceLoaders []ResourceLoader
// KnownExtensions manages x-* attribute we know and the corresponding go structs
KnownExtensions map[string]any
}

// ResourceLoader is a plugable remote resource resolver
Expand Down Expand Up @@ -148,6 +150,7 @@ func (o *Options) clone() *Options {
projectNameImperativelySet: o.projectNameImperativelySet,
Profiles: o.Profiles,
ResourceLoaders: o.ResourceLoaders,
KnownExtensions: o.KnownExtensions,
}
}

Expand Down Expand Up @@ -456,7 +459,11 @@ func load(ctx context.Context, configDetails types.ConfigDetails, opts *Options,
}
delete(dict, "name") // project name set by yaml must be identified by caller as opts.projectName

dict = groupXFieldsIntoExtensions(dict, tree.NewPath())
dict, err = processExtensions(dict, tree.NewPath(), opts.KnownExtensions)
if err != nil {
return nil, err
}

err = Transform(dict, project)
if err != nil {
return nil, err
Expand Down Expand Up @@ -596,8 +603,9 @@ var userDefinedKeys = []tree.Path{
"configs",
}

func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[string]interface{} {
extras := map[string]interface{}{}
func processExtensions(dict map[string]any, p tree.Path, extensions map[string]any) (map[string]interface{}, error) {
extras := map[string]any{}
var err error
for key, value := range dict {
skip := false
for _, uk := range userDefinedKeys {
Expand All @@ -613,19 +621,35 @@ func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[st
}
switch v := value.(type) {
case map[string]interface{}:
dict[key] = groupXFieldsIntoExtensions(v, p.Next(key))
dict[key], err = processExtensions(v, p.Next(key), extensions)
if err != nil {
return nil, err
}
case []interface{}:
for i, e := range v {
if m, ok := e.(map[string]interface{}); ok {
v[i] = groupXFieldsIntoExtensions(m, p.Next(strconv.Itoa(i)))
v[i], err = processExtensions(m, p.Next(strconv.Itoa(i)), extensions)
if err != nil {
return nil, err
}
}
}
}
}
for name, val := range extras {
if typ, ok := extensions[name]; ok {
target := reflect.New(reflect.TypeOf(typ)).Elem().Interface()
err = Transform(val, &target)
if err != nil {
return nil, err
}
extras[name] = target
}
}
if len(extras) > 0 {
dict[consts.Extensions] = extras
}
return dict
return dict, nil
}

// Transform converts the source into the target struct with compose types transformer
Expand Down
31 changes: 31 additions & 0 deletions loader/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3076,3 +3076,34 @@ func withProjectName(projectName string, imperativelySet bool) func(*Options) {
opts.SetProjectName(projectName, imperativelySet)
}
}

func TestKnowExtensions(t *testing.T) {
yaml := `
name: test-know-extensions
services:
test:
image: foo
x-magic:
foo: bar
`
type Magic struct {
Foo string
}

p, err := LoadWithContext(context.Background(), types.ConfigDetails{
ConfigFiles: []types.ConfigFile{
{
Content: []byte(yaml),
},
},
}, func(options *Options) {
options.KnownExtensions = map[string]any{
"x-magic": Magic{},
}
})
assert.NilError(t, err)
x := p.Services["test"].Extensions["x-magic"]
magic, ok := x.(Magic)
assert.Check(t, ok)
assert.Equal(t, magic.Foo, "bar")
}

0 comments on commit 6dbfecb

Please sign in to comment.