Skip to content

Commit 6dbfecb

Browse files
ndeloofglours
authored andcommitted
introduce ability to register types for extensions
Signed-off-by: Nicolas De Loof <[email protected]>
1 parent 209346c commit 6dbfecb

File tree

3 files changed

+74
-6
lines changed

3 files changed

+74
-6
lines changed

cli/options.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,19 @@ func WithResourceLoader(r loader.ResourceLoader) ProjectOptionsFn {
352352
}
353353
}
354354

355+
// WithExtension register a know extension `x-*` with the go struct type to decode into
356+
func WithExtension(name string, typ any) ProjectOptionsFn {
357+
return func(o *ProjectOptions) error {
358+
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {
359+
if options.KnownExtensions == nil {
360+
options.KnownExtensions = map[string]any{}
361+
}
362+
options.KnownExtensions[name] = typ
363+
})
364+
return nil
365+
}
366+
}
367+
355368
// WithoutEnvironmentResolution disable environment resolution
356369
func WithoutEnvironmentResolution(o *ProjectOptions) error {
357370
o.loadOptions = append(o.loadOptions, func(options *loader.Options) {

loader/loader.go

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ type Options struct {
7676
Profiles []string
7777
// ResourceLoaders manages support for remote resources
7878
ResourceLoaders []ResourceLoader
79+
// KnownExtensions manages x-* attribute we know and the corresponding go structs
80+
KnownExtensions map[string]any
7981
}
8082

8183
// ResourceLoader is a plugable remote resource resolver
@@ -148,6 +150,7 @@ func (o *Options) clone() *Options {
148150
projectNameImperativelySet: o.projectNameImperativelySet,
149151
Profiles: o.Profiles,
150152
ResourceLoaders: o.ResourceLoaders,
153+
KnownExtensions: o.KnownExtensions,
151154
}
152155
}
153156

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

459-
dict = groupXFieldsIntoExtensions(dict, tree.NewPath())
462+
dict, err = processExtensions(dict, tree.NewPath(), opts.KnownExtensions)
463+
if err != nil {
464+
return nil, err
465+
}
466+
460467
err = Transform(dict, project)
461468
if err != nil {
462469
return nil, err
@@ -596,8 +603,9 @@ var userDefinedKeys = []tree.Path{
596603
"configs",
597604
}
598605

599-
func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[string]interface{} {
600-
extras := map[string]interface{}{}
606+
func processExtensions(dict map[string]any, p tree.Path, extensions map[string]any) (map[string]interface{}, error) {
607+
extras := map[string]any{}
608+
var err error
601609
for key, value := range dict {
602610
skip := false
603611
for _, uk := range userDefinedKeys {
@@ -613,19 +621,35 @@ func groupXFieldsIntoExtensions(dict map[string]interface{}, p tree.Path) map[st
613621
}
614622
switch v := value.(type) {
615623
case map[string]interface{}:
616-
dict[key] = groupXFieldsIntoExtensions(v, p.Next(key))
624+
dict[key], err = processExtensions(v, p.Next(key), extensions)
625+
if err != nil {
626+
return nil, err
627+
}
617628
case []interface{}:
618629
for i, e := range v {
619630
if m, ok := e.(map[string]interface{}); ok {
620-
v[i] = groupXFieldsIntoExtensions(m, p.Next(strconv.Itoa(i)))
631+
v[i], err = processExtensions(m, p.Next(strconv.Itoa(i)), extensions)
632+
if err != nil {
633+
return nil, err
634+
}
621635
}
622636
}
623637
}
624638
}
639+
for name, val := range extras {
640+
if typ, ok := extensions[name]; ok {
641+
target := reflect.New(reflect.TypeOf(typ)).Elem().Interface()
642+
err = Transform(val, &target)
643+
if err != nil {
644+
return nil, err
645+
}
646+
extras[name] = target
647+
}
648+
}
625649
if len(extras) > 0 {
626650
dict[consts.Extensions] = extras
627651
}
628-
return dict
652+
return dict, nil
629653
}
630654

631655
// Transform converts the source into the target struct with compose types transformer

loader/loader_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,3 +3076,34 @@ func withProjectName(projectName string, imperativelySet bool) func(*Options) {
30763076
opts.SetProjectName(projectName, imperativelySet)
30773077
}
30783078
}
3079+
3080+
func TestKnowExtensions(t *testing.T) {
3081+
yaml := `
3082+
name: test-know-extensions
3083+
services:
3084+
test:
3085+
image: foo
3086+
x-magic:
3087+
foo: bar
3088+
`
3089+
type Magic struct {
3090+
Foo string
3091+
}
3092+
3093+
p, err := LoadWithContext(context.Background(), types.ConfigDetails{
3094+
ConfigFiles: []types.ConfigFile{
3095+
{
3096+
Content: []byte(yaml),
3097+
},
3098+
},
3099+
}, func(options *Options) {
3100+
options.KnownExtensions = map[string]any{
3101+
"x-magic": Magic{},
3102+
}
3103+
})
3104+
assert.NilError(t, err)
3105+
x := p.Services["test"].Extensions["x-magic"]
3106+
magic, ok := x.(Magic)
3107+
assert.Check(t, ok)
3108+
assert.Equal(t, magic.Foo, "bar")
3109+
}

0 commit comments

Comments
 (0)