forked from swaggest/jsonschema-go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
context.go
345 lines (284 loc) · 10.5 KB
/
context.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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
package jsonschema
import (
"context"
"reflect"
"strings"
"github.com/swaggest/refl"
)
// CollectDefinitions enables collecting definitions with provided func instead of result schema.
func CollectDefinitions(f func(name string, schema Schema)) func(*ReflectContext) {
return func(rc *ReflectContext) {
rc.CollectDefinitions = f
}
}
// DefinitionsPrefix sets up location for newly created references, default "#/definitions/".
func DefinitionsPrefix(prefix string) func(*ReflectContext) {
return func(rc *ReflectContext) {
rc.DefinitionsPrefix = prefix
}
}
// PropertyNameTag sets up which field tag to use for property name, default "json".
func PropertyNameTag(tag string, additional ...string) func(*ReflectContext) {
return func(rc *ReflectContext) {
rc.PropertyNameTag = tag
rc.PropertyNameAdditionalTags = additional
}
}
// InterceptTypeFunc can intercept type reflection to control or modify schema.
//
// True bool result demands no further processing for the Schema.
//
// Deprecated: use InterceptSchemaFunc.
type InterceptTypeFunc func(reflect.Value, *Schema) (stop bool, err error)
// InterceptSchemaFunc can intercept type reflection to control or modify schema.
//
// True bool result demands no further processing for the Schema.
type InterceptSchemaFunc func(params InterceptSchemaParams) (stop bool, err error)
// InterceptSchemaParams defines InterceptSchemaFunc parameters.
//
// Interceptor in invoked two times, before and after default schema processing.
// If InterceptSchemaFunc returns true or fails, further processing and second invocation are skipped.
type InterceptSchemaParams struct {
Context *ReflectContext
Value reflect.Value
Schema *Schema
Processed bool
}
// InterceptPropertyFunc can intercept field reflection to control or modify schema.
//
// Return ErrSkipProperty to avoid adding this property to parent Schema.Properties.
// Pointer to parent Schema is available in propertySchema.Parent.
//
// Deprecated: use InterceptPropFunc.
type InterceptPropertyFunc func(name string, field reflect.StructField, propertySchema *Schema) error
// InterceptPropFunc can intercept field reflection to control or modify schema.
//
// Return ErrSkipProperty to avoid adding this property to parent Schema.Properties.
// Pointer to parent Schema is available in propertySchema.Parent.
type InterceptPropFunc func(params InterceptPropParams) error
// InterceptPropParams defines InterceptPropFunc parameters.
//
// Interceptor in invoked two times, before and after default property schema processing.
// If InterceptPropFunc fails, further processing and second invocation are skipped.
type InterceptPropParams struct {
Context *ReflectContext
Path []string
Name string
Field reflect.StructField
PropertySchema *Schema
Processed bool
}
// InterceptNullabilityParams defines InterceptNullabilityFunc parameters.
type InterceptNullabilityParams struct {
Context *ReflectContext
OrigSchema Schema
Schema *Schema
Type reflect.Type
OmitEmpty bool
NullAdded bool
RefDef *Schema
}
// InterceptNullabilityFunc can intercept schema reflection to control or modify nullability state.
// It is called after default nullability rules are applied.
type InterceptNullabilityFunc func(params InterceptNullabilityParams)
// InterceptNullability add hook to customize nullability.
func InterceptNullability(f InterceptNullabilityFunc) func(reflectContext *ReflectContext) {
return func(rc *ReflectContext) {
if rc.InterceptNullability != nil {
prev := rc.InterceptNullability
rc.InterceptNullability = func(params InterceptNullabilityParams) {
prev(params)
f(params)
}
} else {
rc.InterceptNullability = f
}
}
}
// InterceptType adds hook to customize schema.
//
// Deprecated: use InterceptSchema.
func InterceptType(f InterceptTypeFunc) func(*ReflectContext) {
return InterceptSchema(func(params InterceptSchemaParams) (stop bool, err error) {
return f(params.Value, params.Schema)
})
}
// InterceptSchema adds hook to customize schema.
func InterceptSchema(f InterceptSchemaFunc) func(*ReflectContext) {
return func(rc *ReflectContext) {
if rc.interceptSchema != nil {
prev := rc.interceptSchema
rc.interceptSchema = func(params InterceptSchemaParams) (b bool, err error) {
ret, err := prev(params)
if err != nil || ret {
return ret, err
}
return f(params)
}
} else {
rc.interceptSchema = f
}
}
}
// InterceptProperty adds hook to customize property schema.
//
// Deprecated: use InterceptProp.
func InterceptProperty(f InterceptPropertyFunc) func(*ReflectContext) {
return InterceptProp(func(params InterceptPropParams) error {
if !params.Processed {
return nil
}
return f(params.Name, params.Field, params.PropertySchema)
})
}
// InterceptProp adds a hook to customize property schema.
func InterceptProp(f InterceptPropFunc) func(reflectContext *ReflectContext) {
return func(rc *ReflectContext) {
if rc.interceptProp != nil {
prev := rc.interceptProp
rc.interceptProp = func(params InterceptPropParams) error {
err := prev(params)
if err != nil {
return err
}
return f(params)
}
} else {
rc.interceptProp = f
}
}
}
// InlineRefs prevents references.
func InlineRefs(rc *ReflectContext) {
rc.InlineRefs = true
}
// RootNullable enables nullability (by pointer) for root schema, disabled by default.
func RootNullable(rc *ReflectContext) {
rc.RootNullable = true
}
// RootRef enables referencing root schema.
func RootRef(rc *ReflectContext) {
rc.RootRef = true
}
// StripDefinitionNamePrefix checks if definition name has any of provided prefixes
// and removes first encountered.
func StripDefinitionNamePrefix(prefix ...string) func(rc *ReflectContext) {
return func(rc *ReflectContext) {
rc.DefName = func(t reflect.Type, defaultDefName string) string {
for _, p := range prefix {
s := strings.TrimPrefix(defaultDefName, p)
s = strings.ReplaceAll(s, "["+p, "[")
if s != defaultDefName {
return s
}
}
return defaultDefName
}
}
}
// PropertyNameMapping enables property name mapping from a struct field name.
func PropertyNameMapping(mapping map[string]string) func(rc *ReflectContext) {
return func(rc *ReflectContext) {
rc.PropertyNameMapping = mapping
}
}
// ProcessWithoutTags enables processing fields without any tags specified.
func ProcessWithoutTags(rc *ReflectContext) {
rc.ProcessWithoutTags = true
}
// SkipEmbeddedMapsSlices disables shortcutting into embedded maps and slices.
func SkipEmbeddedMapsSlices(rc *ReflectContext) {
rc.SkipEmbeddedMapsSlices = true
}
// SkipUnsupportedProperties skips properties with unsupported types (func, chan, etc...) instead of failing.
func SkipUnsupportedProperties(rc *ReflectContext) {
rc.SkipUnsupportedProperties = true
}
// ReflectContext accompanies single reflect operation.
type ReflectContext struct {
// Context allows communicating user data between reflection steps.
context.Context
// DefName returns custom definition name for a type, can be nil.
DefName func(t reflect.Type, defaultDefName string) string
// CollectDefinitions is triggered when named schema is created, can be nil.
// Non-empty CollectDefinitions disables collection of definitions into resulting schema.
CollectDefinitions func(name string, schema Schema)
// DefinitionsPrefix defines location of named schemas, default #/definitions/.
DefinitionsPrefix string
// PropertyNameTag enables property naming from a field tag, e.g. `header:"first_name"`.
PropertyNameTag string
// PropertyNameAdditionalTags enables property naming from first available of multiple tags
// if PropertyNameTag was not found.
PropertyNameAdditionalTags []string
// PropertyNameMapping enables property name mapping from a struct field name, e.g. "FirstName":"first_name".
// Only applicable to top-level properties (including embedded).
PropertyNameMapping map[string]string
// ProcessWithoutTags enables processing fields without any tags specified.
ProcessWithoutTags bool
// UnnamedFieldWithTag enables a requirement that name tag is present
// when processing _ fields to set up parent schema, e.g.
// _ struct{} `header:"_" additionalProperties:"false"`.
UnnamedFieldWithTag bool
// EnvelopNullability enables `anyOf` enveloping of "type":"null" instead of injecting into definition.
EnvelopNullability bool
// InlineRefs tries to inline all types without making references.
InlineRefs bool
// RootRef exposes root schema as reference.
RootRef bool
// RootNullable enables nullability (by pointer) for root schema, disabled by default.
RootNullable bool
// SkipEmbeddedMapsSlices disables shortcutting into embedded maps and slices.
SkipEmbeddedMapsSlices bool
// InterceptType is called before and after type processing.
// So it may be called twice for the same type, first time with empty Schema and
// second time with fully processed schema.
//
// Deprecated: use InterceptSchema.
InterceptType InterceptTypeFunc
// interceptSchema is called before and after type Schema processing.
// So it may be called twice for the same type, first time with empty Schema and
// second time with fully processed schema.
interceptSchema InterceptSchemaFunc
// Deprecated: Use interceptProp.
InterceptProperty InterceptPropertyFunc
interceptProp InterceptPropFunc
InterceptNullability InterceptNullabilityFunc
// SkipNonConstraints disables parsing of `default` and `example` field tags.
SkipNonConstraints bool
// SkipUnsupportedProperties skips properties with unsupported types (func, chan, etc...) instead of failing.
SkipUnsupportedProperties bool
Path []string
definitionIndex []refl.TypeString
definitions map[refl.TypeString]*Schema // list of all definition objects
definitionRefs map[refl.TypeString]Ref
typeCycles map[refl.TypeString]bool
rootDefName string
jsonAPIRoot bool
}
func (rc *ReflectContext) getDefinition(ref string) *Schema {
for ts, r := range rc.definitionRefs {
if r.Path+r.Name == ref {
return rc.definitions[ts]
}
}
return &Schema{}
}
func (rc *ReflectContext) deprecatedFallback() {
if rc.InterceptType != nil {
f := rc.InterceptType
InterceptSchema(func(params InterceptSchemaParams) (stop bool, err error) {
return f(params.Value, params.Schema)
})
rc.InterceptType = nil
}
if rc.InterceptProperty != nil {
f := rc.InterceptProperty
InterceptProp(func(params InterceptPropParams) error {
if !params.Processed {
return nil
}
return f(params.Name, params.Field, params.PropertySchema)
})
rc.InterceptProperty = nil
}
}