From 0ccd3f386c0256aa5d26453eb5a92c215623eede Mon Sep 17 00:00:00 2001 From: Gustavo Diaz Date: Mon, 23 Mar 2026 21:25:38 +0000 Subject: [PATCH 1/2] Support cross-resource references in nested map types --- pkg/generate/code/resource_reference.go | 18 +++- pkg/generate/code/resource_reference_test.go | 100 ++++++++++++++++++ .../generator-with-nested-references.yaml | 62 +++++++++++ 3 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 pkg/testdata/models/apis/quicksight/0000-00-00/generator-with-nested-references.yaml diff --git a/pkg/generate/code/resource_reference.go b/pkg/generate/code/resource_reference.go index 6bd84ee4..a916efed 100644 --- a/pkg/generate/code/resource_reference.go +++ b/pkg/generate/code/resource_reference.go @@ -355,10 +355,20 @@ func iterReferenceValues( switch ref.Shape.Type { case ("map"): - return "", fmt.Errorf( - "resource %q, field %q: references cannot be within a map", - r.Kind, field.Path, + iterVarName := fmt.Sprintf(iterVarFmt, currentListDepth) + idxVarName := fmt.Sprintf(indexVarFmt, currentListDepth) + + fieldAccessPrefix = fmt.Sprintf("%s.%s", fieldAccessPrefix, fp.At(fpDepth)) + + outPrefix += fmt.Sprintf("%sfor %s, %s := range %s {\n", indent, + lo.Ternary(shouldRenderIndexes, idxVarName, "_"), + iterVarName, + fieldAccessPrefix, ) + outSuffix = fmt.Sprintf("%s}\n%s", indent, outSuffix) + + fieldAccessPrefix = iterVarName + currentListDepth++ case ("structure"): fieldAccessPrefix = fmt.Sprintf("%s.%s", fieldAccessPrefix, fp.At(fpDepth)) @@ -425,7 +435,7 @@ func buildIndexBasedFieldAccessorWithOffset(field *model.Field, sourceVarName, i fieldName := curFP.Pop() indexList := "" - if cur.ShapeRef.Shape.Type == "list" { + if cur.ShapeRef.Shape.Type == "list" || cur.ShapeRef.Shape.Type == "map" { // We want to access indexes when iterating through lists of // structs. If we find a list at the end of the field path, then we diff --git a/pkg/generate/code/resource_reference_test.go b/pkg/generate/code/resource_reference_test.go index c801a536..e0e616c4 100644 --- a/pkg/generate/code/resource_reference_test.go +++ b/pkg/generate/code/resource_reference_test.go @@ -568,3 +568,103 @@ func Test_ClearResolvedReferencesForField_SingleReference_WithinMultipleSlices(t require.NoError(err) assert.Equal(expected, got) } + +func Test_ResolveReferencesForField_SingleReference_WithinMap(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "quicksight", + &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-with-nested-references.yaml", + }) + + crd := testutil.GetCRDByName(t, g, "DataSet") + require.NotNil(crd) + + expected := + ` for f0idx, f0iter := range ko.Spec.PhysicalTableMap { + if f0iter.CustomSQL != nil { + if f0iter.CustomSQL.DataSourceRef != nil && f0iter.CustomSQL.DataSourceRef.From != nil { + hasReferences = true + arr := f0iter.CustomSQL.DataSourceRef.From + if arr.Name == nil || *arr.Name == "" { + return hasReferences, fmt.Errorf("provided resource reference is nil or empty: PhysicalTableMap.CustomSQL.DataSourceRef") + } + namespace := ko.ObjectMeta.GetNamespace() + if arr.Namespace != nil && *arr.Namespace != "" { + namespace = *arr.Namespace + } + obj := &svcapitypes.DataSet{} + if err := getReferencedResourceState_DataSet(ctx, apiReader, obj, *arr.Name, namespace); err != nil { + return hasReferences, err + } + ko.Spec.PhysicalTableMap[f0idx].CustomSQL.DataSourceARN = (*string)(obj.Status.ACKResourceMetadata.ARN) + } + } + } +` + + field := crd.Fields["PhysicalTableMap.CustomSQL.DataSourceARN"] + require.NotNil(field, "Field PhysicalTableMap.CustomSQL.DataSourceARN not found") + got, err := code.ResolveReferencesForField(field, "ko", 1) + require.NoError(err) + assert.Equal(expected, got) +} + +func Test_ClearResolvedReferencesForField_SingleReference_WithinMap(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "quicksight", + &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-with-nested-references.yaml", + }) + + crd := testutil.GetCRDByName(t, g, "DataSet") + require.NotNil(crd) + + expected := + ` for f0idx, f0iter := range ko.Spec.PhysicalTableMap { + if f0iter.CustomSQL != nil { + if f0iter.CustomSQL.DataSourceRef != nil { + ko.Spec.PhysicalTableMap[f0idx].CustomSQL.DataSourceARN = nil + } + } + } +` + + field := crd.Fields["PhysicalTableMap.CustomSQL.DataSourceARN"] + require.NotNil(field, "Field PhysicalTableMap.CustomSQL.DataSourceARN not found") + got, err := code.ClearResolvedReferencesForField(field, "ko", 1) + require.NoError(err) + assert.Equal(expected, got) +} + +func Test_ReferenceFieldsValidation_MapNestedReference(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + g := testutil.NewModelForServiceWithOptions(t, "quicksight", + &testutil.TestingModelOptions{ + GeneratorConfigFile: "generator-with-nested-references.yaml", + }) + + crd := testutil.GetCRDByName(t, g, "DataSet") + require.NotNil(crd) + + expected := + ` for _, f0iter := range ko.Spec.PhysicalTableMap { + if f0iter.CustomSQL != nil { + if f0iter.CustomSQL.DataSourceRef != nil && f0iter.CustomSQL.DataSourceARN != nil { + return ackerr.ResourceReferenceAndIDNotSupportedFor("PhysicalTableMap.CustomSQL.DataSourceARN", "PhysicalTableMap.CustomSQL.DataSourceRef") + } + } + } +` + + field := crd.Fields["PhysicalTableMap.CustomSQL.DataSourceARN"] + require.NotNil(field, "Field PhysicalTableMap.CustomSQL.DataSourceARN not found") + got, err := code.ReferenceFieldsValidation(field, "ko", 1) + require.NoError(err) + assert.Equal(expected, got) +} diff --git a/pkg/testdata/models/apis/quicksight/0000-00-00/generator-with-nested-references.yaml b/pkg/testdata/models/apis/quicksight/0000-00-00/generator-with-nested-references.yaml new file mode 100644 index 00000000..51af1e77 --- /dev/null +++ b/pkg/testdata/models/apis/quicksight/0000-00-00/generator-with-nested-references.yaml @@ -0,0 +1,62 @@ +ignore: + resource_names: [] + field_paths: + - CreateDataSetOutput.RequestId + - CreateDataSetOutput.Status +resources: + DataSet: + exceptions: + errors: + 404: + code: ResourceNotFoundException + fields: + DataSetId: + is_primary_key: true + ColumnGroups: + compare: + is_ignored: true + ColumnLevelPermissionRules: + compare: + is_ignored: true + DataPrepConfiguration: + compare: + is_ignored: true + DataSetUsageConfiguration: + compare: + is_ignored: true + DatasetParameters: + compare: + is_ignored: true + FieldFolders: + compare: + is_ignored: true + FolderArns: + compare: + is_ignored: true + LogicalTableMap: + compare: + is_ignored: true + Permissions: + compare: + is_ignored: true + PhysicalTableMap: + compare: + is_ignored: true + RowLevelPermissionDataSet: + compare: + is_ignored: true + SemanticModelConfiguration: + compare: + is_ignored: true + PerformanceConfiguration: + compare: + is_ignored: true + UseAs: + compare: + is_ignored: true + PhysicalTableMap.CustomSql.DataSourceArn: + references: + resource: DataSet + path: Status.ACKResourceMetadata.ARN + tags: + ignore: true From 718b277dfe1c0af1bfec004b2c21058c9b915944 Mon Sep 17 00:00:00 2001 From: Gustavo Diaz Date: Wed, 25 Mar 2026 20:08:50 +0000 Subject: [PATCH 2/2] Rename map key/value varfmt for references --- pkg/generate/code/resource_reference.go | 42 ++++++++++++-------- pkg/generate/code/resource_reference_test.go | 24 +++++------ 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/pkg/generate/code/resource_reference.go b/pkg/generate/code/resource_reference.go index a916efed..f846246c 100644 --- a/pkg/generate/code/resource_reference.go +++ b/pkg/generate/code/resource_reference.go @@ -30,6 +30,14 @@ var ( // indexVarFmt stores the format string which takes an integer and creates // the name of a variable used for the index part of a for-each loop indexVarFmt = "f%didx" + + // mapKeyVarFmt stores the format string which takes an integer and creates + // the name of a variable used for the key part of a map iteration + mapKeyVarFmt = "f%dkey" + + // mapValueVarFmt stores the format string which takes an integer and creates + // the name of a variable used for the value part of a map iteration + mapValueVarFmt = "f%dvalue" ) // ReferenceFieldsValidation returns the go code to validate reference field and @@ -235,7 +243,7 @@ func ResolveReferencesForField(field *model.Field, sourceVarName string, indentL outPrefix += getReferencedStateForField(field, innerIndentLevel) - concreteValueAccessor, err := buildIndexBasedFieldAccessor(field, sourceVarName, indexVarFmt) + concreteValueAccessor, err := buildIndexBasedFieldAccessor(field, sourceVarName) if err != nil { return "", err } @@ -285,7 +293,7 @@ func ClearResolvedReferencesForField(field *model.Field, targetVarName string, i } else { innerOut += fmt.Sprintf("%sif %s != nil {\n", innerIndent, fieldAccessPrefix) } - concreteValueAccessor, err := buildIndexBasedFieldAccessor(field, targetVarName, indexVarFmt) + concreteValueAccessor, err := buildIndexBasedFieldAccessor(field, targetVarName) if err != nil { return "", err } @@ -355,19 +363,19 @@ func iterReferenceValues( switch ref.Shape.Type { case ("map"): - iterVarName := fmt.Sprintf(iterVarFmt, currentListDepth) - idxVarName := fmt.Sprintf(indexVarFmt, currentListDepth) + keyVarName := fmt.Sprintf(mapKeyVarFmt, currentListDepth) + valueVarName := fmt.Sprintf(mapValueVarFmt, currentListDepth) fieldAccessPrefix = fmt.Sprintf("%s.%s", fieldAccessPrefix, fp.At(fpDepth)) outPrefix += fmt.Sprintf("%sfor %s, %s := range %s {\n", indent, - lo.Ternary(shouldRenderIndexes, idxVarName, "_"), - iterVarName, + lo.Ternary(shouldRenderIndexes, keyVarName, "_"), + valueVarName, fieldAccessPrefix, ) outSuffix = fmt.Sprintf("%s}\n%s", indent, outSuffix) - fieldAccessPrefix = iterVarName + fieldAccessPrefix = valueVarName currentListDepth++ case ("structure"): fieldAccessPrefix = fmt.Sprintf("%s.%s", fieldAccessPrefix, fp.At(fpDepth)) @@ -406,17 +414,12 @@ func iterReferenceValues( } // buildNestedFieldAccessor generates Go code that accesses an inner struct, -// using slice indexes where necessary. -// -// `indexVarFmt` should be a format string that takes a single integer and -// returns the name of a variable which holds the index for the n-th parent -// slice. For example, f%didx will be used to create f0idx, f1idx, etc. for the -// parent slices in the accessors. +// using slice indexes or map keys where necessary. // // By default, this method will iterate through every field in the field path. // Supplying a `parentOffset` will only iterate through the first `fp.Size() - // parentOffset` number of paths. -func buildIndexBasedFieldAccessorWithOffset(field *model.Field, sourceVarName, indexVarFmt string, parentOffset int) (string, error) { +func buildIndexBasedFieldAccessorWithOffset(field *model.Field, sourceVarName string, parentOffset int) (string, error) { r := field.CRD fp := fieldpath.FromString(field.Path) @@ -444,7 +447,12 @@ func buildIndexBasedFieldAccessorWithOffset(field *model.Field, sourceVarName, i // This only applies for when there is no offset, since any offset > // 0 will cut off the initial field from the path if idx != (fp.Size()-1) && !isList { - indexList = fmt.Sprintf("[%s]", fmt.Sprintf(indexVarFmt, nestedFieldDepth)) + // Use map-specific key format for maps, otherwise use list index format + varFmt := indexVarFmt + if cur.ShapeRef.Shape.Type == "map" { + varFmt = mapKeyVarFmt + } + indexList = fmt.Sprintf("[%s]", fmt.Sprintf(varFmt, nestedFieldDepth)) nestedFieldDepth++ } } @@ -457,8 +465,8 @@ func buildIndexBasedFieldAccessorWithOffset(field *model.Field, sourceVarName, i // buildIndexBasedFieldAccessor calls buildNestedFieldAccessorWithOffset with an // offset of 0. -func buildIndexBasedFieldAccessor(field *model.Field, sourceVarName, indexVarFmt string) (string, error) { - return buildIndexBasedFieldAccessorWithOffset(field, sourceVarName, indexVarFmt, 0) +func buildIndexBasedFieldAccessor(field *model.Field, sourceVarName string) (string, error) { + return buildIndexBasedFieldAccessorWithOffset(field, sourceVarName, 0) } // getReferencedStateForField returns Go code that makes a call to diff --git a/pkg/generate/code/resource_reference_test.go b/pkg/generate/code/resource_reference_test.go index e0e616c4..6225e5ba 100644 --- a/pkg/generate/code/resource_reference_test.go +++ b/pkg/generate/code/resource_reference_test.go @@ -582,11 +582,11 @@ func Test_ResolveReferencesForField_SingleReference_WithinMap(t *testing.T) { require.NotNil(crd) expected := - ` for f0idx, f0iter := range ko.Spec.PhysicalTableMap { - if f0iter.CustomSQL != nil { - if f0iter.CustomSQL.DataSourceRef != nil && f0iter.CustomSQL.DataSourceRef.From != nil { + ` for f0key, f0value := range ko.Spec.PhysicalTableMap { + if f0value.CustomSQL != nil { + if f0value.CustomSQL.DataSourceRef != nil && f0value.CustomSQL.DataSourceRef.From != nil { hasReferences = true - arr := f0iter.CustomSQL.DataSourceRef.From + arr := f0value.CustomSQL.DataSourceRef.From if arr.Name == nil || *arr.Name == "" { return hasReferences, fmt.Errorf("provided resource reference is nil or empty: PhysicalTableMap.CustomSQL.DataSourceRef") } @@ -598,7 +598,7 @@ func Test_ResolveReferencesForField_SingleReference_WithinMap(t *testing.T) { if err := getReferencedResourceState_DataSet(ctx, apiReader, obj, *arr.Name, namespace); err != nil { return hasReferences, err } - ko.Spec.PhysicalTableMap[f0idx].CustomSQL.DataSourceARN = (*string)(obj.Status.ACKResourceMetadata.ARN) + ko.Spec.PhysicalTableMap[f0key].CustomSQL.DataSourceARN = (*string)(obj.Status.ACKResourceMetadata.ARN) } } } @@ -624,10 +624,10 @@ func Test_ClearResolvedReferencesForField_SingleReference_WithinMap(t *testing.T require.NotNil(crd) expected := - ` for f0idx, f0iter := range ko.Spec.PhysicalTableMap { - if f0iter.CustomSQL != nil { - if f0iter.CustomSQL.DataSourceRef != nil { - ko.Spec.PhysicalTableMap[f0idx].CustomSQL.DataSourceARN = nil + ` for f0key, f0value := range ko.Spec.PhysicalTableMap { + if f0value.CustomSQL != nil { + if f0value.CustomSQL.DataSourceRef != nil { + ko.Spec.PhysicalTableMap[f0key].CustomSQL.DataSourceARN = nil } } } @@ -653,9 +653,9 @@ func Test_ReferenceFieldsValidation_MapNestedReference(t *testing.T) { require.NotNil(crd) expected := - ` for _, f0iter := range ko.Spec.PhysicalTableMap { - if f0iter.CustomSQL != nil { - if f0iter.CustomSQL.DataSourceRef != nil && f0iter.CustomSQL.DataSourceARN != nil { + ` for _, f0value := range ko.Spec.PhysicalTableMap { + if f0value.CustomSQL != nil { + if f0value.CustomSQL.DataSourceRef != nil && f0value.CustomSQL.DataSourceARN != nil { return ackerr.ResourceReferenceAndIDNotSupportedFor("PhysicalTableMap.CustomSQL.DataSourceARN", "PhysicalTableMap.CustomSQL.DataSourceRef") } }