Skip to content

Commit ad3bd3d

Browse files
authored
Merge pull request #65 from A-Hilaly/equal
Add code generation for type comparison
2 parents 5d545b3 + 697d833 commit ad3bd3d

File tree

4 files changed

+479
-0
lines changed

4 files changed

+479
-0
lines changed

pkg/generate/ack/controller.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@ var (
9696
"GoCodeCompare": func(r *ackmodel.CRD, deltaVarName string, sourceVarName string, targetVarName string, indentLevel int) string {
9797
return code.CompareResource(r.Config(), r, deltaVarName, sourceVarName, targetVarName, indentLevel)
9898
},
99+
"GoCodeIsEqual": func(typeDef *ackmodel.TypeDef, sourceVarName string, targetVarName string, indentLevel int) string {
100+
return code.IsEqualTypeDef(typeDef, sourceVarName, targetVarName, indentLevel)
101+
},
99102
"Empty": func(subject string) bool {
100103
return strings.TrimSpace(subject) == ""
101104
},
@@ -169,6 +172,20 @@ func Controller(
169172
return nil, err
170173
}
171174

175+
typeDefs, err := g.GetTypeDefs()
176+
if err != nil {
177+
return nil, err
178+
}
179+
equalVars := templateCompareVars{
180+
metaVars,
181+
typeDefs,
182+
}
183+
184+
// Next add the template for pkg/compare/struct.go file
185+
if err = ts.Add("pkg/compare/struct.go", "pkg/compare/struct.go.tpl", equalVars); err != nil {
186+
return nil, err
187+
}
188+
172189
// Next add the template for pkg/version/version.go file
173190
if err = ts.Add("pkg/version/version.go", "pkg/version/version.go.tpl", nil); err != nil {
174191
return nil, err
@@ -203,3 +220,10 @@ type templateCmdVars struct {
203220
templateset.MetaVars
204221
SnakeCasedCRDNames []string
205222
}
223+
224+
// templateCompareVars contains template variables for the template that outputs Go
225+
// code for equality/comparison helper functions.
226+
type templateCompareVars struct {
227+
templateset.MetaVars
228+
TypeDefs []*ackmodel.TypeDef
229+
}

pkg/generate/code/equal.go

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
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 code
15+
16+
import (
17+
"fmt"
18+
"sort"
19+
"strings"
20+
21+
awssdkmodel "github.com/aws/aws-sdk-go/private/model/api"
22+
23+
ackmodel "github.com/aws-controllers-k8s/code-generator/pkg/model"
24+
"github.com/aws-controllers-k8s/code-generator/pkg/names"
25+
)
26+
27+
// IsEqualTypeDef returns Go code that checks the equality of two struct types.
28+
// The generated code returns false at the first spotted difference.
29+
//
30+
// Output code will look something like this:
31+
//
32+
// if ackcompare.HasNilDifference(a.CreatedAt, b.CreatedAt) {
33+
// return false
34+
// } else if a.CreatedAt != nil && b.CreatedAt != nil {
35+
// if *a.CreatedAt != *b.CreatedAt {
36+
// return false
37+
// }
38+
// }
39+
// if ackcompare.HasNilDifference(a.EncryptionConfiguration, b.EncryptionConfiguration) {
40+
// return false
41+
// } else if a.EncryptionConfiguration != nil && b.EncryptionConfiguration != nil {
42+
// if !IsEqualEncryptionConfiguration(a.EncryptionConfiguration, b.EncryptionConfiguration) {
43+
// return false
44+
// }
45+
// }
46+
func IsEqualTypeDef(
47+
typeDef *ackmodel.TypeDef,
48+
// String representing the name of the variable that represents the first
49+
// object under comparison. This will typically be something like "a"
50+
firstVarName string,
51+
// String representing the name of the variable that represents the second
52+
// object under comparison. This will typically be something like "b".
53+
secondVarName string,
54+
// Number of levels of indentation to use
55+
indentLevel int,
56+
) string {
57+
out := ""
58+
59+
// We need a deterministic order to loop over attributes
60+
attrsNames := []string{}
61+
for attrName := range typeDef.Attrs {
62+
attrsNames = append(attrsNames, attrName)
63+
}
64+
sort.Strings(attrsNames)
65+
66+
// For each attribute generate the equality check logic
67+
for _, attrName := range attrsNames {
68+
attr := typeDef.Attrs[attrName]
69+
out += isEqualField(attrName, attr.Shape, firstVarName, secondVarName, indentLevel)
70+
}
71+
return out
72+
}
73+
74+
// isEqualField outputs Go code that compares two similar fields from different objects.
75+
// The generated code first checks the nility of the fields and then procede on comparing
76+
// their value.
77+
//
78+
// Output code will look something like this:
79+
//
80+
// if ackcompare.HasNilDifference(a.RepositoryName, b.RepositoryName) {
81+
// return false
82+
// } else if a.RepositoryName != nil && b.RepositoryName != nil {
83+
// if *a.RepositoryName != *b.RepositoryName {
84+
// return false
85+
// }
86+
// }
87+
func isEqualField(
88+
// fieldName is the field to generate the comparison logic for
89+
fieldName string,
90+
shape *awssdkmodel.Shape,
91+
// String representing the name of the variable that represents the object
92+
// under comparison.
93+
firstVarName string,
94+
// String representing the name of the variable that represents the second
95+
// object under comparison.
96+
secondVarName string,
97+
// Number of levels of indentation to use
98+
indentLevel int,
99+
) string {
100+
out := ""
101+
102+
memberNames := names.New(fieldName)
103+
memberNameClean := memberNames.Camel
104+
firstAdaptedVarName := firstVarName + "." + memberNameClean
105+
secondAdaptedVarName := secondVarName + "." + memberNameClean
106+
107+
nilCode := isEqualNil(
108+
shape,
109+
firstAdaptedVarName,
110+
secondAdaptedVarName,
111+
indentLevel,
112+
)
113+
if nilCode != "" {
114+
out += fmt.Sprintf(
115+
"%s else if %s != nil && %s != nil {\n",
116+
nilCode, firstAdaptedVarName, secondAdaptedVarName,
117+
)
118+
indentLevel++
119+
} else {
120+
out += "\n"
121+
}
122+
123+
indent := strings.Repeat("\t", indentLevel)
124+
125+
switch shape.Type {
126+
case "structure":
127+
// We just re-use the generated functions to compare field of `struct` type
128+
out += fmt.Sprintf(
129+
"%sif !IsEqual%s(%s, %s) {\n",
130+
indent,
131+
shape.ShapeName,
132+
firstAdaptedVarName,
133+
secondAdaptedVarName,
134+
)
135+
out += fmt.Sprintf("%s\treturn false\n", indent)
136+
out += fmt.Sprintf("%s}\n", indent)
137+
138+
case "list":
139+
out += isEqualSlice(
140+
shape,
141+
firstAdaptedVarName,
142+
secondAdaptedVarName,
143+
indentLevel,
144+
)
145+
case "map":
146+
out += isEqualMap(
147+
shape,
148+
firstAdaptedVarName,
149+
secondAdaptedVarName,
150+
indentLevel,
151+
)
152+
default:
153+
out += fmt.Sprintf(
154+
"%sif *%s != *%s {\n",
155+
indent,
156+
firstAdaptedVarName,
157+
secondAdaptedVarName,
158+
)
159+
out += fmt.Sprintf("%s\treturn false\n", indent)
160+
out += fmt.Sprintf("%s}\n", indent)
161+
}
162+
163+
if nilCode != "" {
164+
indentLevel--
165+
indent := strings.Repeat("\t", indentLevel)
166+
out += fmt.Sprintf("%s}\n", indent)
167+
}
168+
return out
169+
}
170+
171+
// isEqualNil outputs Go code that compares pointer to struct types for nullability,
172+
// if there is a nil difference the code return false.
173+
//
174+
// Output code will look something like this:
175+
//
176+
// if ackcompare.HasNilDifference(a.DataTraceEnabled, b.DataTraceEnabled) {
177+
// return false
178+
// }
179+
func isEqualNil(
180+
// struct describing the SDK type of the field being compared
181+
shape *awssdkmodel.Shape,
182+
// String representing the name of the variable that represents the object
183+
// under comparison.
184+
firstVarName string,
185+
// String representing the name of the variable that represents the second
186+
// object under comparison.
187+
secondVarName string,
188+
// Number of levels of indentation to use
189+
indentLevel int,
190+
) string {
191+
out := ""
192+
indent := strings.Repeat("\t", indentLevel)
193+
194+
switch shape.Type {
195+
case "list", "blob":
196+
// for slice types, there is no nilability test. Instead, the normal
197+
// value test checks length of slices.
198+
return ""
199+
case "boolean", "string", "character", "byte", "short", "integer", "long",
200+
"float", "double", "timestamp", "structure", "map", "jsonvalue":
201+
out += fmt.Sprintf(
202+
"%sif ackcompare.HasNilDifference(%s, %s) {\n",
203+
indent, firstVarName, secondVarName,
204+
)
205+
default:
206+
panic("Unsupported shape type in generate.code.compareNil: " + shape.Type)
207+
}
208+
// return false
209+
out += fmt.Sprintf(
210+
"%s\treturn false\n",
211+
indent,
212+
)
213+
// }
214+
out += fmt.Sprintf(
215+
"%s}", indent,
216+
)
217+
return out
218+
}
219+
220+
// isEqualSlice outputs Go code that compares two Go slices of the the same value type.
221+
// at the first spotted difference the code return false, return true if the element
222+
// are equal.
223+
// TODO(hilalymh): Modify this function to be configurable: Ordered/Non-Ordered/ExactCountRepeatedElements
224+
func isEqualSlice(
225+
// struct describing the SDK type of the field being compared
226+
shape *awssdkmodel.Shape,
227+
// String representing the name of the variable that represents the object
228+
// under comparison.
229+
firstVarName string,
230+
// String representing the name of the variable that represents the second
231+
// object under comparison.
232+
secondVarName string,
233+
// Number of levels of indentation to use
234+
indentLevel int,
235+
) string {
236+
indent := strings.Repeat("\t", indentLevel)
237+
out := fmt.Sprintf(
238+
"%s//TODO(a-hilaly): equality check for slices\n", indent,
239+
)
240+
return out
241+
}
242+
243+
// isEqualMap outputs Go code that compares two Go maps of the the same value type.
244+
// at the first spotted difference the code return false, return true if the maps
245+
// key/values are equal.
246+
func isEqualMap(
247+
// struct describing the SDK type of the field being compared
248+
shape *awssdkmodel.Shape,
249+
// String representing the name of the variable that represents the object
250+
// under comparison.
251+
firstVarName string,
252+
// String representing the name of the variable that represents the second
253+
// object under comparison.
254+
secondVarName string,
255+
// Number of levels of indentation to use
256+
indentLevel int,
257+
) string {
258+
indent := strings.Repeat("\t", indentLevel)
259+
out := fmt.Sprintf(
260+
"%s//TODO(a-hilaly): equality check for maps\n", indent,
261+
)
262+
return out
263+
}

0 commit comments

Comments
 (0)