emit_examples.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. package main
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "io"
  6. "maps"
  7. "strconv"
  8. "strings"
  9. )
  10. func emitExamples(w io.Writer, schemas []Schema, aliases []Alias) error {
  11. byName := make(map[string]Schema, len(schemas))
  12. for _, s := range schemas {
  13. byName[s.Name] = s
  14. }
  15. aliasByName := make(map[string]Alias, len(aliases))
  16. for _, a := range aliases {
  17. aliasByName[a.Name] = a
  18. }
  19. gen := &exampleGen{byName: byName, aliasByName: aliasByName}
  20. out := make(map[string]any, len(schemas))
  21. for _, s := range schemas {
  22. out[s.Name] = gen.forSchema(s, map[string]bool{})
  23. }
  24. payload, err := json.MarshalIndent(out, "", " ")
  25. if err != nil {
  26. return err
  27. }
  28. if _, err := fmt.Fprintln(w, examplesHeader); err != nil {
  29. return err
  30. }
  31. if _, err := fmt.Fprintf(w, "export const EXAMPLES: Record<string, unknown> = %s;\n", payload); err != nil {
  32. return err
  33. }
  34. return nil
  35. }
  36. type exampleGen struct {
  37. byName map[string]Schema
  38. aliasByName map[string]Alias
  39. }
  40. func (g *exampleGen) forSchema(s Schema, visited map[string]bool) map[string]any {
  41. obj := make(map[string]any, len(s.Fields))
  42. for _, f := range s.Fields {
  43. obj[f.JSONName] = g.forField(f, visited)
  44. }
  45. return obj
  46. }
  47. func (g *exampleGen) forField(f Field, visited map[string]bool) any {
  48. if f.Example != "" {
  49. return coerceExample(f.Example, baseKind(f.Type))
  50. }
  51. if v, ok := firstOneOf(f.Validate); ok {
  52. return v
  53. }
  54. bk := baseKind(f.Type)
  55. if bk.Kind == KindInt || bk.Kind == KindNumber {
  56. if v, ok := numericFloor(bk.Kind, f.Validate); ok {
  57. return v
  58. }
  59. }
  60. return g.forType(f.Type, visited)
  61. }
  62. func (g *exampleGen) forType(t TypeRef, visited map[string]bool) any {
  63. switch t.Kind {
  64. case KindString:
  65. if t.Name == "datetime" {
  66. return "2025-01-01T00:00:00Z"
  67. }
  68. return ""
  69. case KindInt, KindNumber:
  70. return 0
  71. case KindBool:
  72. return false
  73. case KindArray:
  74. if isVisitedRef(*t.Element, visited) {
  75. return []any{}
  76. }
  77. return []any{g.forType(*t.Element, visited)}
  78. case KindMap:
  79. return map[string]any{}
  80. case KindRef:
  81. if t.Name == "nullable" {
  82. return nil
  83. }
  84. if alias, ok := g.aliasByName[t.Name]; ok {
  85. return g.forType(alias.Underlying, visited)
  86. }
  87. schema, ok := g.byName[t.Name]
  88. if !ok || visited[t.Name] {
  89. return map[string]any{}
  90. }
  91. next := cloneVisited(visited)
  92. next[t.Name] = true
  93. return g.forSchema(schema, next)
  94. }
  95. return nil
  96. }
  97. func baseKind(t TypeRef) TypeRef {
  98. if t.Kind == KindRef && t.Name == "nullable" && t.Inner != nil {
  99. return *t.Inner
  100. }
  101. return t
  102. }
  103. func isVisitedRef(t TypeRef, visited map[string]bool) bool {
  104. return t.Kind == KindRef && t.Name != "nullable" && visited[t.Name]
  105. }
  106. func cloneVisited(in map[string]bool) map[string]bool {
  107. out := make(map[string]bool, len(in)+1)
  108. maps.Copy(out, in)
  109. return out
  110. }
  111. func numericFloor(kind TypeKind, rules []ValidateRule) (any, bool) {
  112. for _, r := range rules {
  113. if (r.Name == "gte" || r.Name == "min") && r.Param != "" {
  114. return coerceExample(r.Param, TypeRef{Kind: kind}), true
  115. }
  116. }
  117. return nil, false
  118. }
  119. func firstOneOf(rules []ValidateRule) (string, bool) {
  120. for _, r := range rules {
  121. if r.Name == "oneof" {
  122. fields := strings.Fields(r.Param)
  123. if len(fields) > 0 {
  124. return fields[0], true
  125. }
  126. }
  127. }
  128. return "", false
  129. }
  130. func coerceExample(ex string, t TypeRef) any {
  131. switch t.Kind {
  132. case KindInt:
  133. if n, err := strconv.ParseInt(ex, 10, 64); err == nil {
  134. return n
  135. }
  136. return 0
  137. case KindNumber:
  138. if n, err := strconv.ParseFloat(ex, 64); err == nil {
  139. return n
  140. }
  141. return 0
  142. case KindBool:
  143. return ex == "true"
  144. case KindString:
  145. return ex
  146. default:
  147. var parsed any
  148. if err := json.Unmarshal([]byte(ex), &parsed); err == nil {
  149. return parsed
  150. }
  151. return ex
  152. }
  153. }
  154. const examplesHeader = `// Code generated by tools/openapigen. DO NOT EDIT.`