| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- package main
- import (
- "encoding/json"
- "fmt"
- "io"
- "strconv"
- "strings"
- )
- func emitExamples(w io.Writer, schemas []Schema, aliases []Alias) error {
- byName := make(map[string]Schema, len(schemas))
- for _, s := range schemas {
- byName[s.Name] = s
- }
- aliasByName := make(map[string]Alias, len(aliases))
- for _, a := range aliases {
- aliasByName[a.Name] = a
- }
- gen := &exampleGen{byName: byName, aliasByName: aliasByName}
- out := make(map[string]any, len(schemas))
- for _, s := range schemas {
- out[s.Name] = gen.forSchema(s, map[string]bool{})
- }
- payload, err := json.MarshalIndent(out, "", " ")
- if err != nil {
- return err
- }
- if _, err := fmt.Fprintln(w, examplesHeader); err != nil {
- return err
- }
- if _, err := fmt.Fprintf(w, "export const EXAMPLES: Record<string, unknown> = %s;\n", payload); err != nil {
- return err
- }
- return nil
- }
- type exampleGen struct {
- byName map[string]Schema
- aliasByName map[string]Alias
- }
- func (g *exampleGen) forSchema(s Schema, visited map[string]bool) map[string]any {
- obj := make(map[string]any, len(s.Fields))
- for _, f := range s.Fields {
- obj[f.JSONName] = g.forField(f, visited)
- }
- return obj
- }
- func (g *exampleGen) forField(f Field, visited map[string]bool) any {
- if f.Example != "" {
- return coerceExample(f.Example, baseKind(f.Type))
- }
- if v, ok := firstOneOf(f.Validate); ok {
- return v
- }
- bk := baseKind(f.Type)
- if bk.Kind == KindInt || bk.Kind == KindNumber {
- if v, ok := numericFloor(bk.Kind, f.Validate); ok {
- return v
- }
- }
- return g.forType(f.Type, visited)
- }
- func (g *exampleGen) forType(t TypeRef, visited map[string]bool) any {
- switch t.Kind {
- case KindString:
- if t.Name == "datetime" {
- return "2025-01-01T00:00:00Z"
- }
- return ""
- case KindInt, KindNumber:
- return 0
- case KindBool:
- return false
- case KindArray:
- if isVisitedRef(*t.Element, visited) {
- return []any{}
- }
- return []any{g.forType(*t.Element, visited)}
- case KindMap:
- return map[string]any{}
- case KindRef:
- if t.Name == "nullable" {
- return nil
- }
- if alias, ok := g.aliasByName[t.Name]; ok {
- return g.forType(alias.Underlying, visited)
- }
- schema, ok := g.byName[t.Name]
- if !ok || visited[t.Name] {
- return map[string]any{}
- }
- next := cloneVisited(visited)
- next[t.Name] = true
- return g.forSchema(schema, next)
- }
- return nil
- }
- func baseKind(t TypeRef) TypeRef {
- if t.Kind == KindRef && t.Name == "nullable" && t.Inner != nil {
- return *t.Inner
- }
- return t
- }
- func isVisitedRef(t TypeRef, visited map[string]bool) bool {
- return t.Kind == KindRef && t.Name != "nullable" && visited[t.Name]
- }
- func cloneVisited(in map[string]bool) map[string]bool {
- out := make(map[string]bool, len(in)+1)
- for k, v := range in {
- out[k] = v
- }
- return out
- }
- func numericFloor(kind TypeKind, rules []ValidateRule) (any, bool) {
- for _, r := range rules {
- if (r.Name == "gte" || r.Name == "min") && r.Param != "" {
- return coerceExample(r.Param, TypeRef{Kind: kind}), true
- }
- }
- return nil, false
- }
- func firstOneOf(rules []ValidateRule) (string, bool) {
- for _, r := range rules {
- if r.Name == "oneof" {
- fields := strings.Fields(r.Param)
- if len(fields) > 0 {
- return fields[0], true
- }
- }
- }
- return "", false
- }
- func coerceExample(ex string, t TypeRef) any {
- switch t.Kind {
- case KindInt:
- if n, err := strconv.ParseInt(ex, 10, 64); err == nil {
- return n
- }
- return 0
- case KindNumber:
- if n, err := strconv.ParseFloat(ex, 64); err == nil {
- return n
- }
- return 0
- case KindBool:
- return ex == "true"
- case KindString:
- return ex
- default:
- var parsed any
- if err := json.Unmarshal([]byte(ex), &parsed); err == nil {
- return parsed
- }
- return ex
- }
- }
- const examplesHeader = `// Code generated by tools/openapigen. DO NOT EDIT.`
|