...

Source file src/github.com/google/gnostic-models/jsonschema/operations.go

Documentation: github.com/google/gnostic-models/jsonschema

     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  
    15  package jsonschema
    16  
    17  import (
    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.
    29  func (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.
    67  func (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.
    72  type SchemaOperation func(schema *Schema, context string)
    73  
    74  // Applies a specified function to a Schema and all of the Schemas that it contains.
    75  func (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.
   154  func (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
   260  func (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.
   280  func (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.
   312  func (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.
   354  func (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".
   367  func (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
   378  func stringptr(input string) (output *string) {
   379  	return &input
   380  }
   381  
   382  // CopyOfficialSchemaProperty copies a named property from the official JSON Schema definition
   383  func (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
   390  func (schema *Schema) CopyOfficialSchemaProperties(names []string) {
   391  	for _, name := range names {
   392  		schema.CopyOfficialSchemaProperty(name)
   393  	}
   394  }
   395  

View as plain text