// 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. // schema-generator is a support tool that generates the OpenAPI v3 JSON schema. // Yes, it's gross, but the OpenAPI 3 spec, which defines REST APIs with a // rigorous JSON schema, is itself defined with a Markdown file. Ironic? package main import ( "encoding/json" "fmt" "io/ioutil" "log" "os" "regexp" "sort" "strings" "unicode" "unicode/utf8" "github.com/googleapis/gnostic/jsonschema" ) // convert the first character of a string to lower case func lowerFirst(s string) string { if s == "" { return "" } r, n := utf8.DecodeRuneInString(s) return string(unicode.ToLower(r)) + s[n:] } // Section models a section of the OpenAPI specification text document. type Section struct { Level int Text string Title string Children []*Section } // ReadSection reads a section of the OpenAPI Specification, recursively dividing it into subsections func ReadSection(text string, level int) (section *Section) { titlePattern := regexp.MustCompile("^" + strings.Repeat("#", level) + " .*$") subtitlePattern := regexp.MustCompile("^" + strings.Repeat("#", level+1) + " .*$") section = &Section{Level: level, Text: text} lines := strings.Split(string(text), "\n") subsection := "" for i, line := range lines { if i == 0 && titlePattern.Match([]byte(line)) { section.Title = line } else if subtitlePattern.Match([]byte(line)) { // we've found a subsection title. // if there's a subsection that we've already been reading, save it if len(subsection) != 0 { child := ReadSection(subsection, level+1) section.Children = append(section.Children, child) } // start a new subsection subsection = line + "\n" } else { // add to the subsection we've been reading subsection += line + "\n" } } // if this section has subsections, save the last one if len(section.Children) > 0 { child := ReadSection(subsection, level+1) section.Children = append(section.Children, child) } return } // Display recursively displays a section of the specification. func (s *Section) Display(section string) { if len(s.Children) == 0 { //fmt.Printf("%s\n", s.Text) } else { for i, child := range s.Children { var subsection string if section == "" { subsection = fmt.Sprintf("%d", i) } else { subsection = fmt.Sprintf("%s.%d", section, i) } fmt.Printf("%-12s %s\n", subsection, child.NiceTitle()) child.Display(subsection) } } } // remove a link from a string, leaving only the text that follows it // if there is no link, just return the string func stripLink(input string) (output string) { stringPattern := regexp.MustCompile("^(.*)$") stringWithLinkPattern := regexp.MustCompile("^(.*)$") if matches := stringWithLinkPattern.FindSubmatch([]byte(input)); matches != nil { return string(matches[1]) } else if matches := stringPattern.FindSubmatch([]byte(input)); matches != nil { return string(matches[1]) } else { return input } } // NiceTitle returns a nice-to-display title for a section by removing the opening "###" and any links. func (s *Section) NiceTitle() string { titlePattern := regexp.MustCompile("^#+ (.*)$") titleWithLinkPattern := regexp.MustCompile("^#+ (.*)$") if matches := titleWithLinkPattern.FindSubmatch([]byte(s.Title)); matches != nil { return string(matches[1]) } else if matches := titlePattern.FindSubmatch([]byte(s.Title)); matches != nil { return string(matches[1]) } else { return "" } } // replace markdown links with their link text (removing the URL part) func removeMarkdownLinks(input string) (output string) { markdownLink := regexp.MustCompile("\\[([^\\]\\[]*)\\]\\(([^\\)]*)\\)") // matches [link title](link url) output = string(markdownLink.ReplaceAll([]byte(input), []byte("$1"))) return } // extract the fixed fields from a table in a section func parseFixedFields(input string, schemaObject *SchemaObject) { lines := strings.Split(input, "\n") for _, line := range lines { // replace escaped bars with "OR", assuming these are used to describe union types line = strings.Replace(line, " \\| ", " OR ", -1) // split the table on the remaining bars parts := strings.Split(line, "|") if len(parts) > 1 { fieldName := strings.Trim(stripLink(parts[0]), " ") if fieldName != "Field Name" && fieldName != "---" { if len(parts) == 3 || len(parts) == 4 { // this is what we expect } else { log.Printf("ERROR: %+v", parts) } typeName := parts[1] typeName = strings.Replace(typeName, "{expression}", "Expression", -1) typeName = strings.Trim(typeName, " ") typeName = strings.Replace(typeName, "`", "", -1) typeName = removeMarkdownLinks(typeName) typeName = strings.Replace(typeName, " ", "", -1) typeName = strings.Replace(typeName, "Object", "", -1) isArray := false if typeName[0] == '[' && typeName[len(typeName)-1] == ']' { typeName = typeName[1 : len(typeName)-1] isArray = true } isMap := false mapPattern := regexp.MustCompile("^Mapstring,\\[(.*)\\]$") if matches := mapPattern.FindSubmatch([]byte(typeName)); matches != nil { typeName = string(matches[1]) isMap = true } else { // match map[string,] mapPattern2 := regexp.MustCompile("^Map\\[string,(.+)\\]$") if matches := mapPattern2.FindSubmatch([]byte(typeName)); matches != nil { typeName = string(matches[1]) isMap = true } } description := strings.Trim(parts[len(parts)-1], " ") description = removeMarkdownLinks(description) description = strings.Replace(description, "\n", " ", -1) requiredLabel1 := "**Required.** " requiredLabel2 := "**REQUIRED**." if strings.Contains(description, requiredLabel1) || strings.Contains(description, requiredLabel2) { // only include required values if their "Validity" is "Any" or if no validity is specified valid := true if len(parts) == 4 { validity := parts[2] if strings.Contains(validity, "Any") { valid = true } else { valid = false } } if valid { schemaObject.RequiredFields = append(schemaObject.RequiredFields, fieldName) } description = strings.Replace(description, requiredLabel1, "", -1) description = strings.Replace(description, requiredLabel2, "", -1) } schemaField := SchemaObjectField{ Name: fieldName, Type: typeName, IsArray: isArray, IsMap: isMap, Description: description, } schemaObject.FixedFields = append(schemaObject.FixedFields, schemaField) } } } } // extract the patterned fields from a table in a section func parsePatternedFields(input string, schemaObject *SchemaObject) { lines := strings.Split(input, "\n") for _, line := range lines { line = strings.Replace(line, " \\| ", " OR ", -1) parts := strings.Split(line, "|") if len(parts) > 1 { fieldName := strings.Trim(stripLink(parts[0]), " ") fieldName = removeMarkdownLinks(fieldName) if fieldName == "HTTP Status Code" { fieldName = "^([0-9X]{3})$" } if fieldName != "Field Pattern" && fieldName != "---" { typeName := parts[1] typeName = strings.Trim(typeName, " ") typeName = strings.Replace(typeName, "`", "", -1) typeName = removeMarkdownLinks(typeName) typeName = strings.Replace(typeName, " ", "", -1) typeName = strings.Replace(typeName, "Object", "", -1) typeName = strings.Replace(typeName, "{expression}", "Expression", -1) isArray := false if typeName[0] == '[' && typeName[len(typeName)-1] == ']' { typeName = typeName[1 : len(typeName)-1] isArray = true } isMap := false mapPattern := regexp.MustCompile("^Mapstring,\\[(.*)\\]$") if matches := mapPattern.FindSubmatch([]byte(typeName)); matches != nil { typeName = string(matches[1]) isMap = true } description := strings.Trim(parts[len(parts)-1], " ") description = removeMarkdownLinks(description) description = strings.Replace(description, "\n", " ", -1) schemaField := SchemaObjectField{ Name: fieldName, Type: typeName, IsArray: isArray, IsMap: isMap, Description: description, } schemaObject.PatternedFields = append(schemaObject.PatternedFields, schemaField) } } } } // SchemaObjectField describes a field of a schema. type SchemaObjectField struct { Name string `json:"name"` Type string `json:"type"` IsArray bool `json:"is_array"` IsMap bool `json:"is_map"` Description string `json:"description"` } // SchemaObject describes a schema. type SchemaObject struct { Name string `json:"name"` ID string `json:"id"` Description string `json:"description"` Extendable bool `json:"extendable"` RequiredFields []string `json:"required"` FixedFields []SchemaObjectField `json:"fixed"` PatternedFields []SchemaObjectField `json:"patterned"` } // SchemaModel is a collection of schemas. type SchemaModel struct { Objects []SchemaObject } func (m *SchemaModel) objectWithID(id string) *SchemaObject { for _, object := range m.Objects { if object.ID == id { return &object } } return nil } // NewSchemaModel returns a new SchemaModel. func NewSchemaModel(filename string) (schemaModel *SchemaModel, err error) { b, err := ioutil.ReadFile(filename) if err != nil { return nil, err } // divide the specification into sections document := ReadSection(string(b), 1) document.Display("") // read object names and their details specification := document.Children[4] // fragile! the section title is "Specification" schema := specification.Children[7] // fragile! the section title is "Schema" anchor := regexp.MustCompile("^#### 0 { description := section.Children[0].Text description = removeMarkdownLinks(description) description = strings.Trim(description, " \t\n") description = strings.Replace(description, "\n", " ", -1) schemaObject.Description = description } // is the object extendable? if strings.Contains(section.Text, "Specification Extensions") { schemaObject.Extendable = true } // look for fixed fields for _, child := range section.Children { if child.NiceTitle() == "Fixed Fields" { parseFixedFields(child.Text, &schemaObject) } } // look for patterned fields for _, child := range section.Children { if child.NiceTitle() == "Patterned Fields" { parsePatternedFields(child.Text, &schemaObject) } } schemaObjects = append(schemaObjects, schemaObject) } } return &SchemaModel{Objects: schemaObjects}, nil } // UnionType represents a union of two types. type UnionType struct { Name string ObjectType1 string ObjectType2 string } var unionTypes map[string]*UnionType func noteUnionType(typeName, objectType1, objectType2 string) { if unionTypes == nil { unionTypes = make(map[string]*UnionType, 0) } unionTypes[typeName] = &UnionType{ Name: typeName, ObjectType1: objectType1, ObjectType2: objectType2, } } // MapType represents a map of a specified type (with string keys). type MapType struct { Name string ObjectType string } var mapTypes map[string]*MapType func noteMapType(typeName, objectType string) { if mapTypes == nil { mapTypes = make(map[string]*MapType, 0) } mapTypes[typeName] = &MapType{ Name: typeName, ObjectType: objectType, } } func definitionNameForType(typeName string) string { name := typeName switch typeName { case "OAuthFlows": name = "oauthFlows" case "OAuthFlow": name = "oauthFlow" case "XML": name = "xml" case "ExternalDocumentation": name = "externalDocs" default: // does the name contain an "OR" if parts := strings.Split(typeName, "OR"); len(parts) > 1 { name = lowerFirst(parts[0]) + "Or" + parts[1] noteUnionType(name, parts[0], parts[1]) } else { name = lowerFirst(typeName) } } return "#/definitions/" + name } func pluralize(name string) string { if name == "any" { return "anys" } switch name[len(name)-1] { case 'y': name = name[0:len(name)-1] + "ies" case 's': name = name + "Map" default: name = name + "s" } return name } func definitionNameForMapOfType(typeName string) string { // pluralize the type name to get the name of an object representing a map of them var elementTypeName string var mapTypeName string if parts := strings.Split(typeName, "OR"); len(parts) > 1 { elementTypeName = lowerFirst(parts[0]) + "Or" + parts[1] noteUnionType(elementTypeName, parts[0], parts[1]) mapTypeName = pluralize(lowerFirst(parts[0])) + "Or" + pluralize(parts[1]) } else { elementTypeName = lowerFirst(typeName) mapTypeName = pluralize(elementTypeName) } noteMapType(mapTypeName, elementTypeName) return "#/definitions/" + mapTypeName } func updateSchemaFieldWithModelField(schemaField *jsonschema.Schema, modelField *SchemaObjectField) { // fmt.Printf("IN %s:%+v\n", name, schemaField) // update the attributes of the schema field if modelField.IsArray { // is array itemSchema := &jsonschema.Schema{} switch modelField.Type { case "string": itemSchema.Type = jsonschema.NewStringOrStringArrayWithString("string") case "boolean": itemSchema.Type = jsonschema.NewStringOrStringArrayWithString("boolean") case "primitive": itemSchema.Ref = stringptr(definitionNameForType("Primitive")) default: itemSchema.Ref = stringptr(definitionNameForType(modelField.Type)) } schemaField.Items = jsonschema.NewSchemaOrSchemaArrayWithSchema(itemSchema) schemaField.Type = jsonschema.NewStringOrStringArrayWithString("array") boolValue := true // not sure about this schemaField.UniqueItems = &boolValue } else if modelField.IsMap { schemaField.Ref = stringptr(definitionNameForMapOfType(modelField.Type)) } else { // is scalar switch modelField.Type { case "string": schemaField.Type = jsonschema.NewStringOrStringArrayWithString("string") case "boolean": schemaField.Type = jsonschema.NewStringOrStringArrayWithString("boolean") case "primitive": schemaField.Ref = stringptr(definitionNameForType("Primitive")) default: schemaField.Ref = stringptr(definitionNameForType(modelField.Type)) } } } func buildSchemaWithModel(modelObject *SchemaObject) (schema *jsonschema.Schema) { schema = &jsonschema.Schema{} schema.Type = jsonschema.NewStringOrStringArrayWithString("object") if modelObject.RequiredFields != nil && len(modelObject.RequiredFields) > 0 { // copy array arrayCopy := modelObject.RequiredFields schema.Required = &arrayCopy } schema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(false) schema.Description = stringptr(modelObject.Description) // handle fixed fields if modelObject.FixedFields != nil { newNamedSchemas := make([]*jsonschema.NamedSchema, 0) for _, modelField := range modelObject.FixedFields { schemaField := schema.PropertyWithName(modelField.Name) if schemaField == nil { // create and add the schema field schemaField = &jsonschema.Schema{} namedSchema := &jsonschema.NamedSchema{Name: modelField.Name, Value: schemaField} newNamedSchemas = append(newNamedSchemas, namedSchema) } updateSchemaFieldWithModelField(schemaField, &modelField) } for _, pair := range newNamedSchemas { if schema.Properties == nil { properties := make([]*jsonschema.NamedSchema, 0) schema.Properties = &properties } *(schema.Properties) = append(*(schema.Properties), pair) } } else { if schema.Properties != nil { fmt.Printf("SCHEMA SHOULD NOT HAVE PROPERTIES %s\n", modelObject.ID) } } // handle patterned fields if modelObject.PatternedFields != nil { newNamedSchemas := make([]*jsonschema.NamedSchema, 0) for _, modelField := range modelObject.PatternedFields { schemaField := schema.PatternPropertyWithName(modelField.Name) if schemaField == nil { // create and add the schema field schemaField = &jsonschema.Schema{} // Component names should match "^[a-zA-Z0-9\.\-_]+$" // See https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#componentsObject nameRegex := "^[a-zA-Z0-9\\\\.\\\\-_]+$" if modelObject.Name == "Scopes Object" { nameRegex = "^" } else if modelObject.Name == "Headers Object" { nameRegex = "^[a-zA-Z0-9!#\\-\\$%&'\\*\\+\\\\\\.\\^_`\\|~]+" } propertyName := strings.Replace(modelField.Name, "{name}", nameRegex, -1) // The field name MUST begin with a slash, see https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.md#paths-object // JSON Schema for OpenAPI v2 uses "^/" as regex for paths, see https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/schemas/v2.0/schema.json#L173 propertyName = strings.Replace(propertyName, "/{path}", "^/", -1) // Replace human-friendly (and regex-confusing) description with a blank pattern propertyName = strings.Replace(propertyName, "{expression}", "^", -1) propertyName = strings.Replace(propertyName, "{property}", "^", -1) namedSchema := &jsonschema.NamedSchema{Name: propertyName, Value: schemaField} newNamedSchemas = append(newNamedSchemas, namedSchema) } updateSchemaFieldWithModelField(schemaField, &modelField) } for _, pair := range newNamedSchemas { if schema.PatternProperties == nil { properties := make([]*jsonschema.NamedSchema, 0) schema.PatternProperties = &properties } *(schema.PatternProperties) = append(*(schema.PatternProperties), pair) } } else { if schema.PatternProperties != nil && !modelObject.Extendable { fmt.Printf("SCHEMA SHOULD NOT HAVE PATTERN PROPERTIES %s\n", modelObject.ID) } } if modelObject.Extendable { schemaField := schema.PatternPropertyWithName("^x-") if schemaField != nil { schemaField.Ref = stringptr("#/definitions/specificationExtension") } else { schemaField = &jsonschema.Schema{} schemaField.Ref = stringptr("#/definitions/specificationExtension") namedSchema := &jsonschema.NamedSchema{Name: "^x-", Value: schemaField} if schema.PatternProperties == nil { properties := make([]*jsonschema.NamedSchema, 0) schema.PatternProperties = &properties } *(schema.PatternProperties) = append(*(schema.PatternProperties), namedSchema) } } else { schemaField := schema.PatternPropertyWithName("^x-") if schemaField != nil { fmt.Printf("INVALID EXTENSION SUPPORT %s:%s\n", modelObject.ID, "^x-") } } return schema } // return a pointer to a copy of a passed-in string func stringptr(input string) (output *string) { return &input } func int64ptr(input int64) (output *int64) { return &input } func arrayOfSchema() *jsonschema.Schema { return &jsonschema.Schema{ Type: jsonschema.NewStringOrStringArrayWithString("array"), MinItems: int64ptr(1), Items: jsonschema.NewSchemaOrSchemaArrayWithSchema(&jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")}), } } func main() { // read and parse the text specification into a model structure model, err := NewSchemaModel("3.1.0.md") if err != nil { panic(err) } // write the model as JSON (for debugging) modelJSON, _ := json.MarshalIndent(model, "", " ") err = ioutil.WriteFile("model.json", modelJSON, 0644) if err != nil { panic(err) } // build the top-level schema using the "OAS" model oasModel := model.objectWithID("oas") if oasModel == nil { log.Printf("Unable to find OAS model. Has the source document structure changed?") os.Exit(-1) } schema := buildSchemaWithModel(oasModel) // manually set a few fields schema.Title = stringptr("A JSON Schema for OpenAPI 3.0.") schema.ID = stringptr("http://openapis.org/v3/schema.json#") schema.Schema = stringptr("http://json-schema.org/draft-04/schema#") // loop over all models and create the corresponding schema objects definitions := make([]*jsonschema.NamedSchema, 0) schema.Definitions = &definitions for _, modelObject := range model.Objects { if modelObject.ID == "oas" { continue } definitionSchema := buildSchemaWithModel(&modelObject) name := modelObject.ID if name == "externalDocumentation" { name = "externalDocs" } *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema(name, definitionSchema)) } // copy the properties of headerObject from parameterObject headerObject := schema.DefinitionWithName("header") parameterObject := schema.DefinitionWithName("parameter") if parameterObject != nil { newArray := make([]*jsonschema.NamedSchema, 0) for _, property := range *(parameterObject.Properties) { // we need to remove a few properties... if property.Name != "name" && property.Name != "in" { newArray = append(newArray, property) } } headerObject.Properties = &newArray // "So a shorthand for copying array arr would be tmp := append([]int{}, arr...)" ppArray := make([]*jsonschema.NamedSchema, 0) ppArray = append(ppArray, *(parameterObject.PatternProperties)...) headerObject.PatternProperties = &ppArray } // generate implied union types unionTypeKeys := make([]string, 0, len(unionTypes)) for key := range unionTypes { unionTypeKeys = append(unionTypeKeys, key) } sort.Strings(unionTypeKeys) for _, unionTypeKey := range unionTypeKeys { unionType := unionTypes[unionTypeKey] objectSchema := schema.DefinitionWithName(unionType.Name) if objectSchema == nil { objectSchema = &jsonschema.Schema{} oneOf := make([]*jsonschema.Schema, 0) oneOf = append(oneOf, &jsonschema.Schema{Ref: stringptr("#/definitions/" + lowerFirst(unionType.ObjectType1))}) oneOf = append(oneOf, &jsonschema.Schema{Ref: stringptr("#/definitions/" + lowerFirst(unionType.ObjectType2))}) objectSchema.OneOf = &oneOf *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema(unionType.Name, objectSchema)) } } // generate implied map types mapTypeKeys := make([]string, 0, len(mapTypes)) for key := range mapTypes { mapTypeKeys = append(mapTypeKeys, key) } sort.Strings(mapTypeKeys) for _, mapTypeKey := range mapTypeKeys { mapType := mapTypes[mapTypeKey] objectSchema := schema.DefinitionWithName(mapType.Name) if objectSchema == nil { objectSchema = &jsonschema.Schema{} objectSchema.Type = jsonschema.NewStringOrStringArrayWithString("object") additionalPropertiesSchema := &jsonschema.Schema{} if mapType.ObjectType == "string" { additionalPropertiesSchema.Type = jsonschema.NewStringOrStringArrayWithString("string") } else { additionalPropertiesSchema.Ref = stringptr("#/definitions/" + lowerFirst(mapType.ObjectType)) } objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithSchema(additionalPropertiesSchema) *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema(mapType.Name, objectSchema)) } } // add schema objects for "object", "any", and "expression" if true { objectSchema := &jsonschema.Schema{} objectSchema.Type = jsonschema.NewStringOrStringArrayWithString("object") objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(true) *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("object", objectSchema)) } if true { objectSchema := &jsonschema.Schema{} objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(true) *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("any", objectSchema)) } if true { objectSchema := &jsonschema.Schema{} objectSchema.Type = jsonschema.NewStringOrStringArrayWithString("object") objectSchema.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(true) *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("expression", objectSchema)) } // add schema objects for "specificationExtension" if true { objectSchema := &jsonschema.Schema{} objectSchema.Description = stringptr("Any property starting with x- is valid.") oneOf := make([]*jsonschema.Schema, 0) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("null")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("number")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("object")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("array")}) objectSchema.OneOf = &oneOf *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("specificationExtension", objectSchema)) } // add schema objects for "defaultType" if true { objectSchema := &jsonschema.Schema{} oneOf := make([]*jsonschema.Schema, 0) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("null")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("array")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("object")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("number")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")}) objectSchema.OneOf = &oneOf *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("defaultType", objectSchema)) } // add schema objects for "primitive" if false { // we don't seem to need these for 3.0 RC2 objectSchema := &jsonschema.Schema{} oneOf := make([]*jsonschema.Schema, 0) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("number")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")}) objectSchema.OneOf = &oneOf *schema.Definitions = append(*schema.Definitions, jsonschema.NewNamedSchema("primitive", objectSchema)) } // force a few more things into the "schema" schema schemaObject := schema.DefinitionWithName("schema") schemaObject.CopyOfficialSchemaProperties( []string{ "title", "multipleOf", "maximum", "exclusiveMaximum", "minimum", "exclusiveMinimum", "maxLength", "minLength", "pattern", "maxItems", "minItems", "uniqueItems", "maxProperties", "minProperties", "required", "enum", }) schemaObject.AdditionalProperties = jsonschema.NewSchemaOrBooleanWithBoolean(false) schemaObject.AddProperty("type", &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")}) schemaObject.AddProperty("allOf", arrayOfSchema()) schemaObject.AddProperty("oneOf", arrayOfSchema()) schemaObject.AddProperty("anyOf", arrayOfSchema()) schemaObject.AddProperty("not", &jsonschema.Schema{Ref: stringptr("#/definitions/schema")}) anyOf := make([]*jsonschema.Schema, 0) anyOf = append(anyOf, &jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")}) anyOf = append(anyOf, arrayOfSchema()) schemaObject.AddProperty("items", &jsonschema.Schema{AnyOf: &anyOf}) schemaObject.AddProperty("properties", &jsonschema.Schema{ Type: jsonschema.NewStringOrStringArrayWithString("object"), AdditionalProperties: jsonschema.NewSchemaOrBooleanWithSchema( &jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")})}) if true { // add additionalProperties schema object oneOf := make([]*jsonschema.Schema, 0) oneOf = append(oneOf, &jsonschema.Schema{Ref: stringptr("#/definitions/schemaOrReference")}) oneOf = append(oneOf, &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("boolean")}) schemaObject.AddProperty("additionalProperties", &jsonschema.Schema{OneOf: &oneOf}) } schemaObject.AddProperty("default", &jsonschema.Schema{Ref: stringptr("#/definitions/defaultType")}) schemaObject.AddProperty("description", &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")}) schemaObject.AddProperty("format", &jsonschema.Schema{Type: jsonschema.NewStringOrStringArrayWithString("string")}) // fix the content object contentObject := schema.DefinitionWithName("content") if contentObject != nil { pairs := make([]*jsonschema.NamedSchema, 0) contentObject.PatternProperties = &pairs namedSchema := &jsonschema.NamedSchema{Name: "^", Value: &jsonschema.Schema{Ref: stringptr("#/definitions/mediaType")}} *(contentObject.PatternProperties) = append(*(contentObject.PatternProperties), namedSchema) } // fix the contact object contactObject := schema.DefinitionWithName("contact") if contactObject != nil { emailProperty := contactObject.PropertyWithName("email") if emailProperty != nil { emailProperty.Format = stringptr("email"); } urlProperty := contactObject.PropertyWithName("url") if urlProperty != nil { urlProperty.Format = stringptr("uri"); } } // write the updated schema output := schema.JSONString() err = ioutil.WriteFile("schema.json", []byte(output), 0644) if err != nil { panic(err) } }