You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

451 lines
14 KiB

  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
  7. package jsonschema
  8. import (
  9. "encoding/json"
  10. "net"
  11. "net/url"
  12. "reflect"
  13. "strings"
  14. "time"
  15. "strconv"
  16. )
  17. // Version is the JSON Schema version.
  18. // If extending JSON Schema with custom values use a custom URI.
  19. // RFC draft-wright-json-schema-00, section 6
  20. var Version = "http://json-schema.org/draft-04/schema#"
  21. // Schema is the root schema.
  22. // RFC draft-wright-json-schema-00, section 4.5
  23. type Schema struct {
  24. *Type
  25. Definitions Definitions `json:"definitions,omitempty"`
  26. }
  27. // Type represents a JSON Schema object type.
  28. type Type struct {
  29. // RFC draft-wright-json-schema-00
  30. Version string `json:"$schema,omitempty"` // section 6.1
  31. Ref string `json:"$ref,omitempty"` // section 7
  32. // RFC draft-wright-json-schema-validation-00, section 5
  33. MultipleOf int `json:"multipleOf,omitempty"` // section 5.1
  34. Maximum int `json:"maximum,omitempty"` // section 5.2
  35. ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"` // section 5.3
  36. Minimum int `json:"minimum,omitempty"` // section 5.4
  37. ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"` // section 5.5
  38. MaxLength int `json:"maxLength,omitempty"` // section 5.6
  39. MinLength int `json:"minLength,omitempty"` // section 5.7
  40. Pattern string `json:"pattern,omitempty"` // section 5.8
  41. AdditionalItems *Type `json:"additionalItems,omitempty"` // section 5.9
  42. Items *Type `json:"items,omitempty"` // section 5.9
  43. MaxItems int `json:"maxItems,omitempty"` // section 5.10
  44. MinItems int `json:"minItems,omitempty"` // section 5.11
  45. UniqueItems bool `json:"uniqueItems,omitempty"` // section 5.12
  46. MaxProperties int `json:"maxProperties,omitempty"` // section 5.13
  47. MinProperties int `json:"minProperties,omitempty"` // section 5.14
  48. Required []string `json:"required,omitempty"` // section 5.15
  49. Properties map[string]*Type `json:"properties,omitempty"` // section 5.16
  50. PatternProperties map[string]*Type `json:"patternProperties,omitempty"` // section 5.17
  51. AdditionalProperties json.RawMessage `json:"additionalProperties,omitempty"` // section 5.18
  52. Dependencies map[string]*Type `json:"dependencies,omitempty"` // section 5.19
  53. Enum []interface{} `json:"enum,omitempty"` // section 5.20
  54. Type string `json:"type,omitempty"` // section 5.21
  55. AllOf []*Type `json:"allOf,omitempty"` // section 5.22
  56. AnyOf []*Type `json:"anyOf,omitempty"` // section 5.23
  57. OneOf []*Type `json:"oneOf,omitempty"` // section 5.24
  58. Not *Type `json:"not,omitempty"` // section 5.25
  59. Definitions Definitions `json:"definitions,omitempty"` // section 5.26
  60. // RFC draft-wright-json-schema-validation-00, section 6, 7
  61. Title string `json:"title,omitempty"` // section 6.1
  62. Description string `json:"description,omitempty"` // section 6.1
  63. Default interface{} `json:"default,omitempty"` // section 6.2
  64. Format string `json:"format,omitempty"` // section 7
  65. // RFC draft-wright-json-schema-hyperschema-00, section 4
  66. Media *Type `json:"media,omitempty"` // section 4.3
  67. BinaryEncoding string `json:"binaryEncoding,omitempty"` // section 4.3
  68. }
  69. // Reflect reflects to Schema from a value using the default Reflector
  70. func Reflect(v interface{}) *Schema {
  71. return ReflectFromType(reflect.TypeOf(v))
  72. }
  73. // ReflectFromType generates root schema using the default Reflector
  74. func ReflectFromType(t reflect.Type) *Schema {
  75. r := &Reflector{}
  76. return r.ReflectFromType(t)
  77. }
  78. // A Reflector reflects values into a Schema.
  79. type Reflector struct {
  80. // AllowAdditionalProperties will cause the Reflector to generate a schema
  81. // with additionalProperties to 'true' for all struct types. This means
  82. // the presence of additional keys in JSON objects will not cause validation
  83. // to fail. Note said additional keys will simply be dropped when the
  84. // validated JSON is unmarshaled.
  85. AllowAdditionalProperties bool
  86. // RequiredFromJSONSchemaTags will cause the Reflector to generate a schema
  87. // that requires any key tagged with `jsonschema:required`, overriding the
  88. // default of requiring any key *not* tagged with `json:,omitempty`.
  89. RequiredFromJSONSchemaTags bool
  90. // ExpandedStruct will cause the toplevel definitions of the schema not
  91. // be referenced itself to a definition.
  92. ExpandedStruct bool
  93. }
  94. // Reflect reflects to Schema from a value.
  95. func (r *Reflector) Reflect(v interface{}) *Schema {
  96. return r.ReflectFromType(reflect.TypeOf(v))
  97. }
  98. // ReflectFromType generates root schema
  99. func (r *Reflector) ReflectFromType(t reflect.Type) *Schema {
  100. definitions := Definitions{}
  101. if r.ExpandedStruct {
  102. st := &Type{
  103. Version: Version,
  104. Type: "object",
  105. Properties: map[string]*Type{},
  106. AdditionalProperties: []byte("false"),
  107. }
  108. if r.AllowAdditionalProperties {
  109. st.AdditionalProperties = []byte("true")
  110. }
  111. r.reflectStructFields(st, definitions, t)
  112. r.reflectStruct(definitions, t)
  113. delete(definitions, t.Name())
  114. return &Schema{Type: st, Definitions: definitions}
  115. }
  116. s := &Schema{
  117. Type: r.reflectTypeToSchema(definitions, t),
  118. Definitions: definitions,
  119. }
  120. return s
  121. }
  122. // Definitions hold schema definitions.
  123. // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.26
  124. // RFC draft-wright-json-schema-validation-00, section 5.26
  125. type Definitions map[string]*Type
  126. // Available Go defined types for JSON Schema Validation.
  127. // RFC draft-wright-json-schema-validation-00, section 7.3
  128. var (
  129. timeType = reflect.TypeOf(time.Time{}) // date-time RFC section 7.3.1
  130. ipType = reflect.TypeOf(net.IP{}) // ipv4 and ipv6 RFC section 7.3.4, 7.3.5
  131. uriType = reflect.TypeOf(url.URL{}) // uri RFC section 7.3.6
  132. )
  133. // Byte slices will be encoded as base64
  134. var byteSliceType = reflect.TypeOf([]byte(nil))
  135. // Go code generated from protobuf enum types should fulfil this interface.
  136. type protoEnum interface {
  137. EnumDescriptor() ([]byte, []int)
  138. }
  139. var protoEnumType = reflect.TypeOf((*protoEnum)(nil)).Elem()
  140. func (r *Reflector) reflectTypeToSchema(definitions Definitions, t reflect.Type) *Type {
  141. // Already added to definitions?
  142. if _, ok := definitions[t.Name()]; ok {
  143. return &Type{Ref: "#/definitions/" + t.Name()}
  144. }
  145. // jsonpb will marshal protobuf enum options as either strings or integers.
  146. // It will unmarshal either.
  147. if t.Implements(protoEnumType) {
  148. return &Type{OneOf: []*Type{
  149. {Type: "string"},
  150. {Type: "integer"},
  151. }}
  152. }
  153. // Defined format types for JSON Schema Validation
  154. // RFC draft-wright-json-schema-validation-00, section 7.3
  155. // TODO email RFC section 7.3.2, hostname RFC section 7.3.3, uriref RFC section 7.3.7
  156. switch t {
  157. case ipType:
  158. // TODO differentiate ipv4 and ipv6 RFC section 7.3.4, 7.3.5
  159. return &Type{Type: "string", Format: "ipv4"} // ipv4 RFC section 7.3.4
  160. }
  161. switch t.Kind() {
  162. case reflect.Struct:
  163. switch t {
  164. case timeType: // date-time RFC section 7.3.1
  165. return &Type{Type: "string", Format: "date-time"}
  166. case uriType: // uri RFC section 7.3.6
  167. return &Type{Type: "string", Format: "uri"}
  168. default:
  169. return r.reflectStruct(definitions, t)
  170. }
  171. case reflect.Map:
  172. rt := &Type{
  173. Type: "object",
  174. PatternProperties: map[string]*Type{
  175. ".*": r.reflectTypeToSchema(definitions, t.Elem()),
  176. },
  177. }
  178. delete(rt.PatternProperties, "additionalProperties")
  179. return rt
  180. case reflect.Slice:
  181. switch t {
  182. case byteSliceType:
  183. return &Type{
  184. Type: "string",
  185. Media: &Type{BinaryEncoding: "base64"},
  186. }
  187. default:
  188. return &Type{
  189. Type: "array",
  190. Items: r.reflectTypeToSchema(definitions, t.Elem()),
  191. }
  192. }
  193. case reflect.Interface:
  194. return &Type{
  195. Type: "object",
  196. AdditionalProperties: []byte("true"),
  197. }
  198. case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
  199. reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
  200. return &Type{Type: "integer"}
  201. case reflect.Float32, reflect.Float64:
  202. return &Type{Type: "number"}
  203. case reflect.Bool:
  204. return &Type{Type: "boolean"}
  205. case reflect.String:
  206. return &Type{Type: "string"}
  207. case reflect.Ptr:
  208. return r.reflectTypeToSchema(definitions, t.Elem())
  209. }
  210. panic("unsupported type " + t.String())
  211. }
  212. // Refects a struct to a JSON Schema type.
  213. func (r *Reflector) reflectStruct(definitions Definitions, t reflect.Type) *Type {
  214. st := &Type{
  215. Type: "object",
  216. Properties: map[string]*Type{},
  217. AdditionalProperties: []byte("false"),
  218. }
  219. if r.AllowAdditionalProperties {
  220. st.AdditionalProperties = []byte("true")
  221. }
  222. definitions[t.Name()] = st
  223. r.reflectStructFields(st, definitions, t)
  224. return &Type{
  225. Version: Version,
  226. Ref: "#/definitions/" + t.Name(),
  227. }
  228. }
  229. func (r *Reflector) reflectStructFields(st *Type, definitions Definitions, t reflect.Type) {
  230. if t.Kind() == reflect.Ptr {
  231. t = t.Elem()
  232. }
  233. for i := 0; i < t.NumField(); i++ {
  234. f := t.Field(i)
  235. // anonymous and exported type should be processed recursively
  236. // current type should inherit properties of anonymous one
  237. if f.Anonymous && f.PkgPath == "" {
  238. r.reflectStructFields(st, definitions, f.Type)
  239. continue
  240. }
  241. name, required := r.reflectFieldName(f)
  242. if name == "" {
  243. continue
  244. }
  245. property := r.reflectTypeToSchema(definitions, f.Type)
  246. property.structKeywordsFromTags(f)
  247. st.Properties[name] = property
  248. if required {
  249. st.Required = append(st.Required, name)
  250. }
  251. }
  252. }
  253. func (t *Type) structKeywordsFromTags(f reflect.StructField){
  254. tags := strings.Split(f.Tag.Get("jsonschema"), ",")
  255. switch t.Type{
  256. case "string":
  257. t.stringKeywords(tags)
  258. case "number":
  259. t.numbericKeywords(tags)
  260. case "integer":
  261. t.numbericKeywords(tags)
  262. case "array":
  263. t.arrayKeywords(tags)
  264. }
  265. }
  266. // read struct tags for string type keyworks
  267. func (t *Type) stringKeywords(tags []string) {
  268. for _, tag := range tags{
  269. nameValue := strings.Split(tag, "=")
  270. if len(nameValue) == 2{
  271. name, val := nameValue[0], nameValue[1]
  272. switch name{
  273. case "minLength":
  274. i, _ := strconv.Atoi(val)
  275. t.MinLength = i
  276. case "maxLength":
  277. i, _ := strconv.Atoi(val)
  278. t.MaxLength = i
  279. case "format":
  280. switch val{
  281. case "date-time", "email", "hostname", "ipv4", "ipv6", "uri":
  282. t.Format = val
  283. break
  284. }
  285. }
  286. }
  287. }
  288. }
  289. // read struct tags for numberic type keyworks
  290. func (t *Type) numbericKeywords(tags []string) {
  291. for _, tag := range tags{
  292. nameValue := strings.Split(tag, "=")
  293. if len(nameValue) == 2{
  294. name, val := nameValue[0], nameValue[1]
  295. switch name{
  296. case "multipleOf":
  297. i, _ := strconv.Atoi(val)
  298. t.MultipleOf = i
  299. case "minimum":
  300. i, _ := strconv.Atoi(val)
  301. t.Minimum = i
  302. case "maximum":
  303. i, _ := strconv.Atoi(val)
  304. t.Maximum = i
  305. case "exclusiveMaximum":
  306. b, _ := strconv.ParseBool(val)
  307. t.ExclusiveMaximum = b
  308. case "exclusiveMinimum":
  309. b, _ := strconv.ParseBool(val)
  310. t.ExclusiveMinimum = b
  311. }
  312. }
  313. }
  314. }
  315. // read struct tags for object type keyworks
  316. // func (t *Type) objectKeywords(tags []string) {
  317. // for _, tag := range tags{
  318. // nameValue := strings.Split(tag, "=")
  319. // name, val := nameValue[0], nameValue[1]
  320. // switch name{
  321. // case "dependencies":
  322. // t.Dependencies = val
  323. // break;
  324. // case "patternProperties":
  325. // t.PatternProperties = val
  326. // break;
  327. // }
  328. // }
  329. // }
  330. // read struct tags for array type keyworks
  331. func (t *Type) arrayKeywords(tags []string) {
  332. for _, tag := range tags{
  333. nameValue := strings.Split(tag, "=")
  334. if len(nameValue) == 2{
  335. name, val := nameValue[0], nameValue[1]
  336. switch name{
  337. case "minItems":
  338. i, _ := strconv.Atoi(val)
  339. t.MinItems = i
  340. case "maxItems":
  341. i, _ := strconv.Atoi(val)
  342. t.MaxItems = i
  343. case "uniqueItems":
  344. t.UniqueItems = true
  345. }
  346. }
  347. }
  348. }
  349. func requiredFromJSONTags(tags []string) bool {
  350. if ignoredByJSONTags(tags) {
  351. return false
  352. }
  353. for _, tag := range tags[1:] {
  354. if tag == "omitempty" {
  355. return false
  356. }
  357. }
  358. return true
  359. }
  360. func requiredFromJSONSchemaTags(tags []string) bool {
  361. if ignoredByJSONSchemaTags(tags) {
  362. return false
  363. }
  364. for _, tag := range tags {
  365. if tag == "required" {
  366. return true
  367. }
  368. }
  369. return false
  370. }
  371. func ignoredByJSONTags(tags []string) bool {
  372. return tags[0] == "-"
  373. }
  374. func ignoredByJSONSchemaTags(tags []string) bool {
  375. return tags[0] == "-"
  376. }
  377. func (r *Reflector) reflectFieldName(f reflect.StructField) (string, bool) {
  378. if f.PkgPath != "" { // unexported field, ignore it
  379. return "", false
  380. }
  381. jsonTags := strings.Split(f.Tag.Get("json"), ",")
  382. if ignoredByJSONTags(jsonTags) {
  383. return "", false
  384. }
  385. jsonSchemaTags := strings.Split(f.Tag.Get("jsonschema"), ",")
  386. if ignoredByJSONSchemaTags(jsonSchemaTags) {
  387. return "", false
  388. }
  389. name := f.Name
  390. required := requiredFromJSONTags(jsonTags)
  391. if r.RequiredFromJSONSchemaTags {
  392. required = requiredFromJSONSchemaTags(jsonSchemaTags)
  393. }
  394. if jsonTags[0] != "" {
  395. name = jsonTags[0]
  396. }
  397. return name, required
  398. }