// 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" "gopkg.in/yaml.v3" ) const indentation = " " func renderMappingNode(node *yaml.Node, indent string) (result string) { result = "{\n" innerIndent := indent + indentation for i := 0; i < len(node.Content); i += 2 { // first print the key key := node.Content[i].Value result += fmt.Sprintf("%s\"%+v\": ", innerIndent, key) // then the value value := node.Content[i+1] switch value.Kind { case yaml.ScalarNode: result += "\"" + value.Value + "\"" case yaml.MappingNode: result += renderMappingNode(value, innerIndent) case yaml.SequenceNode: result += renderSequenceNode(value, innerIndent) default: result += fmt.Sprintf("???MapItem(Key:%+v, Value:%T)", value, value) } if i < len(node.Content)-2 { result += "," } result += "\n" } result += indent + "}" return result } func renderSequenceNode(node *yaml.Node, indent string) (result string) { result = "[\n" innerIndent := indent + indentation for i := 0; i < len(node.Content); i++ { item := node.Content[i] switch item.Kind { case yaml.ScalarNode: result += innerIndent + "\"" + item.Value + "\"" case yaml.MappingNode: result += innerIndent + renderMappingNode(item, innerIndent) + "" default: result += innerIndent + fmt.Sprintf("???ArrayItem(%+v)", item) } if i < len(node.Content)-1 { result += "," } result += "\n" } result += indent + "]" return result } func renderStringArray(array []string, indent string) (result string) { result = "[\n" innerIndent := indent + indentation for i, item := range array { result += innerIndent + "\"" + item + "\"" if i < len(array)-1 { result += "," } result += "\n" } result += indent + "]" return result } // Render renders a yaml.Node as JSON func Render(node *yaml.Node) string { if node.Kind == yaml.DocumentNode { if len(node.Content) == 1 { return Render(node.Content[0]) } } else if node.Kind == yaml.MappingNode { return renderMappingNode(node, "") + "\n" } else if node.Kind == yaml.SequenceNode { return renderSequenceNode(node, "") + "\n" } return "" } func (object *SchemaNumber) nodeValue() *yaml.Node { if object.Integer != nil { return nodeForInt64(*object.Integer) } else if object.Float != nil { return nodeForFloat64(*object.Float) } else { return nil } } func (object *SchemaOrBoolean) nodeValue() *yaml.Node { if object.Schema != nil { return object.Schema.nodeValue() } else if object.Boolean != nil { return nodeForBoolean(*object.Boolean) } else { return nil } } func nodeForStringArray(array []string) *yaml.Node { content := make([]*yaml.Node, 0) for _, item := range array { content = append(content, nodeForString(item)) } return nodeForSequence(content) } func nodeForSchemaArray(array []*Schema) *yaml.Node { content := make([]*yaml.Node, 0) for _, item := range array { content = append(content, item.nodeValue()) } return nodeForSequence(content) } func (object *StringOrStringArray) nodeValue() *yaml.Node { if object.String != nil { return nodeForString(*object.String) } else if object.StringArray != nil { return nodeForStringArray(*(object.StringArray)) } else { return nil } } func (object *SchemaOrStringArray) nodeValue() *yaml.Node { if object.Schema != nil { return object.Schema.nodeValue() } else if object.StringArray != nil { return nodeForStringArray(*(object.StringArray)) } else { return nil } } func (object *SchemaOrSchemaArray) nodeValue() *yaml.Node { if object.Schema != nil { return object.Schema.nodeValue() } else if object.SchemaArray != nil { return nodeForSchemaArray(*(object.SchemaArray)) } else { return nil } } func (object *SchemaEnumValue) nodeValue() *yaml.Node { if object.String != nil { return nodeForString(*object.String) } else if object.Bool != nil { return nodeForBoolean(*object.Bool) } else { return nil } } func nodeForNamedSchemaArray(array *[]*NamedSchema) *yaml.Node { content := make([]*yaml.Node, 0) for _, pair := range *(array) { content = appendPair(content, pair.Name, pair.Value.nodeValue()) } return nodeForMapping(content) } func nodeForNamedSchemaOrStringArray(array *[]*NamedSchemaOrStringArray) *yaml.Node { content := make([]*yaml.Node, 0) for _, pair := range *(array) { content = appendPair(content, pair.Name, pair.Value.nodeValue()) } return nodeForMapping(content) } func nodeForSchemaEnumArray(array *[]SchemaEnumValue) *yaml.Node { content := make([]*yaml.Node, 0) for _, item := range *array { content = append(content, item.nodeValue()) } return nodeForSequence(content) } func nodeForMapping(content []*yaml.Node) *yaml.Node { return &yaml.Node{ Kind: yaml.MappingNode, Content: content, } } func nodeForSequence(content []*yaml.Node) *yaml.Node { return &yaml.Node{ Kind: yaml.SequenceNode, Content: content, } } func nodeForString(value string) *yaml.Node { return &yaml.Node{ Kind: yaml.ScalarNode, Tag: "!!str", Value: value, } } func nodeForBoolean(value bool) *yaml.Node { return &yaml.Node{ Kind: yaml.ScalarNode, Tag: "!!bool", Value: fmt.Sprintf("%t", value), } } func nodeForInt64(value int64) *yaml.Node { return &yaml.Node{ Kind: yaml.ScalarNode, Tag: "!!int", Value: fmt.Sprintf("%d", value), } } func nodeForFloat64(value float64) *yaml.Node { return &yaml.Node{ Kind: yaml.ScalarNode, Tag: "!!float", Value: fmt.Sprintf("%f", value), } } func appendPair(nodes []*yaml.Node, name string, value *yaml.Node) []*yaml.Node { nodes = append(nodes, nodeForString(name)) nodes = append(nodes, value) return nodes } func (schema *Schema) nodeValue() *yaml.Node { n := &yaml.Node{Kind: yaml.MappingNode} content := make([]*yaml.Node, 0) if schema.Title != nil { content = appendPair(content, "title", nodeForString(*schema.Title)) } if schema.ID != nil { content = appendPair(content, "id", nodeForString(*schema.ID)) } if schema.Schema != nil { content = appendPair(content, "$schema", nodeForString(*schema.Schema)) } if schema.Type != nil { content = appendPair(content, "type", schema.Type.nodeValue()) } if schema.Items != nil { content = appendPair(content, "items", schema.Items.nodeValue()) } if schema.Description != nil { content = appendPair(content, "description", nodeForString(*schema.Description)) } if schema.Required != nil { content = appendPair(content, "required", nodeForStringArray(*schema.Required)) } if schema.AdditionalProperties != nil { content = appendPair(content, "additionalProperties", schema.AdditionalProperties.nodeValue()) } if schema.PatternProperties != nil { content = appendPair(content, "patternProperties", nodeForNamedSchemaArray(schema.PatternProperties)) } if schema.Properties != nil { content = appendPair(content, "properties", nodeForNamedSchemaArray(schema.Properties)) } if schema.Dependencies != nil { content = appendPair(content, "dependencies", nodeForNamedSchemaOrStringArray(schema.Dependencies)) } if schema.Ref != nil { content = appendPair(content, "$ref", nodeForString(*schema.Ref)) } if schema.MultipleOf != nil { content = appendPair(content, "multipleOf", schema.MultipleOf.nodeValue()) } if schema.Maximum != nil { content = appendPair(content, "maximum", schema.Maximum.nodeValue()) } if schema.ExclusiveMaximum != nil { content = appendPair(content, "exclusiveMaximum", nodeForBoolean(*schema.ExclusiveMaximum)) } if schema.Minimum != nil { content = appendPair(content, "minimum", schema.Minimum.nodeValue()) } if schema.ExclusiveMinimum != nil { content = appendPair(content, "exclusiveMinimum", nodeForBoolean(*schema.ExclusiveMinimum)) } if schema.MaxLength != nil { content = appendPair(content, "maxLength", nodeForInt64(*schema.MaxLength)) } if schema.MinLength != nil { content = appendPair(content, "minLength", nodeForInt64(*schema.MinLength)) } if schema.Pattern != nil { content = appendPair(content, "pattern", nodeForString(*schema.Pattern)) } if schema.AdditionalItems != nil { content = appendPair(content, "additionalItems", schema.AdditionalItems.nodeValue()) } if schema.MaxItems != nil { content = appendPair(content, "maxItems", nodeForInt64(*schema.MaxItems)) } if schema.MinItems != nil { content = appendPair(content, "minItems", nodeForInt64(*schema.MinItems)) } if schema.UniqueItems != nil { content = appendPair(content, "uniqueItems", nodeForBoolean(*schema.UniqueItems)) } if schema.MaxProperties != nil { content = appendPair(content, "maxProperties", nodeForInt64(*schema.MaxProperties)) } if schema.MinProperties != nil { content = appendPair(content, "minProperties", nodeForInt64(*schema.MinProperties)) } if schema.Enumeration != nil { content = appendPair(content, "enum", nodeForSchemaEnumArray(schema.Enumeration)) } if schema.AllOf != nil { content = appendPair(content, "allOf", nodeForSchemaArray(*schema.AllOf)) } if schema.AnyOf != nil { content = appendPair(content, "anyOf", nodeForSchemaArray(*schema.AnyOf)) } if schema.OneOf != nil { content = appendPair(content, "oneOf", nodeForSchemaArray(*schema.OneOf)) } if schema.Not != nil { content = appendPair(content, "not", schema.Not.nodeValue()) } if schema.Definitions != nil { content = appendPair(content, "definitions", nodeForNamedSchemaArray(schema.Definitions)) } if schema.Default != nil { // m = append(m, yaml.MapItem{Key: "default", Value: *schema.Default}) } if schema.Format != nil { content = appendPair(content, "format", nodeForString(*schema.Format)) } n.Content = content return n } // JSONString returns a json representation of a schema. func (schema *Schema) JSONString() string { node := schema.nodeValue() return Render(node) }