1// Package jsonschema uses reflection to generate JSON Schemas from Go types [1].
2//
3// If json tags are present on struct fields, they will be used to infer
4// property names and if a property is required (omitempty is present).
5//
6// [1] http://json-schema.org/latest/json-schema-validation.html
7package jsonschema
8
9import (
10	"encoding/json"
11	"net"
12	"net/url"
13	"reflect"
14	"strconv"
15	"strings"
16	"time"
17
18	"github.com/iancoleman/orderedmap"
19)
20
21// Version is the JSON Schema version.
22// If extending JSON Schema with custom values use a custom URI.
23// RFC draft-wright-json-schema-00, section 6
24var Version = "http://json-schema.org/draft-04/schema#"
25
26// Schema is the root schema.
27// RFC draft-wright-json-schema-00, section 4.5
28type Schema struct {
29	*Type
30	Definitions Definitions
31}
32
33// Type represents a JSON Schema object type.
34type Type struct {
35	// RFC draft-wright-json-schema-00
36	Version string `json:"$schema,omitempty"` // section 6.1
37	Ref     string `json:"$ref,omitempty"`    // section 7
38	// RFC draft-wright-json-schema-validation-00, section 5
39	MultipleOf           int                    `json:"multipleOf,omitempty"`           // section 5.1
40	Maximum              int                    `json:"maximum,omitempty"`              // section 5.2
41	ExclusiveMaximum     bool                   `json:"exclusiveMaximum,omitempty"`     // section 5.3
42	Minimum              int                    `json:"minimum,omitempty"`              // section 5.4
43	ExclusiveMinimum     bool                   `json:"exclusiveMinimum,omitempty"`     // section 5.5
44	MaxLength            int                    `json:"maxLength,omitempty"`            // section 5.6
45	MinLength            int                    `json:"minLength,omitempty"`            // section 5.7
46	Pattern              string                 `json:"pattern,omitempty"`              // section 5.8
47	AdditionalItems      *Type                  `json:"additionalItems,omitempty"`      // section 5.9
48	Items                *Type                  `json:"items,omitempty"`                // section 5.9
49	MaxItems             int                    `json:"maxItems,omitempty"`             // section 5.10
50	MinItems             int                    `json:"minItems,omitempty"`             // section 5.11
51	UniqueItems          bool                   `json:"uniqueItems,omitempty"`          // section 5.12
52	MaxProperties        int                    `json:"maxProperties,omitempty"`        // section 5.13
53	MinProperties        int                    `json:"minProperties,omitempty"`        // section 5.14
54	Required             []string               `json:"required,omitempty"`             // section 5.15
55	Properties           *orderedmap.OrderedMap `json:"properties,omitempty"`           // section 5.16
56	PatternProperties    map[string]*Type       `json:"patternProperties,omitempty"`    // section 5.17
57	AdditionalProperties json.RawMessage        `json:"additionalProperties,omitempty"` // section 5.18
58	Dependencies         map[string]*Type       `json:"dependencies,omitempty"`         // section 5.19
59	Enum                 []interface{}          `json:"enum,omitempty"`                 // section 5.20
60	Type                 string                 `json:"type,omitempty"`                 // section 5.21
61	AllOf                []*Type                `json:"allOf,omitempty"`                // section 5.22
62	AnyOf                []*Type                `json:"anyOf,omitempty"`                // section 5.23
63	OneOf                []*Type                `json:"oneOf,omitempty"`                // section 5.24
64	Not                  *Type                  `json:"not,omitempty"`                  // section 5.25
65	Definitions          Definitions            `json:"definitions,omitempty"`          // section 5.26
66	// RFC draft-wright-json-schema-validation-00, section 6, 7
67	Title       string        `json:"title,omitempty"`       // section 6.1
68	Description string        `json:"description,omitempty"` // section 6.1
69	Default     interface{}   `json:"default,omitempty"`     // section 6.2
70	Format      string        `json:"format,omitempty"`      // section 7
71	Examples    []interface{} `json:"examples,omitempty"`    // section 7.4
72	// RFC draft-wright-json-schema-hyperschema-00, section 4
73	Media          *Type  `json:"media,omitempty"`          // section 4.3
74	BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3
75
76	Extras map[string]interface{} `json:"-"`
77}
78
79// Reflect reflects to Schema from a value using the default Reflector
80func Reflect(v interface{}) *Schema {
81	return ReflectFromType(reflect.TypeOf(v))
82}
83
84// ReflectFromType generates root schema using the default Reflector
85func ReflectFromType(t reflect.Type) *Schema {
86	r := &Reflector{}
87	return r.ReflectFromType(t)
88}
89
90// A Reflector reflects values into a Schema.
91type Reflector struct {
92	// AllowAdditionalProperties will cause the Reflector to generate a schema
93	// with additionalProperties to 'true' for all struct types. This means
94	// the presence of additional keys in JSON objects will not cause validation
95	// to fail. Note said additional keys will simply be dropped when the
96	// validated JSON is unmarshaled.
97	AllowAdditionalProperties bool
98
99	// RequiredFromJSONSchemaTags will cause the Reflector to generate a schema
100	// that requires any key tagged with `jsonschema:required`, overriding the
101	// default of requiring any key *not* tagged with `json:,omitempty`.
102	RequiredFromJSONSchemaTags bool
103
104	// ExpandedStruct will cause the toplevel definitions of the schema not
105	// be referenced itself to a definition.
106	ExpandedStruct bool
107
108	// Do not reference definitions.
109	// All types are still registered under the "definitions" top-level object,
110	// but instead of $ref fields in containing types, the entire definition
111	// of the contained type is inserted.
112	// This will cause the entire structure of types to be output in one tree.
113	DoNotReference bool
114
115	// Use package paths as well as type names, to avoid conflicts.
116	// Without this setting, if two packages contain a type with the same name,
117	// and both are present in a schema, they will conflict and overwrite in
118	// the definition map and produce bad output.  This is particularly
119	// noticeable when using DoNotReference.
120	FullyQualifyTypeNames bool
121
122	// IgnoredTypes defines a slice of types that should be ignored in the schema,
123	// switching to just allowing additional properties instead.
124	IgnoredTypes []interface{}
125
126	// TypeMapper is a function that can be used to map custom Go types to jsconschema types.
127	TypeMapper func(reflect.Type) *Type
128}
129
130// Reflect reflects to Schema from a value.
131func (r *Reflector) Reflect(v interface{}) *Schema {
132	return r.ReflectFromType(reflect.TypeOf(v))
133}
134
135// ReflectFromType generates root schema
136func (r *Reflector) ReflectFromType(t reflect.Type) *Schema {
137	definitions := Definitions{}
138	if r.ExpandedStruct {
139		st := &Type{
140			Version:              Version,
141			Type:                 "object",
142			Properties:           orderedmap.New(),
143			AdditionalProperties: []byte("false"),
144		}
145		if r.AllowAdditionalProperties {
146			st.AdditionalProperties = []byte("true")
147		}
148		r.reflectStructFields(st, definitions, t)
149		r.reflectStruct(definitions, t)
150		delete(definitions, r.typeName(t))
151		return &Schema{Type: st, Definitions: definitions}
152	}
153
154	s := &Schema{
155		Type:        r.reflectTypeToSchema(definitions, t),
156		Definitions: definitions,
157	}
158	return s
159}
160
161// Definitions hold schema definitions.
162// http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26
163// RFC draft-wright-json-schema-validation-00, section 5.26
164type Definitions map[string]*Type
165
166// Available Go defined types for JSON Schema Validation.
167// RFC draft-wright-json-schema-validation-00, section 7.3
168var (
169	timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1
170	ipType   = reflect.TypeOf(net.IP{})    // ipv4 and ipv6 RFC section 7.3.4, 7.3.5
171	uriType  = reflect.TypeOf(url.URL{})   // uri RFC section 7.3.6
172)
173
174// Byte slices will be encoded as base64
175var byteSliceType = reflect.TypeOf([]byte(nil))
176
177// Go code generated from protobuf enum types should fulfil this interface.
178type protoEnum interface {
179	EnumDescriptor() ([]byte, []int)
180}
181
182var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem()
183
184func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Type {
185	// Already added to definitions?
186	if _, ok := definitions[r.typeName(t)]; ok && !r.DoNotReference {
187		return &Type{Ref: "#/definitions/" + r.typeName(t)}
188	}
189
190	// jsonpb will marshal protobuf enum options as either strings or integers.
191	// It will unmarshal either.
192	if t.Implements(protoEnumType) {
193		return &Type{OneOf: []*Type{
194			{Type: "string"},
195			{Type: "integer"},
196		}}
197	}
198
199	if r.TypeMapper != nil {
200		if t := r.TypeMapper(t); t != nil {
201			return t
202		}
203	}
204
205	// Defined format types for JSON Schema Validation
206	// RFC draft-wright-json-schema-validation-00, section 7.3
207	// TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7
208	switch t {
209	case ipType:
210		// TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5
211		return &Type{Type: "string", Format: "ipv4"} // ipv4 RFC section 7.3.4
212	}
213
214	switch t.Kind() {
215	case reflect.Struct:
216
217		switch t {
218		case timeType: // date-time RFC section 7.3.1
219			return &Type{Type: "string", Format: "date-time"}
220		case uriType: // uri RFC section 7.3.6
221			return &Type{Type: "string", Format: "uri"}
222		default:
223			return r.reflectStruct(definitions, t)
224		}
225
226	case reflect.Map:
227		rt := &Type{
228			Type: "object",
229			PatternProperties: map[string]*Type{
230				".*": r.reflectTypeToSchema(definitions, t.Elem()),
231			},
232		}
233		delete(rt.PatternProperties, "additionalProperties")
234		return rt
235
236	case reflect.Slice, reflect.Array:
237		returnType := &Type{}
238		if t.Kind() == reflect.Array {
239			returnType.MinItems = t.Len()
240			returnType.MaxItems = returnType.MinItems
241		}
242		switch t {
243		case byteSliceType:
244			returnType.Type = "string"
245			returnType.Media = &Type{BinaryEncoding: "base64"}
246			return returnType
247		default:
248			returnType.Type = "array"
249			returnType.Items = r.reflectTypeToSchema(definitions, t.Elem())
250			return returnType
251		}
252
253	case reflect.Interface:
254		return &Type{
255			AdditionalProperties: []byte("true"),
256		}
257
258	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
259		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
260		return &Type{Type: "integer"}
261
262	case reflect.Float32, reflect.Float64:
263		return &Type{Type: "number"}
264
265	case reflect.Bool:
266		return &Type{Type: "boolean"}
267
268	case reflect.String:
269		return &Type{Type: "string"}
270
271	case reflect.Ptr:
272		return r.reflectTypeToSchema(definitions, t.Elem())
273	}
274	panic("unsupported type " + t.String())
275}
276
277// Refects a struct to a JSON Schema type.
278func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type {
279	for _, ignored := range r.IgnoredTypes {
280		if reflect.TypeOf(ignored) == t {
281			st := &Type{
282				Type:                 "object",
283				Properties:           orderedmap.New(),
284				AdditionalProperties: []byte("true"),
285			}
286			definitions[r.typeName(t)] = st
287
288			if r.DoNotReference {
289				return st
290			} else {
291				return &Type{
292					Version: Version,
293					Ref:     "#/definitions/" + r.typeName(t),
294				}
295			}
296
297		}
298	}
299	st := &Type{
300		Type:                 "object",
301		Properties:           orderedmap.New(),
302		AdditionalProperties: []byte("false"),
303	}
304	if r.AllowAdditionalProperties {
305		st.AdditionalProperties = []byte("true")
306	}
307	definitions[r.typeName(t)] = st
308	r.reflectStructFields(st, definitions, t)
309
310	if r.DoNotReference {
311		return st
312	} else {
313		return &Type{
314			Version: Version,
315			Ref:     "#/definitions/" + r.typeName(t),
316		}
317	}
318}
319
320func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t reflect.Type) {
321	if t.Kind() == reflect.Ptr {
322		t = t.Elem()
323	}
324	if t.Kind() != reflect.Struct {
325		return
326	}
327	for i := 0; i < t.NumField(); i++ {
328		f := t.Field(i)
329		name, exist, required := r.reflectFieldName(f)
330		// if anonymous and exported type should be processed recursively
331		// current type should inherit properties of anonymous one
332		if name == "" {
333			if f.Anonymous && !exist {
334				r.reflectStructFields(st, definitions, f.Type)
335			}
336			continue
337		}
338
339		property := r.reflectTypeToSchema(definitions, f.Type)
340		property.structKeywordsFromTags(f, st, name)
341
342		st.Properties.Set(name, property)
343		if required {
344			st.Required = append(st.Required, name)
345		}
346	}
347}
348
349func (t *Type) structKeywordsFromTags(f reflect.StructField, parentType *Type, propertyName string) {
350	t.Description = f.Tag.Get("jsonschema_description")
351	tags := strings.Split(f.Tag.Get("jsonschema"), ",")
352	t.genericKeywords(tags, parentType, propertyName)
353	switch t.Type {
354	case "string":
355		t.stringKeywords(tags)
356	case "number":
357		t.numbericKeywords(tags)
358	case "integer":
359		t.numbericKeywords(tags)
360	case "array":
361		t.arrayKeywords(tags)
362	}
363	extras := strings.Split(f.Tag.Get("jsonschema_extras"), ",")
364	t.extraKeywords(extras)
365}
366
367// read struct tags for generic keyworks
368func (t *Type) genericKeywords(tags []string, parentType *Type, propertyName string) {
369	for _, tag := range tags {
370		nameValue := strings.Split(tag, "=")
371		if len(nameValue) == 2 {
372			name, val := nameValue[0], nameValue[1]
373			switch name {
374			case "title":
375				t.Title = val
376			case "description":
377				t.Description = val
378			case "type":
379				t.Type = val
380			case "oneof_required":
381				var typeFound *Type
382				for i := range parentType.OneOf {
383					if parentType.OneOf[i].Title == nameValue[1] {
384						typeFound = parentType.OneOf[i]
385					}
386				}
387				if typeFound == nil {
388					typeFound = &Type{
389						Title:    nameValue[1],
390						Required: []string{},
391					}
392					parentType.OneOf = append(parentType.OneOf, typeFound)
393				}
394				typeFound.Required = append(typeFound.Required, propertyName)
395			case "oneof_type":
396				if t.OneOf == nil {
397					t.OneOf = make([]*Type, 0, 1)
398				}
399				t.Type = ""
400				types := strings.Split(nameValue[1], ";")
401				for _, ty := range types {
402					t.OneOf = append(t.OneOf, &Type{
403						Type: ty,
404					})
405				}
406			case "enum":
407				switch t.Type {
408				case "string":
409					t.Enum = append(t.Enum, val)
410				case "integer":
411					i, _ := strconv.Atoi(val)
412					t.Enum = append(t.Enum, i)
413				case "number":
414					f, _ := strconv.ParseFloat(val, 64)
415					t.Enum = append(t.Enum, f)
416				}
417			}
418		}
419	}
420}
421
422// read struct tags for string type keyworks
423func (t *Type) stringKeywords(tags []string) {
424	for _, tag := range tags {
425		nameValue := strings.Split(tag, "=")
426		if len(nameValue) == 2 {
427			name, val := nameValue[0], nameValue[1]
428			switch name {
429			case "minLength":
430				i, _ := strconv.Atoi(val)
431				t.MinLength = i
432			case "maxLength":
433				i, _ := strconv.Atoi(val)
434				t.MaxLength = i
435			case "pattern":
436				t.Pattern = val
437			case "format":
438				switch val {
439				case "date-time", "email", "hostname", "ipv4", "ipv6", "uri":
440					t.Format = val
441					break
442				}
443			case "default":
444				t.Default = val
445			case "example":
446				t.Examples = append(t.Examples, val)
447			}
448		}
449	}
450}
451
452// read struct tags for numberic type keyworks
453func (t *Type) numbericKeywords(tags []string) {
454	for _, tag := range tags {
455		nameValue := strings.Split(tag, "=")
456		if len(nameValue) == 2 {
457			name, val := nameValue[0], nameValue[1]
458			switch name {
459			case "multipleOf":
460				i, _ := strconv.Atoi(val)
461				t.MultipleOf = i
462			case "minimum":
463				i, _ := strconv.Atoi(val)
464				t.Minimum = i
465			case "maximum":
466				i, _ := strconv.Atoi(val)
467				t.Maximum = i
468			case "exclusiveMaximum":
469				b, _ := strconv.ParseBool(val)
470				t.ExclusiveMaximum = b
471			case "exclusiveMinimum":
472				b, _ := strconv.ParseBool(val)
473				t.ExclusiveMinimum = b
474			case "default":
475				i, _ := strconv.Atoi(val)
476				t.Default = i
477			case "example":
478				if i, err := strconv.Atoi(val); err == nil {
479					t.Examples = append(t.Examples, i)
480				}
481			}
482		}
483	}
484}
485
486// read struct tags for object type keyworks
487// func (t *Type) objectKeywords(tags []string) {
488//     for _, tag := range tags{
489//         nameValue := strings.Split(tag, "=")
490//         name, val := nameValue[0], nameValue[1]
491//         switch name{
492//             case "dependencies":
493//                 t.Dependencies = val
494//                 break;
495//             case "patternProperties":
496//                 t.PatternProperties = val
497//                 break;
498//         }
499//     }
500// }
501
502// read struct tags for array type keyworks
503func (t *Type) arrayKeywords(tags []string) {
504	var defaultValues []interface{}
505	for _, tag := range tags {
506		nameValue := strings.Split(tag, "=")
507		if len(nameValue) == 2 {
508			name, val := nameValue[0], nameValue[1]
509			switch name {
510			case "minItems":
511				i, _ := strconv.Atoi(val)
512				t.MinItems = i
513			case "maxItems":
514				i, _ := strconv.Atoi(val)
515				t.MaxItems = i
516			case "uniqueItems":
517				t.UniqueItems = true
518			case "default":
519				defaultValues = append(defaultValues, val)
520			}
521		}
522	}
523	if len(defaultValues) > 0 {
524		t.Default = defaultValues
525	}
526}
527
528func (t *Type) extraKeywords(tags []string) {
529	for _, tag := range tags {
530		nameValue := strings.Split(tag, "=")
531		if len(nameValue) == 2 {
532			t.setExtra(nameValue[0], nameValue[1])
533		}
534	}
535}
536
537func (t *Type) setExtra(key, val string) {
538	if t.Extras == nil {
539		t.Extras = map[string]interface{}{}
540	}
541	if existingVal, ok := t.Extras[key]; ok {
542		switch existingVal.(type) {
543		case string:
544			t.Extras[key] = []string{existingVal.(string), val}
545		case []string:
546			t.Extras[key] = append(existingVal.([]string), val)
547		}
548	} else {
549		t.Extras[key] = val
550	}
551}
552
553func requiredFromJSONTags(tags []string) bool {
554	if ignoredByJSONTags(tags) {
555		return false
556	}
557
558	for _, tag := range tags[1:] {
559		if tag == "omitempty" {
560			return false
561		}
562	}
563	return true
564}
565
566func requiredFromJSONSchemaTags(tags []string) bool {
567	if ignoredByJSONSchemaTags(tags) {
568		return false
569	}
570	for _, tag := range tags {
571		if tag == "required" {
572			return true
573		}
574	}
575	return false
576}
577
578func ignoredByJSONTags(tags []string) bool {
579	return tags[0] == "-"
580}
581
582func ignoredByJSONSchemaTags(tags []string) bool {
583	return tags[0] == "-"
584}
585
586func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool, bool) {
587	jsonTags, exist := f.Tag.Lookup("json")
588	if !exist {
589		jsonTags = f.Tag.Get("yaml")
590	}
591
592	jsonTagsList := strings.Split(jsonTags, ",")
593
594	if ignoredByJSONTags(jsonTagsList) {
595		return "", exist, false
596	}
597
598	jsonSchemaTags := strings.Split(f.Tag.Get("jsonschema"), ",")
599	if ignoredByJSONSchemaTags(jsonSchemaTags) {
600		return "", exist, false
601	}
602
603	name := f.Name
604	required := requiredFromJSONTags(jsonTagsList)
605
606	if r.RequiredFromJSONSchemaTags {
607		required = requiredFromJSONSchemaTags(jsonSchemaTags)
608	}
609
610	if jsonTagsList[0] != "" {
611		name = jsonTagsList[0]
612	}
613
614	// field not anonymous and not export has no export name
615	if !f.Anonymous && f.PkgPath != "" {
616		name = ""
617	}
618
619	// field anonymous but without json tag should be inherited by current type
620	if f.Anonymous && !exist {
621		name = ""
622	}
623
624	return name, exist, required
625}
626
627func (s *Schema) MarshalJSON() ([]byte, error) {
628	b, err := json.Marshal(s.Type)
629	if err != nil {
630		return nil, err
631	}
632	if s.Definitions == nil || len(s.Definitions) == 0 {
633		return b, nil
634	}
635	d, err := json.Marshal(struct {
636		Definitions Definitions `json:"definitions,omitempty"`
637	}{s.Definitions})
638	if err != nil {
639		return nil, err
640	}
641	if len(b) == 2 {
642		return d, nil
643	} else {
644		b[len(b)-1] = ','
645		return append(b, d[1:]...), nil
646	}
647}
648
649func (t *Type) MarshalJSON() ([]byte, error) {
650	type Type_ Type
651	b, err := json.Marshal((*Type_)(t))
652	if err != nil {
653		return nil, err
654	}
655	if t.Extras == nil || len(t.Extras) == 0 {
656		return b, nil
657	}
658	m, err := json.Marshal(t.Extras)
659	if err != nil {
660		return nil, err
661	}
662	if len(b) == 2 {
663		return m, nil
664	} else {
665		b[len(b)-1] = ','
666		return append(b, m[1:]...), nil
667	}
668}
669
670func (r *Reflector) typeName(t reflect.Type) string {
671	if r.FullyQualifyTypeNames {
672		return t.PkgPath() + "." + t.Name()
673	}
674	return t.Name()
675}
676