-
-
Notifications
You must be signed in to change notification settings - Fork 139
/
registry.go
120 lines (103 loc) · 3.26 KB
/
registry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package huma
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// Registry creates and stores schemas and their references, and supports
// marshalling to JSON/YAML for use as an OpenAPI #/components/schemas object.
// Behavior is implementation-dependent, but the design allows for recursive
// schemas to exist while being flexible enough to support other use cases
// like only inline objects (no refs) or always using refs for structs.
type Registry interface {
Schema(t reflect.Type, allowRef bool, hint string) *Schema
SchemaFromRef(ref string) *Schema
TypeFromRef(ref string) reflect.Type
Map() map[string]*Schema
}
// DefaultSchemaNamer provides schema names for types. It uses the type name
// when possible, ignoring the package name. If the type is generic, e.g.
// `MyType[SubType]`, then the brackets are removed like `MyTypeSubType`.
// If the type is unnamed, then the name hint is used.
// Note: if you plan to use types with the same name from different packages,
// you should implement your own namer function to prevent issues. Nested
// anonymous types can also present naming issues.
func DefaultSchemaNamer(t reflect.Type, hint string) string {
name := deref(t).Name()
// Fix up generics, if used, for nicer refs & URLs.
name = strings.ReplaceAll(name, "[", "")
name = strings.ReplaceAll(name, "]", "")
if name == "" {
name = hint
}
return name
}
type mapRegistry struct {
prefix string
schemas map[string]*Schema
types map[string]reflect.Type
seen map[reflect.Type]bool
namer func(reflect.Type, string) string
}
func (r *mapRegistry) Schema(t reflect.Type, allowRef bool, hint string) *Schema {
t = deref(t)
getsRef := t.Kind() == reflect.Struct
if t == timeType {
// Special case: time.Time is always a string.
getsRef = false
}
name := r.namer(t, hint)
if getsRef {
if s, ok := r.schemas[name]; ok {
if _, ok := r.seen[t]; !ok {
// Name matches but type is different, so we have a dupe.
panic(fmt.Errorf("duplicate name %s does not match existing type. New type %s, have existing types %+v", name, t, r.seen))
}
if allowRef {
return &Schema{Ref: r.prefix + name}
}
return s
}
}
// First, register the type so refs can be created above for recursive types.
if getsRef {
r.schemas[name] = &Schema{}
r.types[name] = t
r.seen[t] = true
}
s := SchemaFromType(r, t)
if getsRef {
r.schemas[name] = s
}
if getsRef && allowRef {
return &Schema{Ref: r.prefix + name}
}
return s
}
func (r *mapRegistry) SchemaFromRef(ref string) *Schema {
return r.schemas[ref[len(r.prefix):]]
}
func (r *mapRegistry) TypeFromRef(ref string) reflect.Type {
return r.types[ref[len(r.prefix):]]
}
func (r *mapRegistry) Map() map[string]*Schema {
return r.schemas
}
func (r *mapRegistry) MarshalJSON() ([]byte, error) {
return json.Marshal(r.schemas)
}
func (r *mapRegistry) MarshalYAML() (interface{}, error) {
return r.schemas, nil
}
// NewMapRegistry creates a new registry that stores schemas in a map and
// returns references to them using the given prefix.
func NewMapRegistry(prefix string, namer func(t reflect.Type, hint string) string) Registry {
return &mapRegistry{
prefix: prefix,
schemas: map[string]*Schema{},
types: map[string]reflect.Type{},
seen: map[reflect.Type]bool{},
namer: namer,
}
}