operations.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. // Copyright 2017 Google LLC. All Rights Reserved.
  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 jsonschema
  15. import (
  16. "fmt"
  17. "log"
  18. "strings"
  19. )
  20. //
  21. // OPERATIONS
  22. // The following methods perform operations on Schemas.
  23. //
  24. // IsEmpty returns true if no members of the Schema are specified.
  25. func (schema *Schema) IsEmpty() bool {
  26. return (schema.Schema == nil) &&
  27. (schema.ID == nil) &&
  28. (schema.MultipleOf == nil) &&
  29. (schema.Maximum == nil) &&
  30. (schema.ExclusiveMaximum == nil) &&
  31. (schema.Minimum == nil) &&
  32. (schema.ExclusiveMinimum == nil) &&
  33. (schema.MaxLength == nil) &&
  34. (schema.MinLength == nil) &&
  35. (schema.Pattern == nil) &&
  36. (schema.AdditionalItems == nil) &&
  37. (schema.Items == nil) &&
  38. (schema.MaxItems == nil) &&
  39. (schema.MinItems == nil) &&
  40. (schema.UniqueItems == nil) &&
  41. (schema.MaxProperties == nil) &&
  42. (schema.MinProperties == nil) &&
  43. (schema.Required == nil) &&
  44. (schema.AdditionalProperties == nil) &&
  45. (schema.Properties == nil) &&
  46. (schema.PatternProperties == nil) &&
  47. (schema.Dependencies == nil) &&
  48. (schema.Enumeration == nil) &&
  49. (schema.Type == nil) &&
  50. (schema.AllOf == nil) &&
  51. (schema.AnyOf == nil) &&
  52. (schema.OneOf == nil) &&
  53. (schema.Not == nil) &&
  54. (schema.Definitions == nil) &&
  55. (schema.Title == nil) &&
  56. (schema.Description == nil) &&
  57. (schema.Default == nil) &&
  58. (schema.Format == nil) &&
  59. (schema.Ref == nil)
  60. }
  61. // IsEqual returns true if two schemas are equal.
  62. func (schema *Schema) IsEqual(schema2 *Schema) bool {
  63. return schema.String() == schema2.String()
  64. }
  65. // SchemaOperation represents a function that can be applied to a Schema.
  66. type SchemaOperation func(schema *Schema, context string)
  67. // Applies a specified function to a Schema and all of the Schemas that it contains.
  68. func (schema *Schema) applyToSchemas(operation SchemaOperation, context string) {
  69. if schema.AdditionalItems != nil {
  70. s := schema.AdditionalItems.Schema
  71. if s != nil {
  72. s.applyToSchemas(operation, "AdditionalItems")
  73. }
  74. }
  75. if schema.Items != nil {
  76. if schema.Items.SchemaArray != nil {
  77. for _, s := range *(schema.Items.SchemaArray) {
  78. s.applyToSchemas(operation, "Items.SchemaArray")
  79. }
  80. } else if schema.Items.Schema != nil {
  81. schema.Items.Schema.applyToSchemas(operation, "Items.Schema")
  82. }
  83. }
  84. if schema.AdditionalProperties != nil {
  85. s := schema.AdditionalProperties.Schema
  86. if s != nil {
  87. s.applyToSchemas(operation, "AdditionalProperties")
  88. }
  89. }
  90. if schema.Properties != nil {
  91. for _, pair := range *(schema.Properties) {
  92. s := pair.Value
  93. s.applyToSchemas(operation, "Properties")
  94. }
  95. }
  96. if schema.PatternProperties != nil {
  97. for _, pair := range *(schema.PatternProperties) {
  98. s := pair.Value
  99. s.applyToSchemas(operation, "PatternProperties")
  100. }
  101. }
  102. if schema.Dependencies != nil {
  103. for _, pair := range *(schema.Dependencies) {
  104. schemaOrStringArray := pair.Value
  105. s := schemaOrStringArray.Schema
  106. if s != nil {
  107. s.applyToSchemas(operation, "Dependencies")
  108. }
  109. }
  110. }
  111. if schema.AllOf != nil {
  112. for _, s := range *(schema.AllOf) {
  113. s.applyToSchemas(operation, "AllOf")
  114. }
  115. }
  116. if schema.AnyOf != nil {
  117. for _, s := range *(schema.AnyOf) {
  118. s.applyToSchemas(operation, "AnyOf")
  119. }
  120. }
  121. if schema.OneOf != nil {
  122. for _, s := range *(schema.OneOf) {
  123. s.applyToSchemas(operation, "OneOf")
  124. }
  125. }
  126. if schema.Not != nil {
  127. schema.Not.applyToSchemas(operation, "Not")
  128. }
  129. if schema.Definitions != nil {
  130. for _, pair := range *(schema.Definitions) {
  131. s := pair.Value
  132. s.applyToSchemas(operation, "Definitions")
  133. }
  134. }
  135. operation(schema, context)
  136. }
  137. // CopyProperties copies all non-nil properties from the source Schema to the schema Schema.
  138. func (schema *Schema) CopyProperties(source *Schema) {
  139. if source.Schema != nil {
  140. schema.Schema = source.Schema
  141. }
  142. if source.ID != nil {
  143. schema.ID = source.ID
  144. }
  145. if source.MultipleOf != nil {
  146. schema.MultipleOf = source.MultipleOf
  147. }
  148. if source.Maximum != nil {
  149. schema.Maximum = source.Maximum
  150. }
  151. if source.ExclusiveMaximum != nil {
  152. schema.ExclusiveMaximum = source.ExclusiveMaximum
  153. }
  154. if source.Minimum != nil {
  155. schema.Minimum = source.Minimum
  156. }
  157. if source.ExclusiveMinimum != nil {
  158. schema.ExclusiveMinimum = source.ExclusiveMinimum
  159. }
  160. if source.MaxLength != nil {
  161. schema.MaxLength = source.MaxLength
  162. }
  163. if source.MinLength != nil {
  164. schema.MinLength = source.MinLength
  165. }
  166. if source.Pattern != nil {
  167. schema.Pattern = source.Pattern
  168. }
  169. if source.AdditionalItems != nil {
  170. schema.AdditionalItems = source.AdditionalItems
  171. }
  172. if source.Items != nil {
  173. schema.Items = source.Items
  174. }
  175. if source.MaxItems != nil {
  176. schema.MaxItems = source.MaxItems
  177. }
  178. if source.MinItems != nil {
  179. schema.MinItems = source.MinItems
  180. }
  181. if source.UniqueItems != nil {
  182. schema.UniqueItems = source.UniqueItems
  183. }
  184. if source.MaxProperties != nil {
  185. schema.MaxProperties = source.MaxProperties
  186. }
  187. if source.MinProperties != nil {
  188. schema.MinProperties = source.MinProperties
  189. }
  190. if source.Required != nil {
  191. schema.Required = source.Required
  192. }
  193. if source.AdditionalProperties != nil {
  194. schema.AdditionalProperties = source.AdditionalProperties
  195. }
  196. if source.Properties != nil {
  197. schema.Properties = source.Properties
  198. }
  199. if source.PatternProperties != nil {
  200. schema.PatternProperties = source.PatternProperties
  201. }
  202. if source.Dependencies != nil {
  203. schema.Dependencies = source.Dependencies
  204. }
  205. if source.Enumeration != nil {
  206. schema.Enumeration = source.Enumeration
  207. }
  208. if source.Type != nil {
  209. schema.Type = source.Type
  210. }
  211. if source.AllOf != nil {
  212. schema.AllOf = source.AllOf
  213. }
  214. if source.AnyOf != nil {
  215. schema.AnyOf = source.AnyOf
  216. }
  217. if source.OneOf != nil {
  218. schema.OneOf = source.OneOf
  219. }
  220. if source.Not != nil {
  221. schema.Not = source.Not
  222. }
  223. if source.Definitions != nil {
  224. schema.Definitions = source.Definitions
  225. }
  226. if source.Title != nil {
  227. schema.Title = source.Title
  228. }
  229. if source.Description != nil {
  230. schema.Description = source.Description
  231. }
  232. if source.Default != nil {
  233. schema.Default = source.Default
  234. }
  235. if source.Format != nil {
  236. schema.Format = source.Format
  237. }
  238. if source.Ref != nil {
  239. schema.Ref = source.Ref
  240. }
  241. }
  242. // TypeIs returns true if the Type of a Schema includes the specified type
  243. func (schema *Schema) TypeIs(typeName string) bool {
  244. if schema.Type != nil {
  245. // the schema Type is either a string or an array of strings
  246. if schema.Type.String != nil {
  247. return (*(schema.Type.String) == typeName)
  248. } else if schema.Type.StringArray != nil {
  249. for _, n := range *(schema.Type.StringArray) {
  250. if n == typeName {
  251. return true
  252. }
  253. }
  254. }
  255. }
  256. return false
  257. }
  258. // ResolveRefs resolves "$ref" elements in a Schema and its children.
  259. // But if a reference refers to an object type, is inside a oneOf, or contains a oneOf,
  260. // the reference is kept and we expect downstream tools to separately model these
  261. // referenced schemas.
  262. func (schema *Schema) ResolveRefs() {
  263. rootSchema := schema
  264. count := 1
  265. for count > 0 {
  266. count = 0
  267. schema.applyToSchemas(
  268. func(schema *Schema, context string) {
  269. if schema.Ref != nil {
  270. resolvedRef, err := rootSchema.resolveJSONPointer(*(schema.Ref))
  271. if err != nil {
  272. log.Printf("%+v", err)
  273. } else if resolvedRef.TypeIs("object") {
  274. // don't substitute for objects, we'll model the referenced schema with a class
  275. } else if context == "OneOf" {
  276. // don't substitute for references inside oneOf declarations
  277. } else if resolvedRef.OneOf != nil {
  278. // don't substitute for references that contain oneOf declarations
  279. } else if resolvedRef.AdditionalProperties != nil {
  280. // don't substitute for references that look like objects
  281. } else {
  282. schema.Ref = nil
  283. schema.CopyProperties(resolvedRef)
  284. count++
  285. }
  286. }
  287. }, "")
  288. }
  289. }
  290. // resolveJSONPointer resolves JSON pointers.
  291. // This current implementation is very crude and custom for OpenAPI 2.0 schemas.
  292. // It panics for any pointer that it is unable to resolve.
  293. func (schema *Schema) resolveJSONPointer(ref string) (result *Schema, err error) {
  294. parts := strings.Split(ref, "#")
  295. if len(parts) == 2 {
  296. documentName := parts[0] + "#"
  297. if documentName == "#" && schema.ID != nil {
  298. documentName = *(schema.ID)
  299. }
  300. path := parts[1]
  301. document := schemas[documentName]
  302. pathParts := strings.Split(path, "/")
  303. // we currently do a very limited (hard-coded) resolution of certain paths and log errors for missed cases
  304. if len(pathParts) == 1 {
  305. return document, nil
  306. } else if len(pathParts) == 3 {
  307. switch pathParts[1] {
  308. case "definitions":
  309. dictionary := document.Definitions
  310. for _, pair := range *dictionary {
  311. if pair.Name == pathParts[2] {
  312. result = pair.Value
  313. }
  314. }
  315. case "properties":
  316. dictionary := document.Properties
  317. for _, pair := range *dictionary {
  318. if pair.Name == pathParts[2] {
  319. result = pair.Value
  320. }
  321. }
  322. default:
  323. break
  324. }
  325. }
  326. }
  327. if result == nil {
  328. return nil, fmt.Errorf("unresolved pointer: %+v", ref)
  329. }
  330. return result, nil
  331. }
  332. // ResolveAllOfs replaces "allOf" elements by merging their properties into the parent Schema.
  333. func (schema *Schema) ResolveAllOfs() {
  334. schema.applyToSchemas(
  335. func(schema *Schema, context string) {
  336. if schema.AllOf != nil {
  337. for _, allOf := range *(schema.AllOf) {
  338. schema.CopyProperties(allOf)
  339. }
  340. schema.AllOf = nil
  341. }
  342. }, "resolveAllOfs")
  343. }
  344. // ResolveAnyOfs replaces all "anyOf" elements with "oneOf".
  345. func (schema *Schema) ResolveAnyOfs() {
  346. schema.applyToSchemas(
  347. func(schema *Schema, context string) {
  348. if schema.AnyOf != nil {
  349. schema.OneOf = schema.AnyOf
  350. schema.AnyOf = nil
  351. }
  352. }, "resolveAnyOfs")
  353. }
  354. // return a pointer to a copy of a passed-in string
  355. func stringptr(input string) (output *string) {
  356. return &input
  357. }
  358. // CopyOfficialSchemaProperty copies a named property from the official JSON Schema definition
  359. func (schema *Schema) CopyOfficialSchemaProperty(name string) {
  360. *schema.Properties = append(*schema.Properties,
  361. NewNamedSchema(name,
  362. &Schema{Ref: stringptr("http://json-schema.org/draft-04/schema#/properties/" + name)}))
  363. }
  364. // CopyOfficialSchemaProperties copies named properties from the official JSON Schema definition
  365. func (schema *Schema) CopyOfficialSchemaProperties(names []string) {
  366. for _, name := range names {
  367. schema.CopyOfficialSchemaProperty(name)
  368. }
  369. }