| // Copyright 2017 Google Inc. 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.0 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("^<a .*</a>(.*)$") |
| 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("^#+ <a .*</a>(.*)$") |
| 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,<typename>] |
| 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("3.0.1.md") |
| 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("^#### <a name=\"(.*)Object\"") |
| schemaObjects := make([]SchemaObject, 0) |
| for _, section := range schema.Children { |
| if matches := anchor.FindSubmatch([]byte(section.Title)); matches != nil { |
| |
| id := string(matches[1]) |
| |
| schemaObject := SchemaObject{ |
| Name: section.NiceTitle(), |
| ID: id, |
| RequiredFields: nil, |
| } |
| |
| if len(section.Children) > 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.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) |
| } |
| } |