1
16
17 package generators
18
19 import (
20 "bytes"
21 "encoding/json"
22 "fmt"
23 "io"
24 "path"
25 "reflect"
26 "regexp"
27 "sort"
28 "strings"
29
30 "k8s.io/gengo/v2"
31 "k8s.io/gengo/v2/generator"
32 "k8s.io/gengo/v2/namer"
33 "k8s.io/gengo/v2/types"
34 openapi "k8s.io/kube-openapi/pkg/common"
35 "k8s.io/kube-openapi/pkg/validation/spec"
36
37 "k8s.io/klog/v2"
38 )
39
40
41 const tagName = "k8s:openapi-gen"
42 const markerPrefix = "+k8s:validation:"
43 const tagOptional = "optional"
44 const tagRequired = "required"
45 const tagDefault = "default"
46
47
48 const (
49 tagValueTrue = "true"
50 tagValueFalse = "false"
51 )
52
53
54
55 var tempPatchTags = [...]string{
56 "patchMergeKey",
57 "patchStrategy",
58 }
59
60 func getOpenAPITagValue(comments []string) []string {
61 return gengo.ExtractCommentTags("+", comments)[tagName]
62 }
63
64 func getSingleTagsValue(comments []string, tag string) (string, error) {
65 tags, ok := gengo.ExtractCommentTags("+", comments)[tag]
66 if !ok || len(tags) == 0 {
67 return "", nil
68 }
69 if len(tags) > 1 {
70 return "", fmt.Errorf("multiple values are not allowed for tag %s", tag)
71 }
72 return tags[0], nil
73 }
74
75 func hasOpenAPITagValue(comments []string, value string) bool {
76 tagValues := getOpenAPITagValue(comments)
77 for _, val := range tagValues {
78 if val == value {
79 return true
80 }
81 }
82 return false
83 }
84
85
86
87
88 func isOptional(m *types.Member) (bool, error) {
89 hasOptionalCommentTag := gengo.ExtractCommentTags(
90 "+", m.CommentLines)[tagOptional] != nil
91 hasRequiredCommentTag := gengo.ExtractCommentTags(
92 "+", m.CommentLines)[tagRequired] != nil
93 if hasOptionalCommentTag && hasRequiredCommentTag {
94 return false, fmt.Errorf("member %s cannot be both optional and required", m.Name)
95 } else if hasRequiredCommentTag {
96 return false, nil
97 } else if hasOptionalCommentTag {
98 return true, nil
99 }
100
101
102
103 return strings.Contains(reflect.StructTag(m.Tags).Get("json"), "omitempty"), nil
104 }
105
106 func apiTypeFilterFunc(c *generator.Context, t *types.Type) bool {
107
108 if strings.HasPrefix(t.Name.Name, "codecSelfer") {
109 return false
110 }
111 pkg := c.Universe.Package(t.Name.Package)
112 if hasOpenAPITagValue(pkg.Comments, tagValueTrue) {
113 return !hasOpenAPITagValue(t.CommentLines, tagValueFalse)
114 }
115 if hasOpenAPITagValue(t.CommentLines, tagValueTrue) {
116 return true
117 }
118 return false
119 }
120
121 const (
122 specPackagePath = "k8s.io/kube-openapi/pkg/validation/spec"
123 openAPICommonPackagePath = "k8s.io/kube-openapi/pkg/common"
124 )
125
126
127 type openAPIGen struct {
128 generator.GoGenerator
129
130 targetPackage string
131 imports namer.ImportTracker
132 }
133
134 func newOpenAPIGen(outputFilename string, targetPackage string) generator.Generator {
135 return &openAPIGen{
136 GoGenerator: generator.GoGenerator{
137 OutputFilename: outputFilename,
138 },
139 imports: generator.NewImportTrackerForPackage(targetPackage),
140 targetPackage: targetPackage,
141 }
142 }
143
144 const nameTmpl = "schema_$.type|private$"
145
146 func (g *openAPIGen) Namers(c *generator.Context) namer.NameSystems {
147
148 return namer.NameSystems{
149 "raw": namer.NewRawNamer(g.targetPackage, g.imports),
150 "private": &namer.NameStrategy{
151 Join: func(pre string, in []string, post string) string {
152 return strings.Join(in, "_")
153 },
154 PrependPackageNames: 4,
155 },
156 }
157 }
158
159 func (g *openAPIGen) Imports(c *generator.Context) []string {
160 importLines := []string{}
161 for _, singleImport := range g.imports.ImportLines() {
162 importLines = append(importLines, singleImport)
163 }
164 return importLines
165 }
166
167 func argsFromType(t *types.Type) generator.Args {
168 return generator.Args{
169 "type": t,
170 "ReferenceCallback": types.Ref(openAPICommonPackagePath, "ReferenceCallback"),
171 "OpenAPIDefinition": types.Ref(openAPICommonPackagePath, "OpenAPIDefinition"),
172 "SpecSchemaType": types.Ref(specPackagePath, "Schema"),
173 }
174 }
175
176 func (g *openAPIGen) Init(c *generator.Context, w io.Writer) error {
177 sw := generator.NewSnippetWriter(w, c, "$", "$")
178 sw.Do("func GetOpenAPIDefinitions(ref $.ReferenceCallback|raw$) map[string]$.OpenAPIDefinition|raw$ {\n", argsFromType(nil))
179 sw.Do("return map[string]$.OpenAPIDefinition|raw${\n", argsFromType(nil))
180
181 for _, t := range c.Order {
182 err := newOpenAPITypeWriter(sw, c).generateCall(t)
183 if err != nil {
184 return err
185 }
186 }
187
188 sw.Do("}\n", nil)
189 sw.Do("}\n\n", nil)
190
191 return sw.Error()
192 }
193
194 func (g *openAPIGen) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
195 klog.V(5).Infof("generating for type %v", t)
196 sw := generator.NewSnippetWriter(w, c, "$", "$")
197 err := newOpenAPITypeWriter(sw, c).generate(t)
198 if err != nil {
199 return err
200 }
201 return sw.Error()
202 }
203
204 func getJsonTags(m *types.Member) []string {
205 jsonTag := reflect.StructTag(m.Tags).Get("json")
206 if jsonTag == "" {
207 return []string{}
208 }
209 return strings.Split(jsonTag, ",")
210 }
211
212 func getReferableName(m *types.Member) string {
213 jsonTags := getJsonTags(m)
214 if len(jsonTags) > 0 {
215 if jsonTags[0] == "-" {
216 return ""
217 } else {
218 return jsonTags[0]
219 }
220 } else {
221 return m.Name
222 }
223 }
224
225 func shouldInlineMembers(m *types.Member) bool {
226 jsonTags := getJsonTags(m)
227 return len(jsonTags) > 1 && jsonTags[1] == "inline"
228 }
229
230 type openAPITypeWriter struct {
231 *generator.SnippetWriter
232 context *generator.Context
233 refTypes map[string]*types.Type
234 enumContext *enumContext
235 GetDefinitionInterface *types.Type
236 }
237
238 func newOpenAPITypeWriter(sw *generator.SnippetWriter, c *generator.Context) openAPITypeWriter {
239 return openAPITypeWriter{
240 SnippetWriter: sw,
241 context: c,
242 refTypes: map[string]*types.Type{},
243 enumContext: newEnumContext(c),
244 }
245 }
246
247 func methodReturnsValue(mt *types.Type, pkg, name string) bool {
248 if len(mt.Signature.Parameters) != 0 || len(mt.Signature.Results) != 1 {
249 return false
250 }
251 r := mt.Signature.Results[0]
252 return r.Name.Name == name && r.Name.Package == pkg
253 }
254
255 func hasOpenAPIV3DefinitionMethod(t *types.Type) bool {
256 for mn, mt := range t.Methods {
257 if mn != "OpenAPIV3Definition" {
258 continue
259 }
260 return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
261 }
262 return false
263 }
264
265 func hasOpenAPIDefinitionMethod(t *types.Type) bool {
266 for mn, mt := range t.Methods {
267 if mn != "OpenAPIDefinition" {
268 continue
269 }
270 return methodReturnsValue(mt, openAPICommonPackagePath, "OpenAPIDefinition")
271 }
272 return false
273 }
274
275 func hasOpenAPIDefinitionMethods(t *types.Type) bool {
276 var hasSchemaTypeMethod, hasOpenAPISchemaFormat bool
277 for mn, mt := range t.Methods {
278 switch mn {
279 case "OpenAPISchemaType":
280 hasSchemaTypeMethod = methodReturnsValue(mt, "", "[]string")
281 case "OpenAPISchemaFormat":
282 hasOpenAPISchemaFormat = methodReturnsValue(mt, "", "string")
283 }
284 }
285 return hasSchemaTypeMethod && hasOpenAPISchemaFormat
286 }
287
288 func hasOpenAPIV3OneOfMethod(t *types.Type) bool {
289 for mn, mt := range t.Methods {
290 if mn != "OpenAPIV3OneOfTypes" {
291 continue
292 }
293 return methodReturnsValue(mt, "", "[]string")
294 }
295 return false
296 }
297
298
299 func typeShortName(t *types.Type) string {
300
301 return path.Base(t.Name.Package) + "." + t.Name.Name
302 }
303
304 func (g openAPITypeWriter) generateMembers(t *types.Type, required []string) ([]string, error) {
305 var err error
306 for t.Kind == types.Pointer {
307 t = t.Elem
308 }
309 for _, m := range t.Members {
310 if hasOpenAPITagValue(m.CommentLines, tagValueFalse) {
311 continue
312 }
313 if shouldInlineMembers(&m) {
314 required, err = g.generateMembers(m.Type, required)
315 if err != nil {
316 return required, err
317 }
318 continue
319 }
320 name := getReferableName(&m)
321 if name == "" {
322 continue
323 }
324 if isOptional, err := isOptional(&m); err != nil {
325 klog.Errorf("Error when generating: %v, %v\n", name, m)
326 return required, err
327 } else if !isOptional {
328 required = append(required, name)
329 }
330 if err = g.generateProperty(&m, t); err != nil {
331 klog.Errorf("Error when generating: %v, %v\n", name, m)
332 return required, err
333 }
334 }
335 return required, nil
336 }
337
338 func (g openAPITypeWriter) generateCall(t *types.Type) error {
339
340 switch t.Kind {
341 case types.Struct:
342 args := argsFromType(t)
343 g.Do("\"$.$\": ", t.Name)
344
345 hasV2Definition := hasOpenAPIDefinitionMethod(t)
346 hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
347 hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
348
349 switch {
350 case hasV2DefinitionTypeAndFormat:
351 g.Do(nameTmpl+"(ref),\n", args)
352 case hasV2Definition && hasV3Definition:
353 g.Do("common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.type|raw${}.OpenAPIDefinition()),\n", args)
354 case hasV2Definition:
355 g.Do("$.type|raw${}.OpenAPIDefinition(),\n", args)
356 case hasV3Definition:
357 g.Do("$.type|raw${}.OpenAPIV3Definition(),\n", args)
358 default:
359 g.Do(nameTmpl+"(ref),\n", args)
360 }
361 }
362 return g.Error()
363 }
364
365 func (g openAPITypeWriter) generateValueValidations(vs *spec.SchemaProps) error {
366
367 if vs == nil {
368 return nil
369 }
370 args := generator.Args{
371 "ptrTo": &types.Type{
372 Name: types.Name{
373 Package: "k8s.io/utils/ptr",
374 Name: "To",
375 }},
376 "spec": vs,
377 }
378 if vs.Minimum != nil {
379 g.Do("Minimum: $.ptrTo|raw$[float64]($.spec.Minimum$),\n", args)
380 }
381 if vs.Maximum != nil {
382 g.Do("Maximum: $.ptrTo|raw$[float64]($.spec.Maximum$),\n", args)
383 }
384 if vs.ExclusiveMinimum {
385 g.Do("ExclusiveMinimum: true,\n", args)
386 }
387 if vs.ExclusiveMaximum {
388 g.Do("ExclusiveMaximum: true,\n", args)
389 }
390 if vs.MinLength != nil {
391 g.Do("MinLength: $.ptrTo|raw$[int64]($.spec.MinLength$),\n", args)
392 }
393 if vs.MaxLength != nil {
394 g.Do("MaxLength: $.ptrTo|raw$[int64]($.spec.MaxLength$),\n", args)
395 }
396
397 if vs.MinProperties != nil {
398 g.Do("MinProperties: $.ptrTo|raw$[int64]($.spec.MinProperties$),\n", args)
399 }
400 if vs.MaxProperties != nil {
401 g.Do("MaxProperties: $.ptrTo|raw$[int64]($.spec.MaxProperties$),\n", args)
402 }
403 if len(vs.Pattern) > 0 {
404 p, err := json.Marshal(vs.Pattern)
405 if err != nil {
406 return err
407 }
408 g.Do("Pattern: $.$,\n", string(p))
409 }
410 if vs.MultipleOf != nil {
411 g.Do("MultipleOf: $.ptrTo|raw$[float64]($.spec.MultipleOf$),\n", args)
412 }
413 if vs.MinItems != nil {
414 g.Do("MinItems: $.ptrTo|raw$[int64]($.spec.MinItems$),\n", args)
415 }
416 if vs.MaxItems != nil {
417 g.Do("MaxItems: $.ptrTo|raw$[int64]($.spec.MaxItems$),\n", args)
418 }
419 if vs.UniqueItems {
420 g.Do("UniqueItems: true,\n", nil)
421 }
422
423 return nil
424 }
425
426 func (g openAPITypeWriter) generate(t *types.Type) error {
427
428 switch t.Kind {
429 case types.Struct:
430 validationSchema, err := ParseCommentTags(t, t.CommentLines, markerPrefix)
431 if err != nil {
432 return err
433 }
434
435 hasV2Definition := hasOpenAPIDefinitionMethod(t)
436 hasV2DefinitionTypeAndFormat := hasOpenAPIDefinitionMethods(t)
437 hasV3OneOfTypes := hasOpenAPIV3OneOfMethod(t)
438 hasV3Definition := hasOpenAPIV3DefinitionMethod(t)
439
440 if hasV2Definition || (hasV3Definition && !hasV2DefinitionTypeAndFormat) {
441
442 return nil
443 }
444
445 args := argsFromType(t)
446 g.Do("func "+nameTmpl+"(ref $.ReferenceCallback|raw$) $.OpenAPIDefinition|raw$ {\n", args)
447 switch {
448 case hasV2DefinitionTypeAndFormat && hasV3Definition:
449 g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.type|raw${}.OpenAPIV3Definition(), $.OpenAPIDefinition|raw${\n"+
450 "Schema: spec.Schema{\n"+
451 "SchemaProps: spec.SchemaProps{\n", args)
452 g.generateDescription(t.CommentLines)
453 g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
454 "Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
455 err = g.generateValueValidations(&validationSchema.SchemaProps)
456 if err != nil {
457 return err
458 }
459 g.Do("},\n", nil)
460 if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
461 return err
462 }
463 g.Do("},\n", nil)
464 g.Do("})\n}\n\n", args)
465 return nil
466 case hasV2DefinitionTypeAndFormat && hasV3OneOfTypes:
467
468 g.Do("return common.EmbedOpenAPIDefinitionIntoV2Extension($.OpenAPIDefinition|raw${\n"+
469 "Schema: spec.Schema{\n"+
470 "SchemaProps: spec.SchemaProps{\n", args)
471 g.generateDescription(t.CommentLines)
472 g.Do("OneOf:common.GenerateOpenAPIV3OneOfSchema($.type|raw${}.OpenAPIV3OneOfTypes()),\n"+
473 "Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
474 err = g.generateValueValidations(&validationSchema.SchemaProps)
475 if err != nil {
476 return err
477 }
478 g.Do("},\n", nil)
479 if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
480 return err
481 }
482 g.Do("},\n", nil)
483 g.Do("},", args)
484
485 g.Do("$.OpenAPIDefinition|raw${\n"+
486 "Schema: spec.Schema{\n"+
487 "SchemaProps: spec.SchemaProps{\n", args)
488 g.generateDescription(t.CommentLines)
489 g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
490 "Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
491 err = g.generateValueValidations(&validationSchema.SchemaProps)
492 if err != nil {
493 return err
494 }
495 g.Do("},\n", nil)
496 if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
497 return err
498 }
499 g.Do("},\n", nil)
500 g.Do("})\n}\n\n", args)
501 return nil
502 case hasV2DefinitionTypeAndFormat:
503 g.Do("return $.OpenAPIDefinition|raw${\n"+
504 "Schema: spec.Schema{\n"+
505 "SchemaProps: spec.SchemaProps{\n", args)
506 g.generateDescription(t.CommentLines)
507 g.Do("Type:$.type|raw${}.OpenAPISchemaType(),\n"+
508 "Format:$.type|raw${}.OpenAPISchemaFormat(),\n", args)
509 err = g.generateValueValidations(&validationSchema.SchemaProps)
510 if err != nil {
511 return err
512 }
513 g.Do("},\n", nil)
514 if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
515 return err
516 }
517 g.Do("},\n", nil)
518 g.Do("}\n}\n\n", args)
519 return nil
520 case hasV3OneOfTypes:
521
522 return fmt.Errorf("type %q has v3 one of types but not v2 type or format", t.Name)
523 }
524
525 g.Do("return $.OpenAPIDefinition|raw${\nSchema: spec.Schema{\nSchemaProps: spec.SchemaProps{\n", args)
526 g.generateDescription(t.CommentLines)
527 g.Do("Type: []string{\"object\"},\n", nil)
528 err = g.generateValueValidations(&validationSchema.SchemaProps)
529 if err != nil {
530 return err
531 }
532
533
534
535 propertiesBuf := bytes.Buffer{}
536 bsw := g
537 bsw.SnippetWriter = generator.NewSnippetWriter(&propertiesBuf, g.context, "$", "$")
538 required, err := bsw.generateMembers(t, []string{})
539 if err != nil {
540 return err
541 }
542 if propertiesBuf.Len() > 0 {
543 g.Do("Properties: map[string]$.SpecSchemaType|raw${\n", args)
544 g.Do(strings.Replace(propertiesBuf.String(), "$", "$\"$\"$", -1), nil)
545 g.Do("},\n", nil)
546 }
547
548 if len(required) > 0 {
549 g.Do("Required: []string{\"$.$\"},\n", strings.Join(required, "\",\""))
550 }
551 g.Do("},\n", nil)
552 if err := g.generateStructExtensions(t, validationSchema.Extensions); err != nil {
553 return err
554 }
555 g.Do("},\n", nil)
556
557
558 keys := []string{}
559 for k := range g.refTypes {
560 keys = append(keys, k)
561 }
562 sort.Strings(keys)
563 deps := []string{}
564 for _, k := range keys {
565 v := g.refTypes[k]
566 if t, _ := openapi.OpenAPITypeFormat(v.String()); t != "" {
567
568
569 continue
570 }
571 deps = append(deps, k)
572 }
573 if len(deps) > 0 {
574 g.Do("Dependencies: []string{\n", args)
575 for _, k := range deps {
576 g.Do("\"$.$\",", k)
577 }
578 g.Do("},\n", nil)
579 }
580 g.Do("}\n}\n\n", nil)
581 }
582 return nil
583 }
584
585 func (g openAPITypeWriter) generateStructExtensions(t *types.Type, otherExtensions map[string]interface{}) error {
586 extensions, errors := parseExtensions(t.CommentLines)
587
588 if len(errors) > 0 {
589 for _, e := range errors {
590 klog.Errorf("[%s]: %s\n", t.String(), e)
591 }
592 }
593 unions, errors := parseUnions(t)
594 if len(errors) > 0 {
595 for _, e := range errors {
596 klog.Errorf("[%s]: %s\n", t.String(), e)
597 }
598 }
599
600
601 g.emitExtensions(extensions, unions, otherExtensions)
602 return nil
603 }
604
605 func (g openAPITypeWriter) generateMemberExtensions(m *types.Member, parent *types.Type, otherExtensions map[string]interface{}) error {
606 extensions, parseErrors := parseExtensions(m.CommentLines)
607 validationErrors := validateMemberExtensions(extensions, m)
608 errors := append(parseErrors, validationErrors...)
609
610 if len(errors) > 0 {
611 errorPrefix := fmt.Sprintf("[%s] %s:", parent.String(), m.String())
612 for _, e := range errors {
613 klog.V(2).Infof("%s %s\n", errorPrefix, e)
614 }
615 }
616 g.emitExtensions(extensions, nil, otherExtensions)
617 return nil
618 }
619
620 func (g openAPITypeWriter) emitExtensions(extensions []extension, unions []union, otherExtensions map[string]interface{}) {
621
622 if len(extensions) == 0 && len(unions) == 0 && len(otherExtensions) == 0 {
623 return
624 }
625 g.Do("VendorExtensible: spec.VendorExtensible{\nExtensions: spec.Extensions{\n", nil)
626 for _, extension := range extensions {
627 g.Do("\"$.$\": ", extension.xName)
628 if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
629 g.Do("[]interface{}{\n", nil)
630 }
631 for _, value := range extension.values {
632 g.Do("\"$.$\",\n", value)
633 }
634 if extension.hasMultipleValues() || extension.isAlwaysArrayFormat() {
635 g.Do("},\n", nil)
636 }
637 }
638 if len(unions) > 0 {
639 g.Do("\"x-kubernetes-unions\": []interface{}{\n", nil)
640 for _, u := range unions {
641 u.emit(g)
642 }
643 g.Do("},\n", nil)
644 }
645
646 if len(otherExtensions) > 0 {
647 for k, v := range otherExtensions {
648 g.Do("$.key$: $.value$,\n", map[string]interface{}{
649 "key": fmt.Sprintf("%#v", k),
650 "value": fmt.Sprintf("%#v", v),
651 })
652 }
653 }
654
655 g.Do("},\n},\n", nil)
656 }
657
658
659 func (g openAPITypeWriter) validatePatchTags(m *types.Member, parent *types.Type) error {
660
661 for _, tagKey := range tempPatchTags {
662 structTagValue := reflect.StructTag(m.Tags).Get(tagKey)
663 commentTagValue, err := getSingleTagsValue(m.CommentLines, tagKey)
664 if err != nil {
665 return err
666 }
667 if structTagValue != commentTagValue {
668 return fmt.Errorf("Tags in comment and struct should match for member (%s) of (%s)",
669 m.Name, parent.Name.String())
670 }
671 }
672 return nil
673 }
674
675 func defaultFromComments(comments []string, commentPath string, t *types.Type) (interface{}, *types.Name, error) {
676 var tag string
677
678 for {
679 var err error
680 tag, err = getSingleTagsValue(comments, tagDefault)
681 if err != nil {
682 return nil, nil, err
683 }
684
685 if t == nil || len(tag) > 0 {
686 break
687 }
688
689 comments = t.CommentLines
690 commentPath = t.Name.Package
691 switch t.Kind {
692 case types.Pointer:
693 t = t.Elem
694 case types.Alias:
695 t = t.Underlying
696 default:
697 t = nil
698 }
699 }
700
701 if tag == "" {
702 return nil, nil, nil
703 }
704
705 var i interface{}
706 if id, ok := parseSymbolReference(tag, commentPath); ok {
707 klog.V(5).Infof("%v, %v", id, commentPath)
708 return nil, &id, nil
709 } else if err := json.Unmarshal([]byte(tag), &i); err != nil {
710 return nil, nil, fmt.Errorf("failed to unmarshal default: %v", err)
711 }
712 return i, nil, nil
713 }
714
715 var refRE = regexp.MustCompile(`^ref\((?P<reference>[^"]+)\)$`)
716 var refREIdentIndex = refRE.SubexpIndex("reference")
717
718
719
720
721
722
723
724
725
726 func parseSymbolReference(s, sourcePackage string) (types.Name, bool) {
727 matches := refRE.FindStringSubmatch(s)
728 if len(matches) < refREIdentIndex || matches[refREIdentIndex] == "" {
729 return types.Name{}, false
730 }
731
732 contents := matches[refREIdentIndex]
733 name := types.ParseFullyQualifiedName(contents)
734 if len(name.Package) == 0 {
735 name.Package = sourcePackage
736 }
737 return name, true
738 }
739
740 func implementsCustomUnmarshalling(t *types.Type) bool {
741 switch t.Kind {
742 case types.Pointer:
743 unmarshaller, isUnmarshaller := t.Elem.Methods["UnmarshalJSON"]
744 return isUnmarshaller && unmarshaller.Signature.Receiver.Kind == types.Pointer
745 case types.Struct:
746 _, isUnmarshaller := t.Methods["UnmarshalJSON"]
747 return isUnmarshaller
748 default:
749 return false
750 }
751 }
752
753 func mustEnforceDefault(t *types.Type, omitEmpty bool) (interface{}, error) {
754
755
756 if implementsCustomUnmarshalling(t) {
757
758
759
760
761
762
763 return nil, nil
764 }
765
766 switch t.Kind {
767 case types.Alias:
768 return mustEnforceDefault(t.Underlying, omitEmpty)
769 case types.Pointer, types.Map, types.Slice, types.Array, types.Interface:
770 return nil, nil
771 case types.Struct:
772 if len(t.Members) == 1 && t.Members[0].Embedded {
773
774 return mustEnforceDefault(t.Members[0].Type, omitEmpty)
775 }
776
777 return map[string]interface{}{}, nil
778 case types.Builtin:
779 if !omitEmpty {
780 if zero, ok := openapi.OpenAPIZeroValue(t.String()); ok {
781 return zero, nil
782 } else {
783 return nil, fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
784 }
785 }
786 return nil, nil
787 default:
788 return nil, fmt.Errorf("not sure how to enforce default for %v", t.Kind)
789 }
790 }
791
792 func (g openAPITypeWriter) generateDefault(comments []string, t *types.Type, omitEmpty bool, commentOwningType *types.Type) error {
793 def, ref, err := defaultFromComments(comments, commentOwningType.Name.Package, t)
794 if err != nil {
795 return err
796 }
797 if enforced, err := mustEnforceDefault(t, omitEmpty); err != nil {
798 return err
799 } else if enforced != nil {
800 if def == nil {
801 def = enforced
802 } else if !reflect.DeepEqual(def, enforced) {
803 enforcedJson, _ := json.Marshal(enforced)
804 return fmt.Errorf("invalid default value (%#v) for non-pointer/non-omitempty. If specified, must be: %v", def, string(enforcedJson))
805 }
806 }
807 if def != nil {
808 g.Do("Default: $.$,\n", fmt.Sprintf("%#v", def))
809 } else if ref != nil {
810 g.Do("Default: $.|raw$,\n", &types.Type{Name: *ref})
811 }
812 return nil
813 }
814
815 func (g openAPITypeWriter) generateDescription(CommentLines []string) {
816 var buffer bytes.Buffer
817 delPrevChar := func() {
818 if buffer.Len() > 0 {
819 buffer.Truncate(buffer.Len() - 1)
820 }
821 }
822
823 for _, line := range CommentLines {
824
825 if line == "---" {
826 break
827 }
828 line = strings.TrimRight(line, " ")
829 leading := strings.TrimLeft(line, " ")
830 switch {
831 case len(line) == 0:
832 delPrevChar()
833 buffer.WriteString("\n\n")
834 case strings.HasPrefix(leading, "TODO"):
835 case strings.HasPrefix(leading, "+"):
836 default:
837 if strings.HasPrefix(line, " ") || strings.HasPrefix(line, "\t") {
838 delPrevChar()
839 line = "\n" + line + "\n"
840 } else {
841 line += " "
842 }
843 buffer.WriteString(line)
844 }
845 }
846
847 postDoc := strings.TrimSpace(buffer.String())
848 if len(postDoc) > 0 {
849 g.Do("Description: $.$,\n", fmt.Sprintf("%#v", postDoc))
850 }
851 }
852
853 func (g openAPITypeWriter) generateProperty(m *types.Member, parent *types.Type) error {
854 name := getReferableName(m)
855 if name == "" {
856 return nil
857 }
858 validationSchema, err := ParseCommentTags(m.Type, m.CommentLines, markerPrefix)
859 if err != nil {
860 return err
861 }
862 if err := g.validatePatchTags(m, parent); err != nil {
863 return err
864 }
865 g.Do("\"$.$\": {\n", name)
866 if err := g.generateMemberExtensions(m, parent, validationSchema.Extensions); err != nil {
867 return err
868 }
869 g.Do("SchemaProps: spec.SchemaProps{\n", nil)
870 var extraComments []string
871 if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
872 extraComments = enumType.DescriptionLines()
873 }
874 g.generateDescription(append(m.CommentLines, extraComments...))
875 jsonTags := getJsonTags(m)
876 if len(jsonTags) > 1 && jsonTags[1] == "string" {
877 g.generateSimpleProperty("string", "")
878 g.Do("},\n},\n", nil)
879 return nil
880 }
881 omitEmpty := strings.Contains(reflect.StructTag(m.Tags).Get("json"), "omitempty")
882 if err := g.generateDefault(m.CommentLines, m.Type, omitEmpty, parent); err != nil {
883 return fmt.Errorf("failed to generate default in %v: %v: %v", parent, m.Name, err)
884 }
885 err = g.generateValueValidations(&validationSchema.SchemaProps)
886 if err != nil {
887 return err
888 }
889 t := resolveAliasAndPtrType(m.Type)
890
891 typeString, format := openapi.OpenAPITypeFormat(t.String())
892 if typeString != "" {
893 g.generateSimpleProperty(typeString, format)
894 if enumType, isEnum := g.enumContext.EnumType(m.Type); isEnum {
895
896 g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
897 }
898 g.Do("},\n},\n", nil)
899 return nil
900 }
901 switch t.Kind {
902 case types.Builtin:
903 return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", t)
904 case types.Map:
905 if err := g.generateMapProperty(t); err != nil {
906 return fmt.Errorf("failed to generate map property in %v: %v: %v", parent, m.Name, err)
907 }
908 case types.Slice, types.Array:
909 if err := g.generateSliceProperty(t); err != nil {
910 return fmt.Errorf("failed to generate slice property in %v: %v: %v", parent, m.Name, err)
911 }
912 case types.Struct, types.Interface:
913 g.generateReferenceProperty(t)
914 default:
915 return fmt.Errorf("cannot generate spec for type %v", t)
916 }
917 g.Do("},\n},\n", nil)
918 return g.Error()
919 }
920
921 func (g openAPITypeWriter) generateSimpleProperty(typeString, format string) {
922 g.Do("Type: []string{\"$.$\"},\n", typeString)
923 g.Do("Format: \"$.$\",\n", format)
924 }
925
926 func (g openAPITypeWriter) generateReferenceProperty(t *types.Type) {
927 g.refTypes[t.Name.String()] = t
928 g.Do("Ref: ref(\"$.$\"),\n", t.Name.String())
929 }
930
931 func resolveAliasAndPtrType(t *types.Type) *types.Type {
932 var prev *types.Type
933 for prev != t {
934 prev = t
935 if t.Kind == types.Alias {
936 t = t.Underlying
937 }
938 if t.Kind == types.Pointer {
939 t = t.Elem
940 }
941 }
942 return t
943 }
944
945 func (g openAPITypeWriter) generateMapProperty(t *types.Type) error {
946 keyType := resolveAliasAndPtrType(t.Key)
947 elemType := resolveAliasAndPtrType(t.Elem)
948
949
950 if keyType.Name.Name != "string" {
951 return fmt.Errorf("map with non-string keys are not supported by OpenAPI in %v", t)
952 }
953
954 g.Do("Type: []string{\"object\"},\n", nil)
955 g.Do("AdditionalProperties: &spec.SchemaOrBool{\nAllows: true,\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
956 if err := g.generateDefault(t.Elem.CommentLines, t.Elem, false, t.Elem); err != nil {
957 return err
958 }
959 typeString, format := openapi.OpenAPITypeFormat(elemType.String())
960 if typeString != "" {
961 g.generateSimpleProperty(typeString, format)
962 if enumType, isEnum := g.enumContext.EnumType(t.Elem); isEnum {
963
964 g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
965 }
966 g.Do("},\n},\n},\n", nil)
967 return nil
968 }
969 switch elemType.Kind {
970 case types.Builtin:
971 return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
972 case types.Struct:
973 g.generateReferenceProperty(elemType)
974 case types.Slice, types.Array:
975 if err := g.generateSliceProperty(elemType); err != nil {
976 return err
977 }
978 case types.Map:
979 if err := g.generateMapProperty(elemType); err != nil {
980 return err
981 }
982 default:
983 return fmt.Errorf("map Element kind %v is not supported in %v", elemType.Kind, t.Name)
984 }
985 g.Do("},\n},\n},\n", nil)
986 return nil
987 }
988
989 func (g openAPITypeWriter) generateSliceProperty(t *types.Type) error {
990 elemType := resolveAliasAndPtrType(t.Elem)
991 g.Do("Type: []string{\"array\"},\n", nil)
992 g.Do("Items: &spec.SchemaOrArray{\nSchema: &spec.Schema{\nSchemaProps: spec.SchemaProps{\n", nil)
993 if err := g.generateDefault(t.Elem.CommentLines, t.Elem, false, t.Elem); err != nil {
994 return err
995 }
996 typeString, format := openapi.OpenAPITypeFormat(elemType.String())
997 if typeString != "" {
998 g.generateSimpleProperty(typeString, format)
999 if enumType, isEnum := g.enumContext.EnumType(t.Elem); isEnum {
1000
1001 g.Do("Enum: []interface{}{$.$},\n", strings.Join(enumType.ValueStrings(), ", "))
1002 }
1003 g.Do("},\n},\n},\n", nil)
1004 return nil
1005 }
1006 switch elemType.Kind {
1007 case types.Builtin:
1008 return fmt.Errorf("please add type %v to getOpenAPITypeFormat function", elemType)
1009 case types.Struct:
1010 g.generateReferenceProperty(elemType)
1011 case types.Slice, types.Array:
1012 if err := g.generateSliceProperty(elemType); err != nil {
1013 return err
1014 }
1015 case types.Map:
1016 if err := g.generateMapProperty(elemType); err != nil {
1017 return err
1018 }
1019 default:
1020 return fmt.Errorf("slice Element kind %v is not supported in %v", elemType.Kind, t)
1021 }
1022 g.Do("},\n},\n},\n", nil)
1023 return nil
1024 }
1025
View as plain text