| // Copyright 2017 Google LLC. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package jsonschema |
| |
| import ( |
| "fmt" |
| "log" |
| "strings" |
| ) |
| |
| // |
| // OPERATIONS |
| // The following methods perform operations on Schemas. |
| // |
| |
| // IsEmpty returns true if no members of the Schema are specified. |
| func (schema *Schema) IsEmpty() bool { |
| return (schema.Schema == nil) && |
| (schema.ID == nil) && |
| (schema.MultipleOf == nil) && |
| (schema.Maximum == nil) && |
| (schema.ExclusiveMaximum == nil) && |
| (schema.Minimum == nil) && |
| (schema.ExclusiveMinimum == nil) && |
| (schema.MaxLength == nil) && |
| (schema.MinLength == nil) && |
| (schema.Pattern == nil) && |
| (schema.AdditionalItems == nil) && |
| (schema.Items == nil) && |
| (schema.MaxItems == nil) && |
| (schema.MinItems == nil) && |
| (schema.UniqueItems == nil) && |
| (schema.MaxProperties == nil) && |
| (schema.MinProperties == nil) && |
| (schema.Required == nil) && |
| (schema.AdditionalProperties == nil) && |
| (schema.Properties == nil) && |
| (schema.PatternProperties == nil) && |
| (schema.Dependencies == nil) && |
| (schema.Enumeration == nil) && |
| (schema.Type == nil) && |
| (schema.AllOf == nil) && |
| (schema.AnyOf == nil) && |
| (schema.OneOf == nil) && |
| (schema.Not == nil) && |
| (schema.Definitions == nil) && |
| (schema.Title == nil) && |
| (schema.Description == nil) && |
| (schema.Default == nil) && |
| (schema.Format == nil) && |
| (schema.Ref == nil) |
| } |
| |
| // IsEqual returns true if two schemas are equal. |
| func (schema *Schema) IsEqual(schema2 *Schema) bool { |
| return schema.String() == schema2.String() |
| } |
| |
| // SchemaOperation represents a function that can be applied to a Schema. |
| type SchemaOperation func(schema *Schema, context string) |
| |
| // Applies a specified function to a Schema and all of the Schemas that it contains. |
| func (schema *Schema) applyToSchemas(operation SchemaOperation, context string) { |
| |
| if schema.AdditionalItems != nil { |
| s := schema.AdditionalItems.Schema |
| if s != nil { |
| s.applyToSchemas(operation, "AdditionalItems") |
| } |
| } |
| |
| if schema.Items != nil { |
| if schema.Items.SchemaArray != nil { |
| for _, s := range *(schema.Items.SchemaArray) { |
| s.applyToSchemas(operation, "Items.SchemaArray") |
| } |
| } else if schema.Items.Schema != nil { |
| schema.Items.Schema.applyToSchemas(operation, "Items.Schema") |
| } |
| } |
| |
| if schema.AdditionalProperties != nil { |
| s := schema.AdditionalProperties.Schema |
| if s != nil { |
| s.applyToSchemas(operation, "AdditionalProperties") |
| } |
| } |
| |
| if schema.Properties != nil { |
| for _, pair := range *(schema.Properties) { |
| s := pair.Value |
| s.applyToSchemas(operation, "Properties") |
| } |
| } |
| if schema.PatternProperties != nil { |
| for _, pair := range *(schema.PatternProperties) { |
| s := pair.Value |
| s.applyToSchemas(operation, "PatternProperties") |
| } |
| } |
| |
| if schema.Dependencies != nil { |
| for _, pair := range *(schema.Dependencies) { |
| schemaOrStringArray := pair.Value |
| s := schemaOrStringArray.Schema |
| if s != nil { |
| s.applyToSchemas(operation, "Dependencies") |
| } |
| } |
| } |
| |
| if schema.AllOf != nil { |
| for _, s := range *(schema.AllOf) { |
| s.applyToSchemas(operation, "AllOf") |
| } |
| } |
| if schema.AnyOf != nil { |
| for _, s := range *(schema.AnyOf) { |
| s.applyToSchemas(operation, "AnyOf") |
| } |
| } |
| if schema.OneOf != nil { |
| for _, s := range *(schema.OneOf) { |
| s.applyToSchemas(operation, "OneOf") |
| } |
| } |
| if schema.Not != nil { |
| schema.Not.applyToSchemas(operation, "Not") |
| } |
| |
| if schema.Definitions != nil { |
| for _, pair := range *(schema.Definitions) { |
| s := pair.Value |
| s.applyToSchemas(operation, "Definitions") |
| } |
| } |
| |
| operation(schema, context) |
| } |
| |
| // CopyProperties copies all non-nil properties from the source Schema to the schema Schema. |
| func (schema *Schema) CopyProperties(source *Schema) { |
| if source.Schema != nil { |
| schema.Schema = source.Schema |
| } |
| if source.ID != nil { |
| schema.ID = source.ID |
| } |
| if source.MultipleOf != nil { |
| schema.MultipleOf = source.MultipleOf |
| } |
| if source.Maximum != nil { |
| schema.Maximum = source.Maximum |
| } |
| if source.ExclusiveMaximum != nil { |
| schema.ExclusiveMaximum = source.ExclusiveMaximum |
| } |
| if source.Minimum != nil { |
| schema.Minimum = source.Minimum |
| } |
| if source.ExclusiveMinimum != nil { |
| schema.ExclusiveMinimum = source.ExclusiveMinimum |
| } |
| if source.MaxLength != nil { |
| schema.MaxLength = source.MaxLength |
| } |
| if source.MinLength != nil { |
| schema.MinLength = source.MinLength |
| } |
| if source.Pattern != nil { |
| schema.Pattern = source.Pattern |
| } |
| if source.AdditionalItems != nil { |
| schema.AdditionalItems = source.AdditionalItems |
| } |
| if source.Items != nil { |
| schema.Items = source.Items |
| } |
| if source.MaxItems != nil { |
| schema.MaxItems = source.MaxItems |
| } |
| if source.MinItems != nil { |
| schema.MinItems = source.MinItems |
| } |
| if source.UniqueItems != nil { |
| schema.UniqueItems = source.UniqueItems |
| } |
| if source.MaxProperties != nil { |
| schema.MaxProperties = source.MaxProperties |
| } |
| if source.MinProperties != nil { |
| schema.MinProperties = source.MinProperties |
| } |
| if source.Required != nil { |
| schema.Required = source.Required |
| } |
| if source.AdditionalProperties != nil { |
| schema.AdditionalProperties = source.AdditionalProperties |
| } |
| if source.Properties != nil { |
| schema.Properties = source.Properties |
| } |
| if source.PatternProperties != nil { |
| schema.PatternProperties = source.PatternProperties |
| } |
| if source.Dependencies != nil { |
| schema.Dependencies = source.Dependencies |
| } |
| if source.Enumeration != nil { |
| schema.Enumeration = source.Enumeration |
| } |
| if source.Type != nil { |
| schema.Type = source.Type |
| } |
| if source.AllOf != nil { |
| schema.AllOf = source.AllOf |
| } |
| if source.AnyOf != nil { |
| schema.AnyOf = source.AnyOf |
| } |
| if source.OneOf != nil { |
| schema.OneOf = source.OneOf |
| } |
| if source.Not != nil { |
| schema.Not = source.Not |
| } |
| if source.Definitions != nil { |
| schema.Definitions = source.Definitions |
| } |
| if source.Title != nil { |
| schema.Title = source.Title |
| } |
| if source.Description != nil { |
| schema.Description = source.Description |
| } |
| if source.Default != nil { |
| schema.Default = source.Default |
| } |
| if source.Format != nil { |
| schema.Format = source.Format |
| } |
| if source.Ref != nil { |
| schema.Ref = source.Ref |
| } |
| } |
| |
| // TypeIs returns true if the Type of a Schema includes the specified type |
| func (schema *Schema) TypeIs(typeName string) bool { |
| if schema.Type != nil { |
| // the schema Type is either a string or an array of strings |
| if schema.Type.String != nil { |
| return (*(schema.Type.String) == typeName) |
| } else if schema.Type.StringArray != nil { |
| for _, n := range *(schema.Type.StringArray) { |
| if n == typeName { |
| return true |
| } |
| } |
| } |
| } |
| return false |
| } |
| |
| // ResolveRefs resolves "$ref" elements in a Schema and its children. |
| // But if a reference refers to an object type, is inside a oneOf, or contains a oneOf, |
| // the reference is kept and we expect downstream tools to separately model these |
| // referenced schemas. |
| func (schema *Schema) ResolveRefs() { |
| rootSchema := schema |
| count := 1 |
| for count > 0 { |
| count = 0 |
| schema.applyToSchemas( |
| func(schema *Schema, context string) { |
| if schema.Ref != nil { |
| resolvedRef, err := rootSchema.resolveJSONPointer(*(schema.Ref)) |
| if err != nil { |
| log.Printf("%+v", err) |
| } else if resolvedRef.TypeIs("object") { |
| // don't substitute for objects, we'll model the referenced schema with a class |
| } else if context == "OneOf" { |
| // don't substitute for references inside oneOf declarations |
| } else if resolvedRef.OneOf != nil { |
| // don't substitute for references that contain oneOf declarations |
| } else if resolvedRef.AdditionalProperties != nil { |
| // don't substitute for references that look like objects |
| } else { |
| schema.Ref = nil |
| schema.CopyProperties(resolvedRef) |
| count++ |
| } |
| } |
| }, "") |
| } |
| } |
| |
| // resolveJSONPointer resolves JSON pointers. |
| // This current implementation is very crude and custom for OpenAPI 2.0 schemas. |
| // It panics for any pointer that it is unable to resolve. |
| func (schema *Schema) resolveJSONPointer(ref string) (result *Schema, err error) { |
| parts := strings.Split(ref, "#") |
| if len(parts) == 2 { |
| documentName := parts[0] + "#" |
| if documentName == "#" && schema.ID != nil { |
| documentName = *(schema.ID) |
| } |
| path := parts[1] |
| document := schemas[documentName] |
| pathParts := strings.Split(path, "/") |
| |
| // we currently do a very limited (hard-coded) resolution of certain paths and log errors for missed cases |
| if len(pathParts) == 1 { |
| return document, nil |
| } else if len(pathParts) == 3 { |
| switch pathParts[1] { |
| case "definitions": |
| dictionary := document.Definitions |
| for _, pair := range *dictionary { |
| if pair.Name == pathParts[2] { |
| result = pair.Value |
| } |
| } |
| case "properties": |
| dictionary := document.Properties |
| for _, pair := range *dictionary { |
| if pair.Name == pathParts[2] { |
| result = pair.Value |
| } |
| } |
| default: |
| break |
| } |
| } |
| } |
| if result == nil { |
| return nil, fmt.Errorf("unresolved pointer: %+v", ref) |
| } |
| return result, nil |
| } |
| |
| // ResolveAllOfs replaces "allOf" elements by merging their properties into the parent Schema. |
| func (schema *Schema) ResolveAllOfs() { |
| schema.applyToSchemas( |
| func(schema *Schema, context string) { |
| if schema.AllOf != nil { |
| for _, allOf := range *(schema.AllOf) { |
| schema.CopyProperties(allOf) |
| } |
| schema.AllOf = nil |
| } |
| }, "resolveAllOfs") |
| } |
| |
| // ResolveAnyOfs replaces all "anyOf" elements with "oneOf". |
| func (schema *Schema) ResolveAnyOfs() { |
| schema.applyToSchemas( |
| func(schema *Schema, context string) { |
| if schema.AnyOf != nil { |
| schema.OneOf = schema.AnyOf |
| schema.AnyOf = nil |
| } |
| }, "resolveAnyOfs") |
| } |
| |
| // return a pointer to a copy of a passed-in string |
| func stringptr(input string) (output *string) { |
| return &input |
| } |
| |
| // CopyOfficialSchemaProperty copies a named property from the official JSON Schema definition |
| func (schema *Schema) CopyOfficialSchemaProperty(name string) { |
| *schema.Properties = append(*schema.Properties, |
| NewNamedSchema(name, |
| &Schema{Ref: stringptr("http://json-schema.org/draft-04/schema#/properties/" + name)})) |
| } |
| |
| // CopyOfficialSchemaProperties copies named properties from the official JSON Schema definition |
| func (schema *Schema) CopyOfficialSchemaProperties(names []string) { |
| for _, name := range names { |
| schema.CopyOfficialSchemaProperty(name) |
| } |
| } |