emit_examples.go 3.7 KB

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