Skip to content

Commit ac02b64

Browse files
authored
Support custom list and map fields (#691)
Issue #, if available: Description of changes: Prior to this PR custom fields could only be applied to `structure` shapes. This PR adds support for adding custom fields to the elements of `list` and `map` types as well. - Add new addMemberShapRef function for applying a shape as a member of another shape - Add error checks for unsupported types - Add error check to prevent overriding an existing field - Add unit test cases to validate new behavior By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent 32cbc57 commit ac02b64

8 files changed

Lines changed: 636 additions & 4 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.25.0
44

55
require (
66
github.com/aws-controllers-k8s/pkg v0.0.23
7-
github.com/aws-controllers-k8s/runtime v0.58.0
7+
github.com/aws-controllers-k8s/runtime v0.58.1
88
github.com/aws/aws-sdk-go v1.49.0
99
github.com/aws/aws-sdk-go-v2 v1.32.7
1010
github.com/dlclark/regexp2 v1.10.0 // indirect

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:l
6464
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
6565
github.com/aws-controllers-k8s/pkg v0.0.23 h1:iqu8jKQUnyP/c6TiVcXySQYpkATui0iXFC5ax9x01oM=
6666
github.com/aws-controllers-k8s/pkg v0.0.23/go.mod h1:VvdjLWmR6IJ3KU8KByKiq/lJE8M+ur2piXysXKTGUS0=
67-
github.com/aws-controllers-k8s/runtime v0.58.0 h1:PbM3hsM5z66BSPTb2CBBElYmGF3EgSbUO88efLXxL78=
68-
github.com/aws-controllers-k8s/runtime v0.58.0/go.mod h1:WPlOiAG+xGySh1I076llz5g6nbuUeH62Qxh49hnieGo=
67+
github.com/aws-controllers-k8s/runtime v0.58.1 h1:FZso3Bwd2JIqglH6cpFurNAWkIyc4Z1qNXE7+t0wxdI=
68+
github.com/aws-controllers-k8s/runtime v0.58.1/go.mod h1:WPlOiAG+xGySh1I076llz5g6nbuUeH62Qxh49hnieGo=
6969
github.com/aws/aws-sdk-go v1.49.0 h1:g9BkW1fo9GqKfwg2+zCD+TW/D36Ux+vtfJ8guF4AYmY=
7070
github.com/aws/aws-sdk-go v1.49.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
7171
github.com/aws/aws-sdk-go-v2 v1.32.7 h1:ky5o35oENWi0JYWUZkB7WYvVPP+bcRF5/Iq7JWSb5Rw=

pkg/model/crd.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,8 +802,33 @@ func (crd *CRD) addCustomNestedFields(customNestedFields map[string]*ackgenconfi
802802
if err != nil {
803803
return fmt.Errorf("resource %q, custom nested field %q: %w", crd.Names.Original, customNestedField, err)
804804
}
805-
parentField.ShapeRef.Shape.MemberRefs[fieldName] = memberShapeRef
805+
//parentField.ShapeRef.Shape.MemberRefs[fieldName] = memberShapeRef
806+
if err := addMemberShapRef(parentField.ShapeRef, memberShapeRef, fieldName); err != nil {
807+
return fmt.Errorf("resource %q, custom nested field %q: %w", crd.Names.Original, customNestedField, err)
808+
}
809+
}
810+
return nil
811+
}
812+
813+
// addMemberShapRef injects a new member shape into the specified shape.
814+
// It returns an error if the shape type is unsupported or if a member with
815+
// the given field name already exists.
816+
func addMemberShapRef(shapeRef, memberShapeRef *awssdkmodel.ShapeRef, fieldName string) error {
817+
var memberRefs map[string]*awssdkmodel.ShapeRef
818+
switch shapeRef.Shape.Type {
819+
case "structure":
820+
memberRefs = shapeRef.Shape.MemberRefs
821+
case "list":
822+
memberRefs = shapeRef.Shape.MemberRef.Shape.MemberRefs
823+
case "map":
824+
memberRefs = shapeRef.Shape.ValueRef.Shape.MemberRefs
825+
default:
826+
return fmt.Errorf("unsupported shape type %q for adding member %q", shapeRef.Shape.Type, fieldName)
827+
}
828+
if _, exists := memberRefs[fieldName]; exists {
829+
return fmt.Errorf("member %q already exists in shape of type %q", fieldName, shapeRef.Shape.Type)
806830
}
831+
memberRefs[fieldName] = memberShapeRef
807832
return nil
808833
}
809834

pkg/model/crd_test.go

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package model
15+
16+
import (
17+
"testing"
18+
19+
awssdkmodel "github.com/aws-controllers-k8s/code-generator/pkg/api"
20+
"github.com/stretchr/testify/assert"
21+
"github.com/stretchr/testify/require"
22+
)
23+
24+
func TestAddMemberShapRef_Structure(t *testing.T) {
25+
assert := assert.New(t)
26+
require := require.New(t)
27+
28+
structShape := &awssdkmodel.Shape{
29+
Type: "structure",
30+
MemberRefs: map[string]*awssdkmodel.ShapeRef{},
31+
}
32+
shapeRef := &awssdkmodel.ShapeRef{
33+
Shape: structShape,
34+
}
35+
36+
memberShapeRef := &awssdkmodel.ShapeRef{
37+
Shape: &awssdkmodel.Shape{Type: "string"},
38+
}
39+
40+
err := addMemberShapRef(shapeRef, memberShapeRef, "NewField")
41+
42+
require.NoError(err)
43+
require.Contains(structShape.MemberRefs, "NewField")
44+
assert.Equal(memberShapeRef, structShape.MemberRefs["NewField"])
45+
}
46+
47+
func TestAddMemberShapRef_List(t *testing.T) {
48+
assert := assert.New(t)
49+
require := require.New(t)
50+
51+
innerStructShape := &awssdkmodel.Shape{
52+
Type: "structure",
53+
MemberRefs: map[string]*awssdkmodel.ShapeRef{},
54+
}
55+
listShape := &awssdkmodel.Shape{
56+
Type: "list",
57+
MemberRef: awssdkmodel.ShapeRef{
58+
Shape: innerStructShape,
59+
},
60+
}
61+
shapeRef := &awssdkmodel.ShapeRef{
62+
Shape: listShape,
63+
}
64+
65+
memberShapeRef := &awssdkmodel.ShapeRef{
66+
Shape: &awssdkmodel.Shape{Type: "integer"},
67+
}
68+
69+
err := addMemberShapRef(shapeRef, memberShapeRef, "Count")
70+
71+
require.NoError(err)
72+
require.Contains(innerStructShape.MemberRefs, "Count")
73+
assert.Equal(memberShapeRef, innerStructShape.MemberRefs["Count"])
74+
}
75+
76+
func TestAddMemberShapRef_Map(t *testing.T) {
77+
assert := assert.New(t)
78+
require := require.New(t)
79+
80+
valueStructShape := &awssdkmodel.Shape{
81+
Type: "structure",
82+
MemberRefs: map[string]*awssdkmodel.ShapeRef{},
83+
}
84+
mapShape := &awssdkmodel.Shape{
85+
Type: "map",
86+
ValueRef: awssdkmodel.ShapeRef{
87+
Shape: valueStructShape,
88+
},
89+
}
90+
shapeRef := &awssdkmodel.ShapeRef{
91+
Shape: mapShape,
92+
}
93+
94+
memberShapeRef := &awssdkmodel.ShapeRef{
95+
Shape: &awssdkmodel.Shape{Type: "boolean"},
96+
}
97+
98+
err := addMemberShapRef(shapeRef, memberShapeRef, "Enabled")
99+
100+
require.NoError(err)
101+
require.Contains(valueStructShape.MemberRefs, "Enabled")
102+
assert.Equal(memberShapeRef, valueStructShape.MemberRefs["Enabled"])
103+
}
104+
105+
func TestAddMemberShapRef_UnsupportedType(t *testing.T) {
106+
assert := assert.New(t)
107+
108+
scalarShape := &awssdkmodel.Shape{
109+
Type: "string",
110+
MemberRefs: map[string]*awssdkmodel.ShapeRef{},
111+
}
112+
shapeRef := &awssdkmodel.ShapeRef{
113+
Shape: scalarShape,
114+
}
115+
116+
memberShapeRef := &awssdkmodel.ShapeRef{
117+
Shape: &awssdkmodel.Shape{Type: "string"},
118+
}
119+
120+
err := addMemberShapRef(shapeRef, memberShapeRef, "ShouldNotExist")
121+
122+
assert.Error(err)
123+
assert.Contains(err.Error(), "unsupported shape type")
124+
assert.Contains(err.Error(), "string")
125+
assert.Contains(err.Error(), "ShouldNotExist")
126+
assert.NotContains(scalarShape.MemberRefs, "ShouldNotExist")
127+
}
128+
129+
func TestAddMemberShapRef_Structure_PreservesExistingMembers(t *testing.T) {
130+
assert := assert.New(t)
131+
require := require.New(t)
132+
133+
existingMemberRef := &awssdkmodel.ShapeRef{
134+
Shape: &awssdkmodel.Shape{Type: "string"},
135+
}
136+
structShape := &awssdkmodel.Shape{
137+
Type: "structure",
138+
MemberRefs: map[string]*awssdkmodel.ShapeRef{
139+
"ExistingField": existingMemberRef,
140+
},
141+
}
142+
shapeRef := &awssdkmodel.ShapeRef{
143+
Shape: structShape,
144+
}
145+
146+
newMemberShapeRef := &awssdkmodel.ShapeRef{
147+
Shape: &awssdkmodel.Shape{Type: "integer"},
148+
}
149+
150+
err := addMemberShapRef(shapeRef, newMemberShapeRef, "NewField")
151+
152+
require.NoError(err)
153+
require.Len(structShape.MemberRefs, 2)
154+
assert.Equal(existingMemberRef, structShape.MemberRefs["ExistingField"])
155+
assert.Equal(newMemberShapeRef, structShape.MemberRefs["NewField"])
156+
}
157+
158+
func TestAddMemberShapRef_List_PreservesExistingMembers(t *testing.T) {
159+
assert := assert.New(t)
160+
require := require.New(t)
161+
162+
existingMemberRef := &awssdkmodel.ShapeRef{
163+
Shape: &awssdkmodel.Shape{Type: "string"},
164+
}
165+
innerStructShape := &awssdkmodel.Shape{
166+
Type: "structure",
167+
MemberRefs: map[string]*awssdkmodel.ShapeRef{
168+
"ExistingField": existingMemberRef,
169+
},
170+
}
171+
listShape := &awssdkmodel.Shape{
172+
Type: "list",
173+
MemberRef: awssdkmodel.ShapeRef{
174+
Shape: innerStructShape,
175+
},
176+
}
177+
shapeRef := &awssdkmodel.ShapeRef{
178+
Shape: listShape,
179+
}
180+
181+
newMemberShapeRef := &awssdkmodel.ShapeRef{
182+
Shape: &awssdkmodel.Shape{Type: "boolean"},
183+
}
184+
185+
err := addMemberShapRef(shapeRef, newMemberShapeRef, "Active")
186+
187+
require.NoError(err)
188+
require.Len(innerStructShape.MemberRefs, 2)
189+
assert.Equal(existingMemberRef, innerStructShape.MemberRefs["ExistingField"])
190+
assert.Equal(newMemberShapeRef, innerStructShape.MemberRefs["Active"])
191+
}
192+
193+
func TestAddMemberShapRef_Map_PreservesExistingMembers(t *testing.T) {
194+
assert := assert.New(t)
195+
require := require.New(t)
196+
197+
existingMemberRef := &awssdkmodel.ShapeRef{
198+
Shape: &awssdkmodel.Shape{Type: "string"},
199+
}
200+
valueStructShape := &awssdkmodel.Shape{
201+
Type: "structure",
202+
MemberRefs: map[string]*awssdkmodel.ShapeRef{
203+
"ExistingField": existingMemberRef,
204+
},
205+
}
206+
mapShape := &awssdkmodel.Shape{
207+
Type: "map",
208+
ValueRef: awssdkmodel.ShapeRef{
209+
Shape: valueStructShape,
210+
},
211+
}
212+
shapeRef := &awssdkmodel.ShapeRef{
213+
Shape: mapShape,
214+
}
215+
216+
newMemberShapeRef := &awssdkmodel.ShapeRef{
217+
Shape: &awssdkmodel.Shape{Type: "integer"},
218+
}
219+
220+
err := addMemberShapRef(shapeRef, newMemberShapeRef, "Priority")
221+
222+
require.NoError(err)
223+
require.Len(valueStructShape.MemberRefs, 2)
224+
assert.Equal(existingMemberRef, valueStructShape.MemberRefs["ExistingField"])
225+
assert.Equal(newMemberShapeRef, valueStructShape.MemberRefs["Priority"])
226+
}
227+
228+
func TestAddMemberShapRef_DuplicateField_Structure(t *testing.T) {
229+
assert := assert.New(t)
230+
231+
oldMemberRef := &awssdkmodel.ShapeRef{
232+
Shape: &awssdkmodel.Shape{Type: "string"},
233+
}
234+
structShape := &awssdkmodel.Shape{
235+
Type: "structure",
236+
MemberRefs: map[string]*awssdkmodel.ShapeRef{
237+
"Field": oldMemberRef,
238+
},
239+
}
240+
shapeRef := &awssdkmodel.ShapeRef{
241+
Shape: structShape,
242+
}
243+
244+
newMemberRef := &awssdkmodel.ShapeRef{
245+
Shape: &awssdkmodel.Shape{Type: "integer"},
246+
}
247+
248+
err := addMemberShapRef(shapeRef, newMemberRef, "Field")
249+
250+
assert.Error(err)
251+
assert.Contains(err.Error(), "Field")
252+
assert.Contains(err.Error(), "already exists")
253+
// Original member should be unchanged
254+
assert.Equal(oldMemberRef, structShape.MemberRefs["Field"])
255+
}
256+
257+
func TestAddMemberShapRef_DuplicateField_List(t *testing.T) {
258+
assert := assert.New(t)
259+
260+
oldMemberRef := &awssdkmodel.ShapeRef{
261+
Shape: &awssdkmodel.Shape{Type: "string"},
262+
}
263+
innerStructShape := &awssdkmodel.Shape{
264+
Type: "structure",
265+
MemberRefs: map[string]*awssdkmodel.ShapeRef{
266+
"Field": oldMemberRef,
267+
},
268+
}
269+
listShape := &awssdkmodel.Shape{
270+
Type: "list",
271+
MemberRef: awssdkmodel.ShapeRef{
272+
Shape: innerStructShape,
273+
},
274+
}
275+
shapeRef := &awssdkmodel.ShapeRef{
276+
Shape: listShape,
277+
}
278+
279+
newMemberRef := &awssdkmodel.ShapeRef{
280+
Shape: &awssdkmodel.Shape{Type: "integer"},
281+
}
282+
283+
err := addMemberShapRef(shapeRef, newMemberRef, "Field")
284+
285+
assert.Error(err)
286+
assert.Contains(err.Error(), "Field")
287+
assert.Contains(err.Error(), "already exists")
288+
assert.Equal(oldMemberRef, innerStructShape.MemberRefs["Field"])
289+
}
290+
291+
func TestAddMemberShapRef_DuplicateField_Map(t *testing.T) {
292+
assert := assert.New(t)
293+
294+
oldMemberRef := &awssdkmodel.ShapeRef{
295+
Shape: &awssdkmodel.Shape{Type: "string"},
296+
}
297+
valueStructShape := &awssdkmodel.Shape{
298+
Type: "structure",
299+
MemberRefs: map[string]*awssdkmodel.ShapeRef{
300+
"Field": oldMemberRef,
301+
},
302+
}
303+
mapShape := &awssdkmodel.Shape{
304+
Type: "map",
305+
ValueRef: awssdkmodel.ShapeRef{
306+
Shape: valueStructShape,
307+
},
308+
}
309+
shapeRef := &awssdkmodel.ShapeRef{
310+
Shape: mapShape,
311+
}
312+
313+
newMemberRef := &awssdkmodel.ShapeRef{
314+
Shape: &awssdkmodel.Shape{Type: "integer"},
315+
}
316+
317+
err := addMemberShapRef(shapeRef, newMemberRef, "Field")
318+
319+
assert.Error(err)
320+
assert.Contains(err.Error(), "Field")
321+
assert.Contains(err.Error(), "already exists")
322+
assert.Equal(oldMemberRef, valueStructShape.MemberRefs["Field"])
323+
}

0 commit comments

Comments
 (0)