schema.go 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  1. // Copyright 2015 go-swagger maintainers
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package spec
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "strings"
  19. "github.com/go-openapi/jsonpointer"
  20. "github.com/go-openapi/swag"
  21. )
  22. // BooleanProperty creates a boolean property
  23. func BooleanProperty() *Schema {
  24. return &Schema{SchemaProps: SchemaProps{Type: []string{"boolean"}}}
  25. }
  26. // BoolProperty creates a boolean property
  27. func BoolProperty() *Schema { return BooleanProperty() }
  28. // StringProperty creates a string property
  29. func StringProperty() *Schema {
  30. return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}
  31. }
  32. // CharProperty creates a string property
  33. func CharProperty() *Schema {
  34. return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}}}
  35. }
  36. // Float64Property creates a float64/double property
  37. func Float64Property() *Schema {
  38. return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "double"}}
  39. }
  40. // Float32Property creates a float32/float property
  41. func Float32Property() *Schema {
  42. return &Schema{SchemaProps: SchemaProps{Type: []string{"number"}, Format: "float"}}
  43. }
  44. // Int8Property creates an int8 property
  45. func Int8Property() *Schema {
  46. return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int8"}}
  47. }
  48. // Int16Property creates an int16 property
  49. func Int16Property() *Schema {
  50. return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int16"}}
  51. }
  52. // Int32Property creates an int32 property
  53. func Int32Property() *Schema {
  54. return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int32"}}
  55. }
  56. // Int64Property creates an int64 property
  57. func Int64Property() *Schema {
  58. return &Schema{SchemaProps: SchemaProps{Type: []string{"integer"}, Format: "int64"}}
  59. }
  60. // StrFmtProperty creates a property for the named string format
  61. func StrFmtProperty(format string) *Schema {
  62. return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: format}}
  63. }
  64. // DateProperty creates a date property
  65. func DateProperty() *Schema {
  66. return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date"}}
  67. }
  68. // DateTimeProperty creates a date time property
  69. func DateTimeProperty() *Schema {
  70. return &Schema{SchemaProps: SchemaProps{Type: []string{"string"}, Format: "date-time"}}
  71. }
  72. // MapProperty creates a map property
  73. func MapProperty(property *Schema) *Schema {
  74. return &Schema{SchemaProps: SchemaProps{Type: []string{"object"},
  75. AdditionalProperties: &SchemaOrBool{Allows: true, Schema: property}}}
  76. }
  77. // RefProperty creates a ref property
  78. func RefProperty(name string) *Schema {
  79. return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}}
  80. }
  81. // RefSchema creates a ref property
  82. func RefSchema(name string) *Schema {
  83. return &Schema{SchemaProps: SchemaProps{Ref: MustCreateRef(name)}}
  84. }
  85. // ArrayProperty creates an array property
  86. func ArrayProperty(items *Schema) *Schema {
  87. if items == nil {
  88. return &Schema{SchemaProps: SchemaProps{Type: []string{"array"}}}
  89. }
  90. return &Schema{SchemaProps: SchemaProps{Items: &SchemaOrArray{Schema: items}, Type: []string{"array"}}}
  91. }
  92. // ComposedSchema creates a schema with allOf
  93. func ComposedSchema(schemas ...Schema) *Schema {
  94. s := new(Schema)
  95. s.AllOf = schemas
  96. return s
  97. }
  98. // SchemaURL represents a schema url
  99. type SchemaURL string
  100. // MarshalJSON marshal this to JSON
  101. func (r SchemaURL) MarshalJSON() ([]byte, error) {
  102. if r == "" {
  103. return []byte("{}"), nil
  104. }
  105. v := map[string]interface{}{"$schema": string(r)}
  106. return json.Marshal(v)
  107. }
  108. // UnmarshalJSON unmarshal this from JSON
  109. func (r *SchemaURL) UnmarshalJSON(data []byte) error {
  110. var v map[string]interface{}
  111. if err := json.Unmarshal(data, &v); err != nil {
  112. return err
  113. }
  114. return r.fromMap(v)
  115. }
  116. func (r *SchemaURL) fromMap(v map[string]interface{}) error {
  117. if v == nil {
  118. return nil
  119. }
  120. if vv, ok := v["$schema"]; ok {
  121. if str, ok := vv.(string); ok {
  122. u, err := parseURL(str)
  123. if err != nil {
  124. return err
  125. }
  126. *r = SchemaURL(u.String())
  127. }
  128. }
  129. return nil
  130. }
  131. // SchemaProps describes a JSON schema (draft 4)
  132. type SchemaProps struct {
  133. ID string `json:"id,omitempty"`
  134. Ref Ref `json:"-"`
  135. Schema SchemaURL `json:"-"`
  136. Description string `json:"description,omitempty"`
  137. Type StringOrArray `json:"type,omitempty"`
  138. Nullable bool `json:"nullable,omitempty"`
  139. Format string `json:"format,omitempty"`
  140. Title string `json:"title,omitempty"`
  141. Default interface{} `json:"default,omitempty"`
  142. Maximum *float64 `json:"maximum,omitempty"`
  143. ExclusiveMaximum bool `json:"exclusiveMaximum,omitempty"`
  144. Minimum *float64 `json:"minimum,omitempty"`
  145. ExclusiveMinimum bool `json:"exclusiveMinimum,omitempty"`
  146. MaxLength *int64 `json:"maxLength,omitempty"`
  147. MinLength *int64 `json:"minLength,omitempty"`
  148. Pattern string `json:"pattern,omitempty"`
  149. MaxItems *int64 `json:"maxItems,omitempty"`
  150. MinItems *int64 `json:"minItems,omitempty"`
  151. UniqueItems bool `json:"uniqueItems,omitempty"`
  152. MultipleOf *float64 `json:"multipleOf,omitempty"`
  153. Enum []interface{} `json:"enum,omitempty"`
  154. MaxProperties *int64 `json:"maxProperties,omitempty"`
  155. MinProperties *int64 `json:"minProperties,omitempty"`
  156. Required []string `json:"required,omitempty"`
  157. Items *SchemaOrArray `json:"items,omitempty"`
  158. AllOf []Schema `json:"allOf,omitempty"`
  159. OneOf []Schema `json:"oneOf,omitempty"`
  160. AnyOf []Schema `json:"anyOf,omitempty"`
  161. Not *Schema `json:"not,omitempty"`
  162. Properties SchemaProperties `json:"properties,omitempty"`
  163. AdditionalProperties *SchemaOrBool `json:"additionalProperties,omitempty"`
  164. PatternProperties SchemaProperties `json:"patternProperties,omitempty"`
  165. Dependencies Dependencies `json:"dependencies,omitempty"`
  166. AdditionalItems *SchemaOrBool `json:"additionalItems,omitempty"`
  167. Definitions Definitions `json:"definitions,omitempty"`
  168. }
  169. // SwaggerSchemaProps are additional properties supported by swagger schemas, but not JSON-schema (draft 4)
  170. type SwaggerSchemaProps struct {
  171. Discriminator string `json:"discriminator,omitempty"`
  172. ReadOnly bool `json:"readOnly,omitempty"`
  173. XML *XMLObject `json:"xml,omitempty"`
  174. ExternalDocs *ExternalDocumentation `json:"externalDocs,omitempty"`
  175. Example interface{} `json:"example,omitempty"`
  176. }
  177. // Schema the schema object allows the definition of input and output data types.
  178. // These types can be objects, but also primitives and arrays.
  179. // This object is based on the [JSON Schema Specification Draft 4](http://json-schema.org/)
  180. // and uses a predefined subset of it.
  181. // On top of this subset, there are extensions provided by this specification to allow for more complete documentation.
  182. //
  183. // For more information: http://goo.gl/8us55a#schemaObject
  184. type Schema struct {
  185. VendorExtensible
  186. SchemaProps
  187. SwaggerSchemaProps
  188. ExtraProps map[string]interface{} `json:"-"`
  189. }
  190. // JSONLookup implements an interface to customize json pointer lookup
  191. func (s Schema) JSONLookup(token string) (interface{}, error) {
  192. if ex, ok := s.Extensions[token]; ok {
  193. return &ex, nil
  194. }
  195. if ex, ok := s.ExtraProps[token]; ok {
  196. return &ex, nil
  197. }
  198. r, _, err := jsonpointer.GetForToken(s.SchemaProps, token)
  199. if r != nil || (err != nil && !strings.HasPrefix(err.Error(), "object has no field")) {
  200. return r, err
  201. }
  202. r, _, err = jsonpointer.GetForToken(s.SwaggerSchemaProps, token)
  203. return r, err
  204. }
  205. // WithID sets the id for this schema, allows for chaining
  206. func (s *Schema) WithID(id string) *Schema {
  207. s.ID = id
  208. return s
  209. }
  210. // WithTitle sets the title for this schema, allows for chaining
  211. func (s *Schema) WithTitle(title string) *Schema {
  212. s.Title = title
  213. return s
  214. }
  215. // WithDescription sets the description for this schema, allows for chaining
  216. func (s *Schema) WithDescription(description string) *Schema {
  217. s.Description = description
  218. return s
  219. }
  220. // WithProperties sets the properties for this schema
  221. func (s *Schema) WithProperties(schemas map[string]Schema) *Schema {
  222. s.Properties = schemas
  223. return s
  224. }
  225. // SetProperty sets a property on this schema
  226. func (s *Schema) SetProperty(name string, schema Schema) *Schema {
  227. if s.Properties == nil {
  228. s.Properties = make(map[string]Schema)
  229. }
  230. s.Properties[name] = schema
  231. return s
  232. }
  233. // WithAllOf sets the all of property
  234. func (s *Schema) WithAllOf(schemas ...Schema) *Schema {
  235. s.AllOf = schemas
  236. return s
  237. }
  238. // WithMaxProperties sets the max number of properties an object can have
  239. func (s *Schema) WithMaxProperties(max int64) *Schema {
  240. s.MaxProperties = &max
  241. return s
  242. }
  243. // WithMinProperties sets the min number of properties an object must have
  244. func (s *Schema) WithMinProperties(min int64) *Schema {
  245. s.MinProperties = &min
  246. return s
  247. }
  248. // Typed sets the type of this schema for a single value item
  249. func (s *Schema) Typed(tpe, format string) *Schema {
  250. s.Type = []string{tpe}
  251. s.Format = format
  252. return s
  253. }
  254. // AddType adds a type with potential format to the types for this schema
  255. func (s *Schema) AddType(tpe, format string) *Schema {
  256. s.Type = append(s.Type, tpe)
  257. if format != "" {
  258. s.Format = format
  259. }
  260. return s
  261. }
  262. // AsNullable flags this schema as nullable.
  263. func (s *Schema) AsNullable() *Schema {
  264. s.Nullable = true
  265. return s
  266. }
  267. // CollectionOf a fluent builder method for an array parameter
  268. func (s *Schema) CollectionOf(items Schema) *Schema {
  269. s.Type = []string{jsonArray}
  270. s.Items = &SchemaOrArray{Schema: &items}
  271. return s
  272. }
  273. // WithDefault sets the default value on this parameter
  274. func (s *Schema) WithDefault(defaultValue interface{}) *Schema {
  275. s.Default = defaultValue
  276. return s
  277. }
  278. // WithRequired flags this parameter as required
  279. func (s *Schema) WithRequired(items ...string) *Schema {
  280. s.Required = items
  281. return s
  282. }
  283. // AddRequired adds field names to the required properties array
  284. func (s *Schema) AddRequired(items ...string) *Schema {
  285. s.Required = append(s.Required, items...)
  286. return s
  287. }
  288. // WithMaxLength sets a max length value
  289. func (s *Schema) WithMaxLength(max int64) *Schema {
  290. s.MaxLength = &max
  291. return s
  292. }
  293. // WithMinLength sets a min length value
  294. func (s *Schema) WithMinLength(min int64) *Schema {
  295. s.MinLength = &min
  296. return s
  297. }
  298. // WithPattern sets a pattern value
  299. func (s *Schema) WithPattern(pattern string) *Schema {
  300. s.Pattern = pattern
  301. return s
  302. }
  303. // WithMultipleOf sets a multiple of value
  304. func (s *Schema) WithMultipleOf(number float64) *Schema {
  305. s.MultipleOf = &number
  306. return s
  307. }
  308. // WithMaximum sets a maximum number value
  309. func (s *Schema) WithMaximum(max float64, exclusive bool) *Schema {
  310. s.Maximum = &max
  311. s.ExclusiveMaximum = exclusive
  312. return s
  313. }
  314. // WithMinimum sets a minimum number value
  315. func (s *Schema) WithMinimum(min float64, exclusive bool) *Schema {
  316. s.Minimum = &min
  317. s.ExclusiveMinimum = exclusive
  318. return s
  319. }
  320. // WithEnum sets a the enum values (replace)
  321. func (s *Schema) WithEnum(values ...interface{}) *Schema {
  322. s.Enum = append([]interface{}{}, values...)
  323. return s
  324. }
  325. // WithMaxItems sets the max items
  326. func (s *Schema) WithMaxItems(size int64) *Schema {
  327. s.MaxItems = &size
  328. return s
  329. }
  330. // WithMinItems sets the min items
  331. func (s *Schema) WithMinItems(size int64) *Schema {
  332. s.MinItems = &size
  333. return s
  334. }
  335. // UniqueValues dictates that this array can only have unique items
  336. func (s *Schema) UniqueValues() *Schema {
  337. s.UniqueItems = true
  338. return s
  339. }
  340. // AllowDuplicates this array can have duplicates
  341. func (s *Schema) AllowDuplicates() *Schema {
  342. s.UniqueItems = false
  343. return s
  344. }
  345. // AddToAllOf adds a schema to the allOf property
  346. func (s *Schema) AddToAllOf(schemas ...Schema) *Schema {
  347. s.AllOf = append(s.AllOf, schemas...)
  348. return s
  349. }
  350. // WithDiscriminator sets the name of the discriminator field
  351. func (s *Schema) WithDiscriminator(discriminator string) *Schema {
  352. s.Discriminator = discriminator
  353. return s
  354. }
  355. // AsReadOnly flags this schema as readonly
  356. func (s *Schema) AsReadOnly() *Schema {
  357. s.ReadOnly = true
  358. return s
  359. }
  360. // AsWritable flags this schema as writeable (not read-only)
  361. func (s *Schema) AsWritable() *Schema {
  362. s.ReadOnly = false
  363. return s
  364. }
  365. // WithExample sets the example for this schema
  366. func (s *Schema) WithExample(example interface{}) *Schema {
  367. s.Example = example
  368. return s
  369. }
  370. // WithExternalDocs sets/removes the external docs for/from this schema.
  371. // When you pass empty strings as params the external documents will be removed.
  372. // When you pass non-empty string as one value then those values will be used on the external docs object.
  373. // So when you pass a non-empty description, you should also pass the url and vice versa.
  374. func (s *Schema) WithExternalDocs(description, url string) *Schema {
  375. if description == "" && url == "" {
  376. s.ExternalDocs = nil
  377. return s
  378. }
  379. if s.ExternalDocs == nil {
  380. s.ExternalDocs = &ExternalDocumentation{}
  381. }
  382. s.ExternalDocs.Description = description
  383. s.ExternalDocs.URL = url
  384. return s
  385. }
  386. // WithXMLName sets the xml name for the object
  387. func (s *Schema) WithXMLName(name string) *Schema {
  388. if s.XML == nil {
  389. s.XML = new(XMLObject)
  390. }
  391. s.XML.Name = name
  392. return s
  393. }
  394. // WithXMLNamespace sets the xml namespace for the object
  395. func (s *Schema) WithXMLNamespace(namespace string) *Schema {
  396. if s.XML == nil {
  397. s.XML = new(XMLObject)
  398. }
  399. s.XML.Namespace = namespace
  400. return s
  401. }
  402. // WithXMLPrefix sets the xml prefix for the object
  403. func (s *Schema) WithXMLPrefix(prefix string) *Schema {
  404. if s.XML == nil {
  405. s.XML = new(XMLObject)
  406. }
  407. s.XML.Prefix = prefix
  408. return s
  409. }
  410. // AsXMLAttribute flags this object as xml attribute
  411. func (s *Schema) AsXMLAttribute() *Schema {
  412. if s.XML == nil {
  413. s.XML = new(XMLObject)
  414. }
  415. s.XML.Attribute = true
  416. return s
  417. }
  418. // AsXMLElement flags this object as an xml node
  419. func (s *Schema) AsXMLElement() *Schema {
  420. if s.XML == nil {
  421. s.XML = new(XMLObject)
  422. }
  423. s.XML.Attribute = false
  424. return s
  425. }
  426. // AsWrappedXML flags this object as wrapped, this is mostly useful for array types
  427. func (s *Schema) AsWrappedXML() *Schema {
  428. if s.XML == nil {
  429. s.XML = new(XMLObject)
  430. }
  431. s.XML.Wrapped = true
  432. return s
  433. }
  434. // AsUnwrappedXML flags this object as an xml node
  435. func (s *Schema) AsUnwrappedXML() *Schema {
  436. if s.XML == nil {
  437. s.XML = new(XMLObject)
  438. }
  439. s.XML.Wrapped = false
  440. return s
  441. }
  442. // SetValidations defines all schema validations.
  443. //
  444. // NOTE: Required, ReadOnly, AllOf, AnyOf, OneOf and Not are not considered.
  445. func (s *Schema) SetValidations(val SchemaValidations) {
  446. s.Maximum = val.Maximum
  447. s.ExclusiveMaximum = val.ExclusiveMaximum
  448. s.Minimum = val.Minimum
  449. s.ExclusiveMinimum = val.ExclusiveMinimum
  450. s.MaxLength = val.MaxLength
  451. s.MinLength = val.MinLength
  452. s.Pattern = val.Pattern
  453. s.MaxItems = val.MaxItems
  454. s.MinItems = val.MinItems
  455. s.UniqueItems = val.UniqueItems
  456. s.MultipleOf = val.MultipleOf
  457. s.Enum = val.Enum
  458. s.MinProperties = val.MinProperties
  459. s.MaxProperties = val.MaxProperties
  460. s.PatternProperties = val.PatternProperties
  461. }
  462. // WithValidations is a fluent method to set schema validations
  463. func (s *Schema) WithValidations(val SchemaValidations) *Schema {
  464. s.SetValidations(val)
  465. return s
  466. }
  467. // Validations returns a clone of the validations for this schema
  468. func (s Schema) Validations() SchemaValidations {
  469. return SchemaValidations{
  470. CommonValidations: CommonValidations{
  471. Maximum: s.Maximum,
  472. ExclusiveMaximum: s.ExclusiveMaximum,
  473. Minimum: s.Minimum,
  474. ExclusiveMinimum: s.ExclusiveMinimum,
  475. MaxLength: s.MaxLength,
  476. MinLength: s.MinLength,
  477. Pattern: s.Pattern,
  478. MaxItems: s.MaxItems,
  479. MinItems: s.MinItems,
  480. UniqueItems: s.UniqueItems,
  481. MultipleOf: s.MultipleOf,
  482. Enum: s.Enum,
  483. },
  484. MinProperties: s.MinProperties,
  485. MaxProperties: s.MaxProperties,
  486. PatternProperties: s.PatternProperties,
  487. }
  488. }
  489. // MarshalJSON marshal this to JSON
  490. func (s Schema) MarshalJSON() ([]byte, error) {
  491. b1, err := json.Marshal(s.SchemaProps)
  492. if err != nil {
  493. return nil, fmt.Errorf("schema props %v", err)
  494. }
  495. b2, err := json.Marshal(s.VendorExtensible)
  496. if err != nil {
  497. return nil, fmt.Errorf("vendor props %v", err)
  498. }
  499. b3, err := s.Ref.MarshalJSON()
  500. if err != nil {
  501. return nil, fmt.Errorf("ref prop %v", err)
  502. }
  503. b4, err := s.Schema.MarshalJSON()
  504. if err != nil {
  505. return nil, fmt.Errorf("schema prop %v", err)
  506. }
  507. b5, err := json.Marshal(s.SwaggerSchemaProps)
  508. if err != nil {
  509. return nil, fmt.Errorf("common validations %v", err)
  510. }
  511. var b6 []byte
  512. if s.ExtraProps != nil {
  513. jj, err := json.Marshal(s.ExtraProps)
  514. if err != nil {
  515. return nil, fmt.Errorf("extra props %v", err)
  516. }
  517. b6 = jj
  518. }
  519. return swag.ConcatJSON(b1, b2, b3, b4, b5, b6), nil
  520. }
  521. // UnmarshalJSON marshal this from JSON
  522. func (s *Schema) UnmarshalJSON(data []byte) error {
  523. props := struct {
  524. SchemaProps
  525. SwaggerSchemaProps
  526. }{}
  527. if err := json.Unmarshal(data, &props); err != nil {
  528. return err
  529. }
  530. sch := Schema{
  531. SchemaProps: props.SchemaProps,
  532. SwaggerSchemaProps: props.SwaggerSchemaProps,
  533. }
  534. var d map[string]interface{}
  535. if err := json.Unmarshal(data, &d); err != nil {
  536. return err
  537. }
  538. _ = sch.Ref.fromMap(d)
  539. _ = sch.Schema.fromMap(d)
  540. delete(d, "$ref")
  541. delete(d, "$schema")
  542. for _, pn := range swag.DefaultJSONNameProvider.GetJSONNames(s) {
  543. delete(d, pn)
  544. }
  545. for k, vv := range d {
  546. lk := strings.ToLower(k)
  547. if strings.HasPrefix(lk, "x-") {
  548. if sch.Extensions == nil {
  549. sch.Extensions = map[string]interface{}{}
  550. }
  551. sch.Extensions[k] = vv
  552. continue
  553. }
  554. if sch.ExtraProps == nil {
  555. sch.ExtraProps = map[string]interface{}{}
  556. }
  557. sch.ExtraProps[k] = vv
  558. }
  559. *s = sch
  560. return nil
  561. }