@@ -23,6 +23,7 @@ import (
2323 "generator/internal/spec"
2424 "io"
2525 "slices"
26+ "strconv"
2627 "strings"
2728 "unicode"
2829
@@ -267,7 +268,7 @@ func (b *Generator) addProperties(g *jen.Group, props []spec.Property) {
267268 s .Id ("any" )
268269 }
269270
270- s .Tag (structTag (& p ))
271+ s .Tag (b . structTag (& p ))
271272 if p .Description != nil {
272273 s .Comment (comment (& p ))
273274 }
@@ -370,19 +371,77 @@ func goIDName(name string) string {
370371 return strings .Join (allParts , "" )
371372}
372373
373- // structTag returns a struct tag for JSON and YAML
374- func structTag (p * spec.Property ) map [string ]string {
374+ // structTag returns a struct tag for json, yaml, and jsonschema.
375+ func (b * Generator ) structTag (p * spec.Property ) map [string ]string {
376+ tag := jsonTag (p )
377+ tags := map [string ]string {
378+ "json" : tag ,
379+ "yaml" : tag ,
380+ }
381+
382+ if tag = b .jsonschemaTag (p ); tag != "" {
383+ tags ["jsonschema" ] = tag
384+ }
385+ return tags
386+ }
387+
388+ // jsonschemaTag generates the jsonschema struct tag value used by
389+ // github.com/invopop/jsonschema.
390+ func (b * Generator ) jsonschemaTag (p * spec.Property ) string {
391+ var tagParts []string
392+
393+ // Enum values.
394+ if p .Type .InstanceOf != nil {
395+ if typeInfo , found := b .allTypes [p .Type .TypeName ]; found {
396+ if enum , ok := typeInfo .Value .(spec.Enum ); ok {
397+ for _ , member := range enum .Members {
398+ tagParts = append (tagParts , "enum=" + member .Name )
399+ }
400+ }
401+ }
402+ }
403+
404+ // Default value.
405+ switch v := p .ServerDefault .(type ) {
406+ case nil :
407+ case string :
408+ // Data cleaning.
409+ v = trimBalanced (v , '`' )
410+ v = trimBalanced (v , '"' )
411+ v = trimBalanced (v , '\'' )
412+
413+ if v != "" && v != "null" {
414+ tagParts = append (tagParts , "default=" + escapeTagPart (v ))
415+ }
416+ case []any :
417+ for _ , v := range v {
418+ switch v := v .(type ) {
419+ case string :
420+ tagParts = append (tagParts , "default=" + escapeTagPart (v ))
421+ default :
422+ panic (fmt .Errorf ("unhandled type %T for default found in array for %s" , v , p .Name ))
423+ }
424+ }
425+ case bool :
426+ tagParts = append (tagParts , "default=" + strconv .FormatBool (v ))
427+ case float64 :
428+ tagParts = append (tagParts , "default=" + strconv .FormatFloat (v , 'g' , - 1 , 64 ))
429+ default :
430+ panic (fmt .Errorf ("unhandled type %T for default found for %s" , v , p .Name ))
431+ }
432+
433+ return strings .Join (tagParts , "," )
434+ }
435+
436+ // jsonTag generates the json struct tag value for a property.
437+ func jsonTag (p * spec.Property ) string {
375438 tag := p .Name
376439 required := p .Required != nil && * p .Required
377440
378441 if ! required {
379442 tag += ",omitempty"
380443 }
381-
382- return map [string ]string {
383- "json" : tag ,
384- "yaml" : tag ,
385- }
444+ return tag
386445}
387446
388447// comment returns the field description with punctuation and information about
@@ -405,3 +464,16 @@ func comment(p *spec.Property) string {
405464 }
406465 return sb .String ()
407466}
467+
468+ // trimBalanced removes the leading and trailing character if they are balanced.
469+ func trimBalanced (s string , c rune ) string {
470+ if len (s ) >= 2 && rune (s [0 ]) == c && rune (s [len (s )- 1 ]) == c {
471+ return s [1 : len (s )- 1 ]
472+ }
473+ return s
474+ }
475+
476+ // escapeTagPart escapes commas in struct tag values.
477+ func escapeTagPart (s string ) string {
478+ return strings .ReplaceAll (s , "," , `\,` )
479+ }
0 commit comments