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