443 lines
12 KiB
Go
443 lines
12 KiB
Go
|
// 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.
|
||
|
|
||
|
//go:generate go run generate-base.go
|
||
|
|
||
|
package jsonschema
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"io/ioutil"
|
||
|
"strconv"
|
||
|
|
||
|
"gopkg.in/yaml.v3"
|
||
|
)
|
||
|
|
||
|
// This is a global map of all known Schemas.
|
||
|
// It is initialized when the first Schema is created and inserted.
|
||
|
var schemas map[string]*Schema
|
||
|
|
||
|
// NewBaseSchema builds a schema object from an embedded json representation.
|
||
|
func NewBaseSchema() (schema *Schema, err error) {
|
||
|
b, err := baseSchemaBytes()
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
var node yaml.Node
|
||
|
err = yaml.Unmarshal(b, &node)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return NewSchemaFromObject(&node), nil
|
||
|
}
|
||
|
|
||
|
// NewSchemaFromFile reads a schema from a file.
|
||
|
// Currently this assumes that schemas are stored in the source distribution of this project.
|
||
|
func NewSchemaFromFile(filename string) (schema *Schema, err error) {
|
||
|
file, err := ioutil.ReadFile(filename)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
var node yaml.Node
|
||
|
err = yaml.Unmarshal(file, &node)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return NewSchemaFromObject(&node), nil
|
||
|
}
|
||
|
|
||
|
// NewSchemaFromObject constructs a schema from a parsed JSON object.
|
||
|
// Due to the complexity of the schema representation, this is a
|
||
|
// custom reader and not the standard Go JSON reader (encoding/json).
|
||
|
func NewSchemaFromObject(jsonData *yaml.Node) *Schema {
|
||
|
switch jsonData.Kind {
|
||
|
case yaml.DocumentNode:
|
||
|
return NewSchemaFromObject(jsonData.Content[0])
|
||
|
case yaml.MappingNode:
|
||
|
schema := &Schema{}
|
||
|
|
||
|
for i := 0; i < len(jsonData.Content); i += 2 {
|
||
|
k := jsonData.Content[i].Value
|
||
|
v := jsonData.Content[i+1]
|
||
|
|
||
|
switch k {
|
||
|
case "$schema":
|
||
|
schema.Schema = schema.stringValue(v)
|
||
|
case "id":
|
||
|
schema.ID = schema.stringValue(v)
|
||
|
|
||
|
case "multipleOf":
|
||
|
schema.MultipleOf = schema.numberValue(v)
|
||
|
case "maximum":
|
||
|
schema.Maximum = schema.numberValue(v)
|
||
|
case "exclusiveMaximum":
|
||
|
schema.ExclusiveMaximum = schema.boolValue(v)
|
||
|
case "minimum":
|
||
|
schema.Minimum = schema.numberValue(v)
|
||
|
case "exclusiveMinimum":
|
||
|
schema.ExclusiveMinimum = schema.boolValue(v)
|
||
|
|
||
|
case "maxLength":
|
||
|
schema.MaxLength = schema.intValue(v)
|
||
|
case "minLength":
|
||
|
schema.MinLength = schema.intValue(v)
|
||
|
case "pattern":
|
||
|
schema.Pattern = schema.stringValue(v)
|
||
|
|
||
|
case "additionalItems":
|
||
|
schema.AdditionalItems = schema.schemaOrBooleanValue(v)
|
||
|
case "items":
|
||
|
schema.Items = schema.schemaOrSchemaArrayValue(v)
|
||
|
case "maxItems":
|
||
|
schema.MaxItems = schema.intValue(v)
|
||
|
case "minItems":
|
||
|
schema.MinItems = schema.intValue(v)
|
||
|
case "uniqueItems":
|
||
|
schema.UniqueItems = schema.boolValue(v)
|
||
|
|
||
|
case "maxProperties":
|
||
|
schema.MaxProperties = schema.intValue(v)
|
||
|
case "minProperties":
|
||
|
schema.MinProperties = schema.intValue(v)
|
||
|
case "required":
|
||
|
schema.Required = schema.arrayOfStringsValue(v)
|
||
|
case "additionalProperties":
|
||
|
schema.AdditionalProperties = schema.schemaOrBooleanValue(v)
|
||
|
case "properties":
|
||
|
schema.Properties = schema.mapOfSchemasValue(v)
|
||
|
case "patternProperties":
|
||
|
schema.PatternProperties = schema.mapOfSchemasValue(v)
|
||
|
case "dependencies":
|
||
|
schema.Dependencies = schema.mapOfSchemasOrStringArraysValue(v)
|
||
|
|
||
|
case "enum":
|
||
|
schema.Enumeration = schema.arrayOfEnumValuesValue(v)
|
||
|
|
||
|
case "type":
|
||
|
schema.Type = schema.stringOrStringArrayValue(v)
|
||
|
case "allOf":
|
||
|
schema.AllOf = schema.arrayOfSchemasValue(v)
|
||
|
case "anyOf":
|
||
|
schema.AnyOf = schema.arrayOfSchemasValue(v)
|
||
|
case "oneOf":
|
||
|
schema.OneOf = schema.arrayOfSchemasValue(v)
|
||
|
case "not":
|
||
|
schema.Not = NewSchemaFromObject(v)
|
||
|
case "definitions":
|
||
|
schema.Definitions = schema.mapOfSchemasValue(v)
|
||
|
|
||
|
case "title":
|
||
|
schema.Title = schema.stringValue(v)
|
||
|
case "description":
|
||
|
schema.Description = schema.stringValue(v)
|
||
|
|
||
|
case "default":
|
||
|
schema.Default = v
|
||
|
|
||
|
case "format":
|
||
|
schema.Format = schema.stringValue(v)
|
||
|
case "$ref":
|
||
|
schema.Ref = schema.stringValue(v)
|
||
|
default:
|
||
|
fmt.Printf("UNSUPPORTED (%s)\n", k)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// insert schema in global map
|
||
|
if schema.ID != nil {
|
||
|
if schemas == nil {
|
||
|
schemas = make(map[string]*Schema, 0)
|
||
|
}
|
||
|
schemas[*(schema.ID)] = schema
|
||
|
}
|
||
|
return schema
|
||
|
|
||
|
default:
|
||
|
fmt.Printf("schemaValue: unexpected node %+v\n", jsonData)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
//
|
||
|
// BUILDERS
|
||
|
// The following methods build elements of Schemas from interface{} values.
|
||
|
// Each returns nil if it is unable to build the desired element.
|
||
|
//
|
||
|
|
||
|
// Gets the string value of an interface{} value if possible.
|
||
|
func (schema *Schema) stringValue(v *yaml.Node) *string {
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
return &v.Value
|
||
|
default:
|
||
|
fmt.Printf("stringValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets the numeric value of an interface{} value if possible.
|
||
|
func (schema *Schema) numberValue(v *yaml.Node) *SchemaNumber {
|
||
|
number := &SchemaNumber{}
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
switch v.Tag {
|
||
|
case "!!float":
|
||
|
v2, _ := strconv.ParseFloat(v.Value, 64)
|
||
|
number.Float = &v2
|
||
|
return number
|
||
|
case "!!int":
|
||
|
v2, _ := strconv.ParseInt(v.Value, 10, 64)
|
||
|
number.Integer = &v2
|
||
|
return number
|
||
|
default:
|
||
|
fmt.Printf("stringValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
default:
|
||
|
fmt.Printf("stringValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets the integer value of an interface{} value if possible.
|
||
|
func (schema *Schema) intValue(v *yaml.Node) *int64 {
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
switch v.Tag {
|
||
|
case "!!float":
|
||
|
v2, _ := strconv.ParseFloat(v.Value, 64)
|
||
|
v3 := int64(v2)
|
||
|
return &v3
|
||
|
case "!!int":
|
||
|
v2, _ := strconv.ParseInt(v.Value, 10, 64)
|
||
|
return &v2
|
||
|
default:
|
||
|
fmt.Printf("intValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
default:
|
||
|
fmt.Printf("intValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets the bool value of an interface{} value if possible.
|
||
|
func (schema *Schema) boolValue(v *yaml.Node) *bool {
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
switch v.Tag {
|
||
|
case "!!bool":
|
||
|
v2, _ := strconv.ParseBool(v.Value)
|
||
|
return &v2
|
||
|
default:
|
||
|
fmt.Printf("boolValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
default:
|
||
|
fmt.Printf("boolValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets a map of Schemas from an interface{} value if possible.
|
||
|
func (schema *Schema) mapOfSchemasValue(v *yaml.Node) *[]*NamedSchema {
|
||
|
switch v.Kind {
|
||
|
case yaml.MappingNode:
|
||
|
m := make([]*NamedSchema, 0)
|
||
|
for i := 0; i < len(v.Content); i += 2 {
|
||
|
k2 := v.Content[i].Value
|
||
|
v2 := v.Content[i+1]
|
||
|
pair := &NamedSchema{Name: k2, Value: NewSchemaFromObject(v2)}
|
||
|
m = append(m, pair)
|
||
|
}
|
||
|
return &m
|
||
|
default:
|
||
|
fmt.Printf("mapOfSchemasValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets an array of Schemas from an interface{} value if possible.
|
||
|
func (schema *Schema) arrayOfSchemasValue(v *yaml.Node) *[]*Schema {
|
||
|
switch v.Kind {
|
||
|
case yaml.SequenceNode:
|
||
|
m := make([]*Schema, 0)
|
||
|
for _, v2 := range v.Content {
|
||
|
switch v2.Kind {
|
||
|
case yaml.MappingNode:
|
||
|
s := NewSchemaFromObject(v2)
|
||
|
m = append(m, s)
|
||
|
default:
|
||
|
fmt.Printf("arrayOfSchemasValue: unexpected node %+v\n", v2)
|
||
|
}
|
||
|
}
|
||
|
return &m
|
||
|
case yaml.MappingNode:
|
||
|
m := make([]*Schema, 0)
|
||
|
s := NewSchemaFromObject(v)
|
||
|
m = append(m, s)
|
||
|
return &m
|
||
|
default:
|
||
|
fmt.Printf("arrayOfSchemasValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets a Schema or an array of Schemas from an interface{} value if possible.
|
||
|
func (schema *Schema) schemaOrSchemaArrayValue(v *yaml.Node) *SchemaOrSchemaArray {
|
||
|
switch v.Kind {
|
||
|
case yaml.SequenceNode:
|
||
|
m := make([]*Schema, 0)
|
||
|
for _, v2 := range v.Content {
|
||
|
switch v2.Kind {
|
||
|
case yaml.MappingNode:
|
||
|
s := NewSchemaFromObject(v2)
|
||
|
m = append(m, s)
|
||
|
default:
|
||
|
fmt.Printf("schemaOrSchemaArrayValue: unexpected node %+v\n", v2)
|
||
|
}
|
||
|
}
|
||
|
return &SchemaOrSchemaArray{SchemaArray: &m}
|
||
|
case yaml.MappingNode:
|
||
|
s := NewSchemaFromObject(v)
|
||
|
return &SchemaOrSchemaArray{Schema: s}
|
||
|
default:
|
||
|
fmt.Printf("schemaOrSchemaArrayValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets an array of strings from an interface{} value if possible.
|
||
|
func (schema *Schema) arrayOfStringsValue(v *yaml.Node) *[]string {
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
a := []string{v.Value}
|
||
|
return &a
|
||
|
case yaml.SequenceNode:
|
||
|
a := make([]string, 0)
|
||
|
for _, v2 := range v.Content {
|
||
|
switch v2.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
a = append(a, v2.Value)
|
||
|
default:
|
||
|
fmt.Printf("arrayOfStringsValue: unexpected node %+v\n", v2)
|
||
|
}
|
||
|
}
|
||
|
return &a
|
||
|
default:
|
||
|
fmt.Printf("arrayOfStringsValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets a string or an array of strings from an interface{} value if possible.
|
||
|
func (schema *Schema) stringOrStringArrayValue(v *yaml.Node) *StringOrStringArray {
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
s := &StringOrStringArray{}
|
||
|
s.String = &v.Value
|
||
|
return s
|
||
|
case yaml.SequenceNode:
|
||
|
a := make([]string, 0)
|
||
|
for _, v2 := range v.Content {
|
||
|
switch v2.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
a = append(a, v2.Value)
|
||
|
default:
|
||
|
fmt.Printf("arrayOfStringsValue: unexpected node %+v\n", v2)
|
||
|
}
|
||
|
}
|
||
|
s := &StringOrStringArray{}
|
||
|
s.StringArray = &a
|
||
|
return s
|
||
|
default:
|
||
|
fmt.Printf("arrayOfStringsValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Gets an array of enum values from an interface{} value if possible.
|
||
|
func (schema *Schema) arrayOfEnumValuesValue(v *yaml.Node) *[]SchemaEnumValue {
|
||
|
a := make([]SchemaEnumValue, 0)
|
||
|
switch v.Kind {
|
||
|
case yaml.SequenceNode:
|
||
|
for _, v2 := range v.Content {
|
||
|
switch v2.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
switch v2.Tag {
|
||
|
case "!!str":
|
||
|
a = append(a, SchemaEnumValue{String: &v2.Value})
|
||
|
case "!!bool":
|
||
|
v3, _ := strconv.ParseBool(v2.Value)
|
||
|
a = append(a, SchemaEnumValue{Bool: &v3})
|
||
|
default:
|
||
|
fmt.Printf("arrayOfEnumValuesValue: unexpected type %s\n", v2.Tag)
|
||
|
}
|
||
|
default:
|
||
|
fmt.Printf("arrayOfEnumValuesValue: unexpected node %+v\n", v2)
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
fmt.Printf("arrayOfEnumValuesValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return &a
|
||
|
}
|
||
|
|
||
|
// Gets a map of schemas or string arrays from an interface{} value if possible.
|
||
|
func (schema *Schema) mapOfSchemasOrStringArraysValue(v *yaml.Node) *[]*NamedSchemaOrStringArray {
|
||
|
m := make([]*NamedSchemaOrStringArray, 0)
|
||
|
switch v.Kind {
|
||
|
case yaml.MappingNode:
|
||
|
for i := 0; i < len(v.Content); i += 2 {
|
||
|
k2 := v.Content[i].Value
|
||
|
v2 := v.Content[i+1]
|
||
|
switch v2.Kind {
|
||
|
case yaml.SequenceNode:
|
||
|
a := make([]string, 0)
|
||
|
for _, v3 := range v2.Content {
|
||
|
switch v3.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
a = append(a, v3.Value)
|
||
|
default:
|
||
|
fmt.Printf("mapOfSchemasOrStringArraysValue: unexpected node %+v\n", v3)
|
||
|
}
|
||
|
}
|
||
|
s := &SchemaOrStringArray{}
|
||
|
s.StringArray = &a
|
||
|
pair := &NamedSchemaOrStringArray{Name: k2, Value: s}
|
||
|
m = append(m, pair)
|
||
|
default:
|
||
|
fmt.Printf("mapOfSchemasOrStringArraysValue: unexpected node %+v\n", v2)
|
||
|
}
|
||
|
}
|
||
|
default:
|
||
|
fmt.Printf("mapOfSchemasOrStringArraysValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return &m
|
||
|
}
|
||
|
|
||
|
// Gets a schema or a boolean value from an interface{} value if possible.
|
||
|
func (schema *Schema) schemaOrBooleanValue(v *yaml.Node) *SchemaOrBoolean {
|
||
|
schemaOrBoolean := &SchemaOrBoolean{}
|
||
|
switch v.Kind {
|
||
|
case yaml.ScalarNode:
|
||
|
v2, _ := strconv.ParseBool(v.Value)
|
||
|
schemaOrBoolean.Boolean = &v2
|
||
|
case yaml.MappingNode:
|
||
|
schemaOrBoolean.Schema = NewSchemaFromObject(v)
|
||
|
default:
|
||
|
fmt.Printf("schemaOrBooleanValue: unexpected node %+v\n", v)
|
||
|
}
|
||
|
return schemaOrBoolean
|
||
|
}
|