Skip to content

Commit ee8ff4f

Browse files
committed
component: support array,slice,map for refs
1 parent 71b9433 commit ee8ff4f

File tree

2 files changed

+118
-71
lines changed

2 files changed

+118
-71
lines changed

component/component.go

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -296,27 +296,49 @@ func (c *BaseComponentWithRefs[T, R]) resolveRefs(container Container) error {
296296

297297
// recursiveResolveRefs recursively resolves references in nested structs
298298
func (c *BaseComponentWithRefs[T, R]) recursiveResolveRefs(container Container, t reflect.Type, v reflect.Value) error {
299-
for i := 0; i < v.NumField(); i++ {
300-
ft := t.Field(i)
301-
fv := v.Field(i)
302-
303-
resolver := getResolver(fv)
304-
if resolver == nil && fv.CanAddr() {
305-
resolver = getResolver(fv.Addr())
299+
resolver := getResolver(v)
300+
if resolver == nil && v.CanAddr() {
301+
resolver = getResolver(v.Addr())
302+
}
303+
if resolver != nil {
304+
if err := resolver.Resolve(container); err != nil {
305+
return fmt.Errorf("failed to resolve reference %s to %s: %w", t.Name(), resolver.UUID(), err)
306306
}
307-
if resolver == nil {
308-
if fv.Kind() == reflect.Struct {
309-
if err := c.recursiveResolveRefs(container, fv.Type(), fv); err != nil {
310-
return err
311-
}
307+
c.Logger().Info("resolve referenced component", "current", c.identifier, "ref", resolver.UUID())
308+
return nil
309+
}
310+
switch v.Kind() {
311+
case reflect.Ptr:
312+
if v.IsNil() {
313+
return nil
314+
}
315+
if err := c.recursiveResolveRefs(container, v.Elem().Type(), v.Elem()); err != nil {
316+
return err
317+
}
318+
case reflect.Struct:
319+
for i := 0; i < v.NumField(); i++ {
320+
ft := t.Field(i)
321+
fv := v.Field(i)
322+
if err := c.recursiveResolveRefs(container, ft.Type, fv); err != nil {
323+
return err
312324
}
313-
continue
314325
}
315-
316-
if err := resolver.Resolve(container); err != nil {
317-
return fmt.Errorf("failed to resolve reference %s to %s: %w", ft.Name, resolver.UUID(), err)
326+
case reflect.Array, reflect.Slice:
327+
for i := 0; i < v.Len(); i++ {
328+
if err := c.recursiveResolveRefs(container, v.Index(i).Type(), v.Index(i)); err != nil {
329+
return err
330+
}
331+
}
332+
case reflect.Map:
333+
for _, key := range v.MapKeys() {
334+
value := v.MapIndex(key)
335+
if value.Kind() != reflect.Ptr {
336+
return fmt.Errorf("failed to resolve refs: unexpected map value type: want pointer, got %s", value.Kind())
337+
}
338+
if err := c.recursiveResolveRefs(container, value.Type(), value); err != nil {
339+
return err
340+
}
318341
}
319-
c.Logger().Info("resolve referenced component", "current", c.identifier, "ref", resolver.UUID())
320342
}
321343

322344
return nil

component/component_test.go

Lines changed: 79 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -104,36 +104,20 @@ func (f *failingBaseComponent) Setup(container component.Container, config *comp
104104
return errors.New("setup failed")
105105
}
106106

107-
type nonStructRefs string
108-
109107
type componentWithNonStructRefs struct {
110-
component.BaseComponentWithRefs[struct{}, nonStructRefs]
111-
}
112-
113-
type deeplyNestedRefs struct {
114-
Ref1 component.Reference[*mockComponent]
115-
Nested struct {
116-
Ref2 component.Reference[*mockComponent]
117-
DeepNested struct {
118-
Ref3 component.Reference[*mockComponent]
119-
}
120-
}
108+
component.BaseComponentWithRefs[struct{}, string]
121109
}
122110

123111
type componentWithDeepRefs struct {
124-
component.BaseComponentWithRefs[struct{}, deeplyNestedRefs]
125-
}
126-
127-
type complexRefs struct {
128-
DirectRef component.Reference[*mockComponent]
129-
PointerRef *component.Reference[*mockComponent]
130-
NonRefField string
131-
NestedStruct struct {
132-
NestedRef component.Reference[*mockComponent]
133-
}
134-
NestedPointer *struct {
135-
PointerNestedRef component.Reference[*mockComponent]
136-
}
112+
component.BaseComponentWithRefs[struct{}, struct {
113+
Ref1 component.Reference[*mockComponent]
114+
Nested struct {
115+
Ref2 component.Reference[*mockComponent]
116+
DeepNested struct {
117+
Ref3 component.Reference[*mockComponent]
118+
}
119+
}
120+
}]
137121
}
138122

139123
type failingResolver struct {
@@ -664,13 +648,11 @@ func ExampleGroup() {
664648

665649
// TestBaseComponentWithRefs tests the BaseComponentWithRefs functionality
666650
func TestBaseComponentWithRefs(t *testing.T) {
667-
type testRefs struct {
668-
Ref1 component.Reference[*mockComponent]
669-
Ref2 component.OptionalReference[*mockComponent]
670-
}
671-
672651
type testComponent struct {
673-
component.BaseComponentWithRefs[mockOptions, testRefs]
652+
component.BaseComponentWithRefs[mockOptions, struct {
653+
Ref1 component.Reference[*mockComponent]
654+
Ref2 component.OptionalReference[*mockComponent]
655+
}]
674656
}
675657

676658
container := newMockContainer()
@@ -729,15 +711,13 @@ func TestBaseComponentWithRefs(t *testing.T) {
729711
})
730712

731713
t.Run("Nested refs", func(t *testing.T) {
732-
type nestedRefs struct {
733-
Ref1 component.Reference[*mockComponent]
734-
NestedStruct struct {
735-
Ref2 component.Reference[*mockComponent]
736-
}
737-
}
738-
739714
type nestedComponent struct {
740-
component.BaseComponentWithRefs[mockOptions, nestedRefs]
715+
component.BaseComponentWithRefs[mockOptions, struct {
716+
Ref1 component.Reference[*mockComponent]
717+
NestedStruct struct {
718+
Ref2 component.Reference[*mockComponent]
719+
}
720+
}]
741721
}
742722

743723
container := newMockContainer()
@@ -768,15 +748,23 @@ func TestBaseComponentWithRefs(t *testing.T) {
768748
})
769749

770750
t.Run("Different field types", func(t *testing.T) {
771-
type mixedRefs struct {
772-
Ref1 component.Reference[*mockComponent]
773-
Ref2 *component.Reference[*mockComponent]
774-
Ref3 interface{}
775-
Ref4 string
776-
}
777-
778751
type mixedComponent struct {
779-
component.BaseComponentWithRefs[mockOptions, mixedRefs]
752+
component.BaseComponentWithRefs[mockOptions, struct {
753+
Ref1 component.Reference[*mockComponent]
754+
Ref2 *component.Reference[*mockComponent]
755+
Ref3 interface{}
756+
Ref4 string
757+
758+
X struct {
759+
Ref5 component.Reference[*mockComponent]
760+
}
761+
Y *struct {
762+
Ref6 component.Reference[*mockComponent]
763+
}
764+
Ref7 [2]component.Reference[*mockComponent]
765+
Ref8 []component.Reference[*mockComponent]
766+
Ref9 map[string]*component.Reference[*mockComponent]
767+
}]
780768
}
781769

782770
container := newMockContainer()
@@ -787,7 +775,7 @@ func TestBaseComponentWithRefs(t *testing.T) {
787775
config := component.Config{
788776
Name: "MixedComponent",
789777
UUID: "mixed-uuid",
790-
Refs: types.NewRawObject(`{"Ref1":"ref1","Ref2":null,"Ref3":{},"Ref4":"not-a-ref"}`),
778+
Refs: types.NewRawObject(`{"Ref1":"ref1","Ref2":null,"Ref3":{},"Ref4":"not-a-ref","X":{"Ref5":"ref1"},"Y":{"Ref6":"ref1"},"Ref7":["ref1","ref1"],"Ref8":["ref1"],"Ref9":{"key":"ref1"}}`),
791779
}
792780

793781
err := mixedComp.Setup(container, &config, false)
@@ -803,6 +791,28 @@ func TestBaseComponentWithRefs(t *testing.T) {
803791
t.Error("Ref2 should be nil")
804792
}
805793

794+
if mixedComp.Refs().X.Ref5.Component() != mc {
795+
t.Error("Nested Ref5 did not resolve to the correct component")
796+
}
797+
798+
if mixedComp.Refs().Y.Ref6.Component() != mc {
799+
t.Error("Nested Ref6 did not resolve to the correct component")
800+
}
801+
802+
if mixedComp.Refs().Ref7[0].Component() != mc || mixedComp.Refs().Ref7[1].Component() != mc {
803+
t.Error("Array refs did not resolve to the correct component")
804+
}
805+
806+
if mixedComp.Refs().Ref8[0].Component() != mc {
807+
t.Error("Slice ref did not resolve to the correct component")
808+
}
809+
810+
if len(mixedComp.Refs().Ref9) != 1 {
811+
t.Error("Map ref should have one entry")
812+
} else if mixedComp.Refs().Ref9["key"].Component() != mc {
813+
t.Error("Map ref did not resolve to the correct component: got", mixedComp.Refs().Ref9["key"])
814+
}
815+
806816
// Ref3 and Ref4 should not cause errors, but won't be resolved
807817
})
808818

@@ -885,12 +895,10 @@ func TestBaseComponentWithRefs(t *testing.T) {
885895
})
886896

887897
t.Run("Failing resolver", func(t *testing.T) {
888-
type refsWithFailingResolver struct {
889-
FailingRef failingResolver
890-
}
891-
892898
type componentWithFailingResolver struct {
893-
component.BaseComponentWithRefs[struct{}, refsWithFailingResolver]
899+
component.BaseComponentWithRefs[struct{}, struct {
900+
FailingRef failingResolver
901+
}]
894902
}
895903

896904
cfr := &componentWithFailingResolver{}
@@ -907,6 +915,23 @@ func TestBaseComponentWithRefs(t *testing.T) {
907915
})
908916
}
909917

918+
func TestComplexRefs(t *testing.T) {
919+
type complexRefs struct {
920+
DirectRef component.Reference[*mockComponent]
921+
PointerRef *component.Reference[*mockComponent]
922+
NonRefField string
923+
NestedStruct struct {
924+
NestedRef component.Reference[*mockComponent]
925+
}
926+
NestedPointer *struct {
927+
PointerNestedRef component.Reference[*mockComponent]
928+
}
929+
ArrayRef [2]component.Reference[*mockComponent]
930+
SliceRef []component.Reference[*mockComponent]
931+
MapRef map[string]component.Reference[*mockComponent]
932+
}
933+
}
934+
910935
// TestComponentLifecycle tests the full lifecycle of a component
911936
func TestComponentLifecycle(t *testing.T) {
912937
ctx := context.Background()

0 commit comments

Comments
 (0)