1 package genaip
2
3 import (
4 "strconv"
5 "strings"
6
7 "github.com/stoewer/go-strcase"
8 "go.einride.tech/aip/reflect/aipreflect"
9 "go.einride.tech/aip/resourcename"
10 "google.golang.org/genproto/googleapis/api/annotations"
11 "google.golang.org/protobuf/compiler/protogen"
12 "google.golang.org/protobuf/reflect/protoregistry"
13 )
14
15 type resourceNameCodeGenerator struct {
16 resource *annotations.ResourceDescriptor
17 file *protogen.File
18 files *protoregistry.Files
19 }
20
21 func (r resourceNameCodeGenerator) GenerateCode(g *protogen.GeneratedFile) error {
22 if len(r.resource.GetPattern()) == 0 {
23 return nil
24 }
25 hasMultiPattern := len(r.resource.GetPattern()) > 1
26 hasFutureMultiPattern := r.resource.GetHistory() == annotations.ResourceDescriptor_FUTURE_MULTI_PATTERN
27
28 if hasMultiPattern || hasFutureMultiPattern {
29 if err := r.generateMultiPatternInterface(g); err != nil {
30 return err
31 }
32 if err := r.generateMultiPatternParseMethod(g); err != nil {
33 return err
34 }
35 }
36
37
38 firstPattern := r.resource.GetPattern()[0]
39 shouldGenerateSinglePatternStruct := !hasFutureMultiPattern
40 firstSinglePatternStructName := r.SinglePatternStructName()
41 if shouldGenerateSinglePatternStruct {
42 if err := r.generatePatternStruct(
43 g, firstPattern, firstSinglePatternStructName,
44 ); err != nil {
45 return err
46 }
47 }
48
49
50
51 firstMultiPatternStructName := r.MultiPatternStructName(firstPattern)
52 equalMultiSinglePatternStructName := firstMultiPatternStructName == firstSinglePatternStructName
53 avoidGeneratingSameSingleStruct := shouldGenerateSinglePatternStruct && equalMultiSinglePatternStructName
54 if (hasMultiPattern || hasFutureMultiPattern) && !avoidGeneratingSameSingleStruct {
55 if err := r.generatePatternStruct(
56 g, firstPattern, firstMultiPatternStructName,
57 ); err != nil {
58 return err
59 }
60 }
61
62 for _, pattern := range r.resource.GetPattern()[1:] {
63 if err := r.generatePatternStruct(g, pattern, r.MultiPatternStructName(pattern)); err != nil {
64 return err
65 }
66 }
67 return nil
68 }
69
70 func (r resourceNameCodeGenerator) generatePatternStruct(
71 g *protogen.GeneratedFile,
72 pattern string,
73 typeName string,
74 ) error {
75 g.P()
76 g.P("type ", typeName, " struct {")
77 var sc resourcename.Scanner
78 sc.Init(pattern)
79 for sc.Scan() {
80 if sc.Segment().IsVariable() {
81 g.P(strcase.UpperCamelCase(string(sc.Segment().Literal())), " string")
82 }
83 }
84 g.P("}")
85 var parentConstructorsErr error
86 aipreflect.RangeParentResourcesInPackage(
87 r.files,
88 r.file.Desc.Package(),
89 pattern,
90 func(parent *annotations.ResourceDescriptor) bool {
91 if err := r.generateParentConstructorMethod(g, pattern, typeName, parent); err != nil {
92 parentConstructorsErr = err
93 return false
94 }
95 return true
96 },
97 )
98 if parentConstructorsErr != nil {
99 return parentConstructorsErr
100 }
101 if err := r.generateValidateMethod(g, pattern, typeName); err != nil {
102 return err
103 }
104 if err := r.generateContainsWildcardMethod(g, pattern, typeName); err != nil {
105 return err
106 }
107 if err := r.generateStringMethod(g, pattern, typeName); err != nil {
108 return err
109 }
110 if err := r.generateMarshalStringMethod(g, typeName); err != nil {
111 return err
112 }
113 if err := r.generateUnmarshalStringMethod(g, pattern, typeName); err != nil {
114 return err
115 }
116 var parentErr error
117 aipreflect.RangeParentResourcesInPackage(
118 r.files,
119 r.file.Desc.Package(),
120 pattern,
121 func(parent *annotations.ResourceDescriptor) bool {
122 if err := r.generateParentMethod(g, pattern, typeName, parent); err != nil {
123 parentErr = err
124 return false
125 }
126 return true
127 },
128 )
129 return parentErr
130 }
131
132 func (r *resourceNameCodeGenerator) generateParentConstructorMethod(
133 g *protogen.GeneratedFile,
134 pattern string,
135 typeName string,
136 parent *annotations.ResourceDescriptor,
137 ) error {
138 var parentPattern string
139 for _, parentCandidate := range parent.GetPattern() {
140 if resourcename.HasParent(pattern, parentCandidate) {
141 parentPattern = parentCandidate
142 break
143 }
144 }
145 if parentPattern == "" {
146 return nil
147 }
148 pg := resourceNameCodeGenerator{resource: parent, file: r.file, files: r.files}
149 parentStruct := pg.StructName(parentPattern)
150 g.P()
151 g.P("func (n ", parentStruct, ") ", typeName, "(")
152 var sc resourcename.Scanner
153 sc.Init(strings.TrimPrefix(pattern, parentPattern))
154 for sc.Scan() {
155 if sc.Segment().IsVariable() {
156 g.P(strcase.LowerCamelCase(string(sc.Segment().Literal())), " string,")
157 }
158 }
159 g.P(") ", typeName, " {")
160 g.P("return ", typeName, "{")
161 sc.Init(parentPattern)
162 for sc.Scan() {
163 if sc.Segment().IsVariable() {
164 g.P(
165 strcase.UpperCamelCase(string(sc.Segment().Literal())),
166 ": ",
167 "n.",
168 strcase.UpperCamelCase(string(sc.Segment().Literal())),
169 ",",
170 )
171 }
172 }
173 sc.Init(strings.TrimPrefix(pattern, parentPattern))
174 for sc.Scan() {
175 if sc.Segment().IsVariable() {
176 g.P(
177 strcase.UpperCamelCase(string(sc.Segment().Literal())),
178 ": ",
179 strcase.LowerCamelCase(string(sc.Segment().Literal())),
180 ",",
181 )
182 }
183 }
184 g.P("}")
185 g.P("}")
186 return nil
187 }
188
189 func (r *resourceNameCodeGenerator) generateParentMethod(
190 g *protogen.GeneratedFile,
191 pattern string,
192 typeName string,
193 parent *annotations.ResourceDescriptor,
194 ) error {
195 var parentPattern string
196 for _, parentCandidate := range parent.GetPattern() {
197 if resourcename.HasParent(pattern, parentCandidate) {
198 parentPattern = parentCandidate
199 break
200 }
201 }
202 if parentPattern == "" {
203 return nil
204 }
205 pg := resourceNameCodeGenerator{resource: parent, file: r.file, files: r.files}
206 parentStruct := pg.StructName(parentPattern)
207 g.P()
208 g.P("func (n ", typeName, ") ", parentStruct, "() ", parentStruct, " {")
209 g.P("return ", parentStruct, "{")
210 var sc resourcename.Scanner
211 sc.Init(parentPattern)
212 for sc.Scan() {
213 if sc.Segment().IsVariable() {
214 fieldName := strcase.UpperCamelCase(string(sc.Segment().Literal()))
215 g.P(fieldName, ": n.", fieldName, ",")
216 }
217 }
218 g.P("}")
219 g.P("}")
220 return nil
221 }
222
223 func (r resourceNameCodeGenerator) generateValidateMethod(
224 g *protogen.GeneratedFile,
225 pattern string,
226 typeName string,
227 ) error {
228 stringsIndexByte := g.QualifiedGoIdent(protogen.GoIdent{
229 GoImportPath: "strings",
230 GoName: "IndexByte",
231 })
232 fmtErrorf := g.QualifiedGoIdent(protogen.GoIdent{
233 GoImportPath: "fmt",
234 GoName: "Errorf",
235 })
236 g.P()
237 g.P("func (n ", typeName, ") Validate() error {")
238 var sc resourcename.Scanner
239 sc.Init(pattern)
240 for sc.Scan() {
241 if !sc.Segment().IsVariable() {
242 continue
243 }
244 value := string(sc.Segment().Literal())
245 g.P("if n.", strcase.UpperCamelCase(value), " == ", strconv.Quote(""), "{")
246 g.P("return ", fmtErrorf, `("`, value, `: empty")`)
247 g.P("}")
248 g.P("if ", stringsIndexByte, "(n.", strcase.UpperCamelCase(value), ", '/') != - 1 {")
249 g.P("return ", fmtErrorf, `("`, value, `: contains illegal character '/'")`)
250 g.P("}")
251 }
252 g.P("return nil")
253 g.P("}")
254 return nil
255 }
256
257 func (r resourceNameCodeGenerator) generateContainsWildcardMethod(
258 g *protogen.GeneratedFile,
259 pattern string,
260 typeName string,
261 ) error {
262 g.P()
263 g.P("func (n ", typeName, ") ContainsWildcard() bool {")
264 var returnStatement strings.Builder
265 returnStatement.WriteString("return false")
266 var sc resourcename.Scanner
267 sc.Init(pattern)
268 for sc.Scan() {
269 if !sc.Segment().IsVariable() {
270 continue
271 }
272 returnStatement.WriteString("|| n.")
273 returnStatement.WriteString(strcase.UpperCamelCase(string(sc.Segment().Literal())))
274 returnStatement.WriteString(" == ")
275 returnStatement.WriteString(strconv.Quote("-"))
276 }
277 g.P(returnStatement.String())
278 g.P("}")
279 return nil
280 }
281
282 func (r *resourceNameCodeGenerator) generateStringMethod(
283 g *protogen.GeneratedFile,
284 pattern string,
285 typeName string,
286 ) error {
287 resourcenameSprint := g.QualifiedGoIdent(protogen.GoIdent{
288 GoImportPath: "go.einride.tech/aip/resourcename",
289 GoName: "Sprint",
290 })
291 g.P()
292 g.P("func (n ", typeName, ") String() string {")
293 g.P("return ", resourcenameSprint, "(")
294 g.P(strconv.Quote(pattern), ",")
295 var sc resourcename.Scanner
296 sc.Init(pattern)
297 for sc.Scan() {
298 if sc.Segment().IsVariable() {
299 g.P("n.", strcase.UpperCamelCase(string(sc.Segment().Literal())), ",")
300 }
301 }
302 g.P(")")
303 g.P("}")
304 return nil
305 }
306
307 func (r resourceNameCodeGenerator) generateMarshalStringMethod(
308 g *protogen.GeneratedFile,
309 typeName string,
310 ) error {
311 g.P()
312 g.P("func (n ", typeName, ") MarshalString() (string, error) {")
313 g.P("if err := n.Validate(); err != nil {")
314 g.P("return ", strconv.Quote(""), ", err")
315 g.P("}")
316 g.P("return n.String(), nil")
317 g.P("}")
318 return nil
319 }
320
321 func (r resourceNameCodeGenerator) generateUnmarshalStringMethod(
322 g *protogen.GeneratedFile,
323 pattern string,
324 typeName string,
325 ) error {
326 resourcenameSscan := g.QualifiedGoIdent(protogen.GoIdent{
327 GoImportPath: "go.einride.tech/aip/resourcename",
328 GoName: "Sscan",
329 })
330 g.P()
331 g.P("func (n *", typeName, ") UnmarshalString(name string) error {")
332 g.P("err := ", resourcenameSscan, "(")
333 g.P("name,")
334 g.P(strconv.Quote(pattern), ",")
335 var sc resourcename.Scanner
336 sc.Init(pattern)
337 for sc.Scan() {
338 if sc.Segment().IsVariable() {
339 g.P("&n.", strcase.UpperCamelCase(string(sc.Segment().Literal())), ",")
340 }
341 }
342 g.P(")")
343 g.P("if err != nil {")
344 g.P("return err")
345 g.P("}")
346 g.P("return n.Validate()")
347 g.P("}")
348 return nil
349 }
350
351 func (r resourceNameCodeGenerator) generateMultiPatternInterface(g *protogen.GeneratedFile) error {
352 fmtStringer := g.QualifiedGoIdent(protogen.GoIdent{
353 GoImportPath: "fmt",
354 GoName: "Stringer",
355 })
356 g.P()
357 g.P("type ", r.MultiPatternInterfaceName(), " interface {")
358 g.P(fmtStringer)
359 g.P("MarshalString() (string, error)")
360 g.P("ContainsWildcard() bool")
361 g.P("}")
362 return nil
363 }
364
365 func (r *resourceNameCodeGenerator) generateMultiPatternParseMethod(g *protogen.GeneratedFile) error {
366 resourcenameMatch := g.QualifiedGoIdent(protogen.GoIdent{
367 GoImportPath: "go.einride.tech/aip/resourcename",
368 GoName: "Match",
369 })
370 fmtErrorf := g.QualifiedGoIdent(protogen.GoIdent{
371 GoImportPath: "fmt",
372 GoName: "Errorf",
373 })
374 g.P()
375 g.P("func Parse", r.MultiPatternInterfaceName(), "(name string) (", r.MultiPatternInterfaceName(), ", error) {")
376 g.P("switch {")
377 for _, pattern := range r.resource.GetPattern() {
378 g.P("case ", resourcenameMatch, "(", strconv.Quote(pattern), ", name):")
379 g.P("var result ", r.MultiPatternStructName(pattern))
380 g.P("return &result, result.UnmarshalString(name)")
381 }
382 g.P("default:")
383 g.P("return nil, ", fmtErrorf, `("no matching pattern")`)
384 g.P("}")
385 g.P("}")
386 return nil
387 }
388
389 func (r *resourceNameCodeGenerator) SinglePatternStructName() string {
390 return aipreflect.ResourceType(r.resource.GetType()).Type() + "ResourceName"
391 }
392
393 func (r *resourceNameCodeGenerator) StructName(pattern string) string {
394 if r.resource.GetHistory() == annotations.ResourceDescriptor_FUTURE_MULTI_PATTERN || len(r.resource.GetPattern()) > 1 {
395 return r.MultiPatternStructName(pattern)
396 }
397 if r.resource.GetPattern()[0] == pattern {
398 return r.SinglePatternStructName()
399 }
400 return r.MultiPatternStructName(pattern)
401 }
402
403 func (r *resourceNameCodeGenerator) MultiPatternStructName(pattern string) string {
404 var result strings.Builder
405 var sc resourcename.Scanner
406 sc.Init(pattern)
407 for sc.Scan() {
408 if !sc.Segment().IsVariable() && string(sc.Segment().Literal()) != r.resource.GetPlural() {
409 _, _ = result.WriteString(strcase.UpperCamelCase(string(sc.Segment().Literal())))
410 }
411 }
412 _, _ = result.WriteString(r.SinglePatternStructName())
413 return result.String()
414 }
415
416 func (r *resourceNameCodeGenerator) MultiPatternInterfaceName() string {
417 return aipreflect.ResourceType(r.resource.GetType()).Type() + "MultiPatternResourceName"
418 }
419
View as plain text