vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

View File

@ -0,0 +1,13 @@
# generate-gnostic
## The gnostic compiler generator
This directory contains code that generates a protocol buffer
representation and supporting code for a JSON schema.
It is currently used to build models of OpenAPI specifications
and extensions which are described as "vendor extensions" in
OpenAPI 2.0 and "specification extensions" in OpenAPI 3.0.
For usage information, run the `generate-gnostic` binary with no
options.

View File

@ -0,0 +1,624 @@
// 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.
package main
import (
"errors"
"fmt"
"log"
"sort"
"strings"
"github.com/googleapis/gnostic/jsonschema"
)
// Domain models a collection of types that is defined by a schema.
type Domain struct {
TypeModels map[string]*TypeModel // models of the types in the domain
Prefix string // type prefix to use
Schema *jsonschema.Schema // top-level schema
TypeNameOverrides map[string]string // a configured mapping from patterns to type names
PropertyNameOverrides map[string]string // a configured mapping from patterns to property names
ObjectTypeRequests map[string]*TypeRequest // anonymous types implied by type instantiation
MapTypeRequests map[string]string // "NamedObject" types that will be used to implement ordered maps
Version string // OpenAPI Version ("v2" or "v3")
}
// NewDomain creates a domain representation.
func NewDomain(schema *jsonschema.Schema, version string) *Domain {
cc := &Domain{}
cc.TypeModels = make(map[string]*TypeModel, 0)
cc.TypeNameOverrides = make(map[string]string, 0)
cc.PropertyNameOverrides = make(map[string]string, 0)
cc.ObjectTypeRequests = make(map[string]*TypeRequest, 0)
cc.MapTypeRequests = make(map[string]string, 0)
cc.Schema = schema
cc.Version = version
return cc
}
// TypeNameForStub returns a capitalized name to use for a generated type.
func (domain *Domain) TypeNameForStub(stub string) string {
return domain.Prefix + strings.ToUpper(stub[0:1]) + stub[1:len(stub)]
}
// typeNameForReference returns a capitalized name to use for a generated type based on a JSON reference
func (domain *Domain) typeNameForReference(reference string) string {
parts := strings.Split(reference, "/")
first := parts[0]
last := parts[len(parts)-1]
if first == "#" {
return domain.TypeNameForStub(last)
}
return "Schema"
}
// propertyNameForReference returns a property name to use for a JSON reference
func (domain *Domain) propertyNameForReference(reference string) *string {
parts := strings.Split(reference, "/")
first := parts[0]
last := parts[len(parts)-1]
if first == "#" {
return &last
}
return nil
}
// arrayItemTypeForSchema determines the item type for arrays defined by a schema
func (domain *Domain) arrayItemTypeForSchema(propertyName string, schema *jsonschema.Schema) string {
// default
itemTypeName := "Any"
if schema.Items != nil {
if schema.Items.SchemaArray != nil {
if len(*(schema.Items.SchemaArray)) > 0 {
ref := (*schema.Items.SchemaArray)[0].Ref
if ref != nil {
itemTypeName = domain.typeNameForReference(*ref)
} else {
types := (*schema.Items.SchemaArray)[0].Type
if types == nil {
// do nothing
} else if (types.StringArray != nil) && len(*(types.StringArray)) == 1 {
itemTypeName = (*types.StringArray)[0]
} else if (types.StringArray != nil) && len(*(types.StringArray)) > 1 {
itemTypeName = fmt.Sprintf("%+v", types.StringArray)
} else if types.String != nil {
itemTypeName = *(types.String)
} else {
itemTypeName = "UNKNOWN"
}
}
}
} else if schema.Items.Schema != nil {
types := schema.Items.Schema.Type
if schema.Items.Schema.Ref != nil {
itemTypeName = domain.typeNameForReference(*schema.Items.Schema.Ref)
} else if schema.Items.Schema.OneOf != nil {
// this type is implied by the "oneOf"
itemTypeName = domain.TypeNameForStub(propertyName + "Item")
domain.ObjectTypeRequests[itemTypeName] =
NewTypeRequest(itemTypeName, propertyName, schema.Items.Schema)
} else if types == nil {
// do nothing
} else if (types.StringArray != nil) && len(*(types.StringArray)) == 1 {
itemTypeName = (*types.StringArray)[0]
} else if (types.StringArray != nil) && len(*(types.StringArray)) > 1 {
itemTypeName = fmt.Sprintf("%+v", types.StringArray)
} else if types.String != nil {
itemTypeName = *(types.String)
} else {
itemTypeName = "UNKNOWN"
}
}
}
return itemTypeName
}
func (domain *Domain) buildTypeProperties(typeModel *TypeModel, schema *jsonschema.Schema) {
if schema.Properties != nil {
for _, pair := range *(schema.Properties) {
propertyName := pair.Name
propertySchema := pair.Value
if propertySchema.Ref != nil {
// the property schema is a reference, so we will add a property with the type of the referenced schema
propertyTypeName := domain.typeNameForReference(*(propertySchema.Ref))
typeProperty := NewTypeProperty()
typeProperty.Name = propertyName
typeProperty.Type = propertyTypeName
typeModel.addProperty(typeProperty)
} else if propertySchema.Type != nil {
// the property schema specifies a type, so add a property with the specified type
if propertySchema.TypeIs("string") {
typeProperty := NewTypePropertyWithNameAndType(propertyName, "string")
if propertySchema.Description != nil {
typeProperty.Description = *propertySchema.Description
}
if propertySchema.Enumeration != nil {
allowedValues := make([]string, 0)
for _, enumValue := range *propertySchema.Enumeration {
if enumValue.String != nil {
allowedValues = append(allowedValues, *enumValue.String)
}
}
typeProperty.StringEnumValues = allowedValues
}
typeModel.addProperty(typeProperty)
} else if propertySchema.TypeIs("boolean") {
typeProperty := NewTypePropertyWithNameAndType(propertyName, "bool")
if propertySchema.Description != nil {
typeProperty.Description = *propertySchema.Description
}
typeModel.addProperty(typeProperty)
} else if propertySchema.TypeIs("number") {
typeProperty := NewTypePropertyWithNameAndType(propertyName, "float")
if propertySchema.Description != nil {
typeProperty.Description = *propertySchema.Description
}
typeModel.addProperty(typeProperty)
} else if propertySchema.TypeIs("integer") {
typeProperty := NewTypePropertyWithNameAndType(propertyName, "int")
if propertySchema.Description != nil {
typeProperty.Description = *propertySchema.Description
}
typeModel.addProperty(typeProperty)
} else if propertySchema.TypeIs("object") {
// the property has an "anonymous" object schema, so define a new type for it and request its creation
anonymousObjectTypeName := domain.TypeNameForStub(propertyName)
domain.ObjectTypeRequests[anonymousObjectTypeName] =
NewTypeRequest(anonymousObjectTypeName, propertyName, propertySchema)
// add a property with the type of the requested type
typeProperty := NewTypePropertyWithNameAndType(propertyName, anonymousObjectTypeName)
if propertySchema.Description != nil {
typeProperty.Description = *propertySchema.Description
}
typeModel.addProperty(typeProperty)
} else if propertySchema.TypeIs("array") {
// the property has an array type, so define it as a repeated property of the specified type
propertyTypeName := domain.arrayItemTypeForSchema(propertyName, propertySchema)
typeProperty := NewTypePropertyWithNameAndType(propertyName, propertyTypeName)
typeProperty.Repeated = true
if propertySchema.Description != nil {
typeProperty.Description = *propertySchema.Description
}
if typeProperty.Type == "string" {
itemSchema := propertySchema.Items.Schema
if itemSchema != nil {
if itemSchema.Enumeration != nil {
allowedValues := make([]string, 0)
for _, enumValue := range *itemSchema.Enumeration {
if enumValue.String != nil {
allowedValues = append(allowedValues, *enumValue.String)
}
}
typeProperty.StringEnumValues = allowedValues
}
}
}
typeModel.addProperty(typeProperty)
} else {
log.Printf("ignoring %+v, which has an unsupported property type '%s'", propertyName, propertySchema.Type.Description())
}
} else if propertySchema.IsEmpty() {
// an empty schema can contain anything, so add an accessor for a generic object
typeName := "Any"
typeProperty := NewTypePropertyWithNameAndType(propertyName, typeName)
typeModel.addProperty(typeProperty)
} else if propertySchema.OneOf != nil {
anonymousObjectTypeName := domain.TypeNameForStub(propertyName + "Item")
domain.ObjectTypeRequests[anonymousObjectTypeName] =
NewTypeRequest(anonymousObjectTypeName, propertyName, propertySchema)
typeProperty := NewTypePropertyWithNameAndType(propertyName, anonymousObjectTypeName)
typeModel.addProperty(typeProperty)
} else if propertySchema.AnyOf != nil {
anonymousObjectTypeName := domain.TypeNameForStub(propertyName + "Item")
domain.ObjectTypeRequests[anonymousObjectTypeName] =
NewTypeRequest(anonymousObjectTypeName, propertyName, propertySchema)
typeProperty := NewTypePropertyWithNameAndType(propertyName, anonymousObjectTypeName)
typeModel.addProperty(typeProperty)
} else {
log.Printf("ignoring %s.%s, which has an unrecognized schema:\n%+v", typeModel.Name, propertyName, propertySchema.String())
}
}
}
}
func (domain *Domain) buildTypeRequirements(typeModel *TypeModel, schema *jsonschema.Schema) {
if schema.Required != nil {
typeModel.Required = (*schema.Required)
}
}
func (domain *Domain) buildPatternPropertyAccessors(typeModel *TypeModel, schema *jsonschema.Schema) {
if schema.PatternProperties != nil {
typeModel.OpenPatterns = make([]string, 0)
for _, pair := range *(schema.PatternProperties) {
propertyPattern := pair.Name
propertySchema := pair.Value
typeModel.OpenPatterns = append(typeModel.OpenPatterns, propertyPattern)
if propertySchema.Ref != nil {
typeName := domain.typeNameForReference(*propertySchema.Ref)
if _, ok := domain.TypeNameOverrides[typeName]; ok {
typeName = domain.TypeNameOverrides[typeName]
}
propertyName := domain.typeNameForReference(*propertySchema.Ref)
if _, ok := domain.PropertyNameOverrides[propertyName]; ok {
propertyName = domain.PropertyNameOverrides[propertyName]
}
propertyTypeName := fmt.Sprintf("Named%s", typeName)
property := NewTypePropertyWithNameTypeAndPattern(propertyName, propertyTypeName, propertyPattern)
property.Implicit = true
property.MapType = typeName
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
}
}
}
}
func (domain *Domain) buildAdditionalPropertyAccessors(typeModel *TypeModel, schema *jsonschema.Schema) {
if schema.AdditionalProperties != nil {
if schema.AdditionalProperties.Boolean != nil {
if *schema.AdditionalProperties.Boolean == true {
typeModel.Open = true
propertyName := "additionalProperties"
typeName := "NamedAny"
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.Implicit = true
property.MapType = "Any"
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
return
}
} else if schema.AdditionalProperties.Schema != nil {
typeModel.Open = true
schema := schema.AdditionalProperties.Schema
if schema.Ref != nil {
propertyName := "additionalProperties"
mapType := domain.typeNameForReference(*schema.Ref)
typeName := fmt.Sprintf("Named%s", mapType)
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.Implicit = true
property.MapType = mapType
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
return
} else if schema.Type != nil {
typeName := *schema.Type.String
if typeName == "string" {
propertyName := "additionalProperties"
typeName := "NamedString"
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.Implicit = true
property.MapType = "string"
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
return
} else if typeName == "array" {
if schema.Items != nil {
itemType := *schema.Items.Schema.Type.String
if itemType == "string" {
propertyName := "additionalProperties"
typeName := "NamedStringArray"
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.Implicit = true
property.MapType = "StringArray"
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
return
}
}
}
} else if schema.OneOf != nil {
propertyTypeName := domain.TypeNameForStub(typeModel.Name + "Item")
propertyName := "additionalProperties"
typeName := fmt.Sprintf("Named%s", propertyTypeName)
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.Implicit = true
property.MapType = propertyTypeName
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
domain.ObjectTypeRequests[propertyTypeName] =
NewTypeRequest(propertyTypeName, propertyName, schema)
}
}
}
}
func (domain *Domain) buildOneOfAccessors(typeModel *TypeModel, schema *jsonschema.Schema) {
oneOfs := schema.OneOf
if oneOfs == nil {
return
}
typeModel.Open = true
typeModel.OneOfWrapper = true
for _, oneOf := range *oneOfs {
if oneOf.Ref != nil {
ref := *oneOf.Ref
typeName := domain.typeNameForReference(ref)
propertyName := domain.propertyNameForReference(ref)
if propertyName != nil {
typeProperty := NewTypePropertyWithNameAndType(*propertyName, typeName)
typeModel.addProperty(typeProperty)
}
} else if oneOf.Type != nil && oneOf.Type.String != nil {
switch *oneOf.Type.String {
case "boolean":
typeProperty := NewTypePropertyWithNameAndType("boolean", "bool")
typeModel.addProperty(typeProperty)
case "integer":
typeProperty := NewTypePropertyWithNameAndType("integer", "int")
typeModel.addProperty(typeProperty)
case "number":
typeProperty := NewTypePropertyWithNameAndType("number", "float")
typeModel.addProperty(typeProperty)
case "string":
typeProperty := NewTypePropertyWithNameAndType("string", "string")
typeModel.addProperty(typeProperty)
default:
log.Printf("Unsupported oneOf:\n%+v", oneOf.String())
}
} else {
log.Printf("Unsupported oneOf:\n%+v", oneOf.String())
}
}
}
func schemaIsContainedInArray(s1 *jsonschema.Schema, s2 *jsonschema.Schema) bool {
if s2.TypeIs("array") {
if s2.Items.Schema != nil {
if s1.IsEqual(s2.Items.Schema) {
return true
}
}
}
return false
}
func (domain *Domain) addAnonymousAccessorForSchema(
typeModel *TypeModel,
schema *jsonschema.Schema,
repeated bool) {
ref := schema.Ref
if ref != nil {
typeName := domain.typeNameForReference(*ref)
propertyName := domain.propertyNameForReference(*ref)
if propertyName != nil {
property := NewTypePropertyWithNameAndType(*propertyName, typeName)
property.Repeated = true
typeModel.addProperty(property)
typeModel.IsItemArray = true
}
} else {
typeName := "string"
propertyName := "value"
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.Repeated = true
typeModel.addProperty(property)
typeModel.IsStringArray = true
}
}
func (domain *Domain) buildAnyOfAccessors(typeModel *TypeModel, schema *jsonschema.Schema) {
anyOfs := schema.AnyOf
if anyOfs == nil {
return
}
if len(*anyOfs) == 2 {
if schemaIsContainedInArray((*anyOfs)[0], (*anyOfs)[1]) {
//log.Printf("ARRAY OF %+v", (*anyOfs)[0].String())
schema := (*anyOfs)[0]
domain.addAnonymousAccessorForSchema(typeModel, schema, true)
} else if schemaIsContainedInArray((*anyOfs)[1], (*anyOfs)[0]) {
//log.Printf("ARRAY OF %+v", (*anyOfs)[1].String())
schema := (*anyOfs)[1]
domain.addAnonymousAccessorForSchema(typeModel, schema, true)
} else {
for _, anyOf := range *anyOfs {
ref := anyOf.Ref
if ref != nil {
typeName := domain.typeNameForReference(*ref)
propertyName := domain.propertyNameForReference(*ref)
if propertyName != nil {
property := NewTypePropertyWithNameAndType(*propertyName, typeName)
typeModel.addProperty(property)
}
} else {
typeName := "bool"
propertyName := "boolean"
property := NewTypePropertyWithNameAndType(propertyName, typeName)
typeModel.addProperty(property)
}
}
}
} else {
log.Printf("Unhandled anyOfs:\n%s", schema.String())
}
}
func (domain *Domain) buildDefaultAccessors(typeModel *TypeModel, schema *jsonschema.Schema) {
typeModel.Open = true
propertyName := "additionalProperties"
typeName := "NamedAny"
property := NewTypePropertyWithNameAndType(propertyName, typeName)
property.MapType = "Any"
property.Repeated = true
domain.MapTypeRequests[property.MapType] = property.MapType
typeModel.addProperty(property)
}
// BuildTypeForDefinition creates a type representation for a schema definition.
func (domain *Domain) BuildTypeForDefinition(
typeName string,
propertyName string,
schema *jsonschema.Schema) *TypeModel {
if (schema.Type == nil) || (*schema.Type.String == "object") {
return domain.buildTypeForDefinitionObject(typeName, propertyName, schema)
}
return nil
}
func (domain *Domain) buildTypeForDefinitionObject(
typeName string,
propertyName string,
schema *jsonschema.Schema) *TypeModel {
typeModel := NewTypeModel()
typeModel.Name = typeName
if schema.IsEmpty() {
domain.buildDefaultAccessors(typeModel, schema)
} else {
if schema.Description != nil {
typeModel.Description = *schema.Description
}
domain.buildTypeProperties(typeModel, schema)
domain.buildTypeRequirements(typeModel, schema)
domain.buildPatternPropertyAccessors(typeModel, schema)
domain.buildAdditionalPropertyAccessors(typeModel, schema)
domain.buildOneOfAccessors(typeModel, schema)
domain.buildAnyOfAccessors(typeModel, schema)
}
return typeModel
}
// Build builds a domain model.
func (domain *Domain) Build() (err error) {
if (domain.Schema == nil) || (domain.Schema.Definitions == nil) {
return errors.New("missing definitions section")
}
// create a type for the top-level schema
typeName := domain.Prefix + "Document"
typeModel := NewTypeModel()
typeModel.Name = typeName
domain.buildTypeProperties(typeModel, domain.Schema)
domain.buildTypeRequirements(typeModel, domain.Schema)
domain.buildPatternPropertyAccessors(typeModel, domain.Schema)
domain.buildAdditionalPropertyAccessors(typeModel, domain.Schema)
domain.buildOneOfAccessors(typeModel, domain.Schema)
domain.buildAnyOfAccessors(typeModel, domain.Schema)
if len(typeModel.Properties) > 0 {
domain.TypeModels[typeName] = typeModel
}
// create a type for each object defined in the schema
if domain.Schema.Definitions != nil {
for _, pair := range *(domain.Schema.Definitions) {
definitionName := pair.Name
definitionSchema := pair.Value
typeName := domain.TypeNameForStub(definitionName)
typeModel := domain.BuildTypeForDefinition(typeName, definitionName, definitionSchema)
if typeModel != nil {
domain.TypeModels[typeName] = typeModel
}
}
}
// iterate over anonymous object types to be instantiated and generate a type for each
for typeName, typeRequest := range domain.ObjectTypeRequests {
domain.TypeModels[typeRequest.Name] =
domain.buildTypeForDefinitionObject(typeName, typeRequest.PropertyName, typeRequest.Schema)
}
// iterate over map item types to be instantiated and generate a type for each
mapTypeNames := make([]string, 0)
for mapTypeName := range domain.MapTypeRequests {
mapTypeNames = append(mapTypeNames, mapTypeName)
}
sort.Strings(mapTypeNames)
for _, mapTypeName := range mapTypeNames {
typeName := "Named" + strings.Title(mapTypeName)
typeModel := NewTypeModel()
typeModel.Name = typeName
typeModel.Description = fmt.Sprintf(
"Automatically-generated message used to represent maps of %s as ordered (name,value) pairs.",
mapTypeName)
typeModel.IsPair = true
typeModel.PairValueType = mapTypeName
nameProperty := NewTypeProperty()
nameProperty.Name = "name"
nameProperty.Type = "string"
nameProperty.Description = "Map key"
typeModel.addProperty(nameProperty)
valueProperty := NewTypeProperty()
valueProperty.Name = "value"
valueProperty.Type = mapTypeName
valueProperty.Description = "Mapped value"
typeModel.addProperty(valueProperty)
domain.TypeModels[typeName] = typeModel
}
// add a type for string arrays
stringArrayType := NewTypeModel()
stringArrayType.Name = "StringArray"
stringProperty := NewTypeProperty()
stringProperty.Name = "value"
stringProperty.Type = "string"
stringProperty.Repeated = true
stringArrayType.addProperty(stringProperty)
domain.TypeModels[stringArrayType.Name] = stringArrayType
// add a type for "Any"
anyType := NewTypeModel()
anyType.Name = "Any"
anyType.Open = true
anyType.IsBlob = true
valueProperty := NewTypeProperty()
valueProperty.Name = "value"
valueProperty.Type = "google.protobuf.Any"
anyType.addProperty(valueProperty)
yamlProperty := NewTypeProperty()
yamlProperty.Name = "yaml"
yamlProperty.Type = "string"
anyType.addProperty(yamlProperty)
domain.TypeModels[anyType.Name] = anyType
return err
}
func (domain *Domain) sortedTypeNames() []string {
typeNames := make([]string, 0)
for typeName := range domain.TypeModels {
typeNames = append(typeNames, typeName)
}
sort.Strings(typeNames)
return typeNames
}
// Description returns a string representation of a domain.
func (domain *Domain) Description() string {
typeNames := domain.sortedTypeNames()
result := ""
for _, typeName := range typeNames {
result += domain.TypeModels[typeName].description()
}
return result
}

View File

@ -0,0 +1,913 @@
// 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.
package main
import (
"fmt"
"regexp"
"sort"
"strings"
"github.com/googleapis/gnostic/printer"
)
// patternNames hands out unique names for a given string.
type patternNames struct {
prefix string
values map[string]int
last int
specialCase map[string]func(variable string) string
}
// SpecialCaseExpression returns true if the provided regex can be inlined as a faster
// expression.
func (p *patternNames) SpecialCaseExpression(value, variable string) (code string, ok bool) {
fn, ok := p.specialCase[value]
if !ok {
return "", false
}
return fn(variable), ok
}
// VariableName returns the variable name for the given value.
func (p *patternNames) VariableName(value string) string {
num, ok := p.values[value]
if !ok {
if p.values == nil {
p.values = make(map[string]int)
}
num = p.last
p.last++
p.values[value] = num
}
return fmt.Sprintf("%s%d", p.prefix, num)
}
func (p *patternNames) Names() map[string]string {
names := make(map[string]string)
for value, num := range p.values {
names[fmt.Sprintf("%s%d", p.prefix, num)] = value
}
return names
}
// GenerateCompiler generates the compiler code for a domain.
func (domain *Domain) GenerateCompiler(packageName string, license string, imports []string) string {
code := &printer.Code{}
code.Print(license)
code.Print("// THIS FILE IS AUTOMATICALLY GENERATED.\n")
// generate package declaration
code.Print("package %s\n", packageName)
code.Print("import (")
for _, filename := range imports {
code.Print("\"" + filename + "\"")
}
code.Print(")\n")
// generate a simple Version() function
code.Print("// Version returns the package name (and OpenAPI version).")
code.Print("func Version() string {")
code.Print(" return \"%s\"", packageName)
code.Print("}\n")
typeNames := domain.sortedTypeNames()
regexPatterns := &patternNames{
prefix: "pattern",
specialCase: map[string]func(string) string{
"^x-": func(variable string) string { return fmt.Sprintf("strings.HasPrefix(%s, \"x-\")", variable) },
"^/": func(variable string) string { return fmt.Sprintf("strings.HasPrefix(%s, \"/\")", variable) },
"^": func(_ string) string { return "true" },
},
}
// generate NewX() constructor functions for each type
for _, typeName := range typeNames {
domain.generateConstructorForType(code, typeName, regexPatterns)
}
// generate ResolveReferences() methods for each type
for _, typeName := range typeNames {
domain.generateResolveReferencesMethodsForType(code, typeName)
}
// generate ToRawInfo() methods for each type
for _, typeName := range typeNames {
domain.generateToRawInfoMethodForType(code, typeName)
}
domain.generateConstantVariables(code, regexPatterns)
return code.String()
}
func escapeSlashes(pattern string) string {
return strings.Replace(pattern, "\\", "\\\\", -1)
}
var subpatternPattern = regexp.MustCompile("^.*(\\{.*\\}).*$")
func nameForPattern(regexPatterns *patternNames, pattern string) string {
if !strings.HasPrefix(pattern, "^") {
if matches := subpatternPattern.FindStringSubmatch(pattern); matches != nil {
match := string(matches[1])
pattern = strings.Replace(pattern, match, ".*", -1)
}
}
return regexPatterns.VariableName(pattern)
}
func (domain *Domain) generateConstructorForType(code *printer.Code, typeName string, regexPatterns *patternNames) {
code.Print("// New%s creates an object of type %s if possible, returning an error if not.", typeName, typeName)
code.Print("func New%s(in interface{}, context *compiler.Context) (*%s, error) {", typeName, typeName)
code.Print("errors := make([]error, 0)")
typeModel := domain.TypeModels[typeName]
parentTypeName := typeName
if typeModel.IsStringArray {
code.Print("x := &TypeItem{}")
code.Print("switch in := in.(type) {")
code.Print("case string:")
code.Print(" x.Value = make([]string, 0)")
code.Print(" x.Value = append(x.Value, in)")
code.Print("case []interface{}:")
code.Print(" x.Value = make([]string, 0)")
code.Print(" for _, v := range in {")
code.Print(" value, ok := v.(string)")
code.Print(" if ok {")
code.Print(" x.Value = append(x.Value, value)")
code.Print(" } else {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for string array element: %%+v (%%T)\", value, value)")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print(" }")
code.Print(" }")
code.Print("default:")
code.Print(" message := fmt.Sprintf(\"has unexpected value for string array: %%+v (%%T)\", in, in)")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("}")
} else if typeModel.IsItemArray {
if domain.Version == "v2" {
code.Print("x := &ItemsItem{}")
code.Print("m, ok := compiler.UnpackMap(in)")
code.Print("if !ok {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for item array: %%+v (%%T)\", in, in)")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("} else {")
code.Print(" x.Schema = make([]*Schema, 0)")
code.Print(" y, err := NewSchema(m, compiler.NewContext(\"<array>\", context))")
code.Print(" if err != nil {")
code.Print(" return nil, err")
code.Print(" }")
code.Print(" x.Schema = append(x.Schema, y)")
code.Print("}")
} else if domain.Version == "v3" {
code.Print("x := &ItemsItem{}")
code.Print("m, ok := compiler.UnpackMap(in)")
code.Print("if !ok {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for item array: %%+v (%%T)\", in, in)")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("} else {")
code.Print(" x.SchemaOrReference = make([]*SchemaOrReference, 0)")
code.Print(" y, err := NewSchemaOrReference(m, compiler.NewContext(\"<array>\", context))")
code.Print(" if err != nil {")
code.Print(" return nil, err")
code.Print(" }")
code.Print(" x.SchemaOrReference = append(x.SchemaOrReference, y)")
code.Print("}")
}
} else if typeModel.IsBlob {
code.Print("x := &Any{}")
code.Print("bytes, _ := yaml.Marshal(in)")
code.Print("x.Yaml = string(bytes)")
} else if typeModel.Name == "StringArray" {
code.Print("x := &StringArray{}")
code.Print("a, ok := in.([]interface{})")
code.Print("if !ok {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for StringArray: %%+v (%%T)\", in, in)")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("} else {")
code.Print(" x.Value = make([]string, 0)")
code.Print(" for _, s := range a {")
code.Print(" x.Value = append(x.Value, s.(string))")
code.Print(" }")
code.Print("}")
} else if typeModel.Name == "Primitive" {
code.Print(" x := &Primitive{}")
code.Print(" matched := false")
code.Print(" switch in := in.(type) {")
code.Print(" case bool:")
code.Print(" x.Oneof = &Primitive_Boolean{Boolean: in}")
code.Print(" matched = true")
code.Print(" case string:")
code.Print(" x.Oneof = &Primitive_String_{String_: in}")
code.Print(" matched = true")
code.Print(" case int64:")
code.Print(" x.Oneof = &Primitive_Integer{Integer: in}")
code.Print(" matched = true")
code.Print(" case int32:")
code.Print(" x.Oneof = &Primitive_Integer{Integer: int64(in)}")
code.Print(" matched = true")
code.Print(" case int:")
code.Print(" x.Oneof = &Primitive_Integer{Integer: int64(in)}")
code.Print(" matched = true")
code.Print(" case float64:")
code.Print(" x.Oneof = &Primitive_Number{Number: in}")
code.Print(" matched = true")
code.Print(" case float32:")
code.Print(" x.Oneof = &Primitive_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" }")
code.Print(" if matched {")
code.Print(" // since the oneof matched one of its possibilities, discard any matching errors")
code.Print(" errors = make([]error, 0)")
code.Print(" }")
} else if typeModel.Name == "SpecificationExtension" {
code.Print(" x := &SpecificationExtension{}")
code.Print(" matched := false")
code.Print(" switch in := in.(type) {")
code.Print(" case bool:")
code.Print(" x.Oneof = &SpecificationExtension_Boolean{Boolean: in}")
code.Print(" matched = true")
code.Print(" case string:")
code.Print(" x.Oneof = &SpecificationExtension_String_{String_: in}")
code.Print(" matched = true")
code.Print(" case int64:")
code.Print(" x.Oneof = &SpecificationExtension_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" case int32:")
code.Print(" x.Oneof = &SpecificationExtension_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" case int:")
code.Print(" x.Oneof = &SpecificationExtension_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" case float64:")
code.Print(" x.Oneof = &SpecificationExtension_Number{Number: in}")
code.Print(" matched = true")
code.Print(" case float32:")
code.Print(" x.Oneof = &SpecificationExtension_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" }")
code.Print(" if matched {")
code.Print(" // since the oneof matched one of its possibilities, discard any matching errors")
code.Print(" errors = make([]error, 0)")
code.Print(" }")
} else if typeModel.Name == "DefaultType" {
code.Print(" x := &DefaultType{}")
code.Print(" matched := false")
code.Print(" switch in := in.(type) {")
code.Print(" case bool:")
code.Print(" x.Oneof = &DefaultType_Boolean{Boolean: in}")
code.Print(" matched = true")
code.Print(" case string:")
code.Print(" x.Oneof = &DefaultType_String_{String_: in}")
code.Print(" matched = true")
code.Print(" case int64:")
code.Print(" x.Oneof = &DefaultType_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" case int32:")
code.Print(" x.Oneof = &DefaultType_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" case int:")
code.Print(" x.Oneof = &DefaultType_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" case float64:")
code.Print(" x.Oneof = &DefaultType_Number{Number: in}")
code.Print(" matched = true")
code.Print(" case float32:")
code.Print(" x.Oneof = &DefaultType_Number{Number: float64(in)}")
code.Print(" matched = true")
code.Print(" }")
code.Print(" if matched {")
code.Print(" // since the oneof matched one of its possibilities, discard any matching errors")
code.Print(" errors = make([]error, 0)")
code.Print(" }")
} else {
oneOfWrapper := typeModel.OneOfWrapper
code.Print("x := &%s{}", typeName)
if oneOfWrapper {
code.Print("matched := false")
}
unpackAtTop := !oneOfWrapper || len(typeModel.Required) > 0
if unpackAtTop {
code.Print("m, ok := compiler.UnpackMap(in)")
code.Print("if !ok {")
code.Print(" message := fmt.Sprintf(\"has unexpected value: %%+v (%%T)\", in, in)")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("} else {")
}
if len(typeModel.Required) > 0 {
// verify that map includes all required keys
keyString := ""
sort.Strings(typeModel.Required)
for _, k := range typeModel.Required {
if keyString != "" {
keyString += ","
}
keyString += "\""
keyString += k
keyString += "\""
}
code.Print("requiredKeys := []string{%s}", keyString)
code.Print("missingKeys := compiler.MissingKeysInMap(m, requiredKeys)")
code.Print("if len(missingKeys) > 0 {")
code.Print(" message := fmt.Sprintf(\"is missing required %%s: %%+v\", compiler.PluralProperties(len(missingKeys)), strings.Join(missingKeys, \", \"))")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("}")
}
if !typeModel.Open {
// verify that map has no unspecified keys
allowedKeys := make([]string, 0)
for _, property := range typeModel.Properties {
if !property.Implicit {
allowedKeys = append(allowedKeys, property.Name)
}
}
sort.Strings(allowedKeys)
allowedKeyString := ""
for _, allowedKey := range allowedKeys {
if allowedKeyString != "" {
allowedKeyString += ","
}
allowedKeyString += "\""
allowedKeyString += allowedKey
allowedKeyString += "\""
}
allowedPatternString := ""
if typeModel.OpenPatterns != nil {
for _, pattern := range typeModel.OpenPatterns {
if allowedPatternString != "" {
allowedPatternString += ","
}
allowedPatternString += nameForPattern(regexPatterns, pattern)
}
}
// verify that map includes only allowed keys and patterns
code.Print("allowedKeys := []string{%s}", allowedKeyString)
if len(allowedPatternString) > 0 {
code.Print("allowedPatterns := []*regexp.Regexp{%s}", allowedPatternString)
} else {
code.Print("var allowedPatterns []*regexp.Regexp")
}
code.Print("invalidKeys := compiler.InvalidKeysInMap(m, allowedKeys, allowedPatterns)")
code.Print("if len(invalidKeys) > 0 {")
code.Print(" message := fmt.Sprintf(\"has invalid %%s: %%+v\", compiler.PluralProperties(len(invalidKeys)), strings.Join(invalidKeys, \", \"))")
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("}")
}
var fieldNumber = 0
for _, propertyModel := range typeModel.Properties {
propertyName := propertyModel.Name
fieldNumber++
propertyType := propertyModel.Type
if propertyType == "int" {
propertyType = "int64"
}
var displayName = propertyName
if displayName == "$ref" {
displayName = "_ref"
}
if displayName == "$schema" {
displayName = "_schema"
}
displayName = camelCaseToSnakeCase(displayName)
var line = fmt.Sprintf("%s %s = %d;", propertyType, displayName, fieldNumber)
if propertyModel.Repeated {
line = "repeated " + line
}
code.Print("// " + line)
fieldName := strings.Title(snakeCaseToCamelCase(propertyName))
if propertyName == "$ref" {
fieldName = "XRef"
}
typeModel, typeFound := domain.TypeModels[propertyType]
if typeFound && !typeModel.IsPair {
if propertyModel.Repeated {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" // repeated %s", typeModel.Name)
code.Print(" x.%s = make([]*%s, 0)", fieldName, typeModel.Name)
code.Print(" a, ok := v%d.([]interface{})", fieldNumber)
code.Print(" if ok {")
code.Print(" for _, item := range a {")
code.Print(" y, err := New%s(item, compiler.NewContext(\"%s\", context))", typeModel.Name, propertyName)
code.Print(" if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print(" }")
code.Print(" x.%s = append(x.%s, y)", fieldName, fieldName)
code.Print(" }")
code.Print(" }")
code.Print("}")
} else {
if oneOfWrapper {
code.Print("{")
if !unpackAtTop {
code.Print(" m, ok := compiler.UnpackMap(in)")
code.Print(" if ok {")
}
code.Print(" // errors might be ok here, they mean we just don't have the right subtype")
code.Print(" t, matchingError := New%s(m, compiler.NewContext(\"%s\", context))", typeModel.Name, propertyName)
code.Print(" if matchingError == nil {")
code.Print(" x.Oneof = &%s_%s{%s: t}", parentTypeName, typeModel.Name, typeModel.Name)
code.Print(" matched = true")
code.Print(" } else {")
code.Print(" errors = append(errors, matchingError)")
code.Print(" }")
if !unpackAtTop {
code.Print(" }")
}
code.Print("}")
} else {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" var err error")
code.Print(" x.%s, err = New%s(v%d, compiler.NewContext(\"%s\", context))",
fieldName, typeModel.Name, fieldNumber, propertyName)
code.Print(" if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print(" }")
code.Print("}")
}
}
} else if propertyType == "string" {
if propertyModel.Repeated {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" v, ok := v%d.([]interface{})", fieldNumber)
code.Print(" if ok {")
code.Print(" x.%s = compiler.ConvertInterfaceArrayToStringArray(v)", fieldName)
code.Print(" } else {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v (%%T)\", v%d, v%d)", propertyName, fieldNumber, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("}")
if propertyModel.StringEnumValues != nil {
code.Print("// check for valid enum values")
code.Print("// %+v", propertyModel.StringEnumValues)
stringArrayLiteral := "[]string{"
for i, item := range propertyModel.StringEnumValues {
if i > 0 {
stringArrayLiteral += ","
}
stringArrayLiteral += "\"" + item + "\""
}
stringArrayLiteral += "}"
code.Print("if ok && !compiler.StringArrayContainsValues(%s, x.%s) {", stringArrayLiteral, fieldName)
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v\", v%d)", propertyName, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("}")
}
code.Print("}")
} else {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" x.%s, ok = v%d.(string)", fieldName, fieldNumber)
code.Print(" if !ok {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v (%%T)\", v%d, v%d)", propertyName, fieldNumber, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print(" }")
if propertyModel.StringEnumValues != nil {
code.Print("// check for valid enum values")
code.Print("// %+v", propertyModel.StringEnumValues)
stringArrayLiteral := "[]string{"
for i, item := range propertyModel.StringEnumValues {
if i > 0 {
stringArrayLiteral += ","
}
stringArrayLiteral += "\"" + item + "\""
}
stringArrayLiteral += "}"
code.Print("if ok && !compiler.StringArrayContainsValue(%s, x.%s) {", stringArrayLiteral, fieldName)
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v (%%T)\", v%d, v%d)", propertyName, fieldNumber, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print("}")
}
code.Print("}")
}
} else if propertyType == "float" {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" switch v%d := v%d.(type) {", fieldNumber, fieldNumber)
code.Print(" case float64:")
code.Print(" x.%s = v%d", fieldName, fieldNumber)
code.Print(" case float32:")
code.Print(" x.%s = float64(v%d)", fieldName, fieldNumber)
code.Print(" case uint64:")
code.Print(" x.%s = float64(v%d)", fieldName, fieldNumber)
code.Print(" case uint32:")
code.Print(" x.%s = float64(v%d)", fieldName, fieldNumber)
code.Print(" case int64:")
code.Print(" x.%s = float64(v%d)", fieldName, fieldNumber)
code.Print(" case int32:")
code.Print(" x.%s = float64(v%d)", fieldName, fieldNumber)
code.Print(" case int:")
code.Print(" x.%s = float64(v%d)", fieldName, fieldNumber)
code.Print(" default:")
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v (%%T)\", v%d, v%d)", propertyName, fieldNumber, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print(" }")
code.Print("}")
} else if propertyType == "int64" {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" t, ok := v%d.(int)", fieldNumber)
code.Print(" if ok {")
code.Print(" x.%s = int64(t)", fieldName)
code.Print(" } else {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v (%%T)\", v%d, v%d)", propertyName, fieldNumber, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print(" }")
code.Print("}")
} else if propertyType == "bool" {
if oneOfWrapper {
propertyName := "Boolean"
code.Print("boolValue, ok := in.(bool)")
code.Print("if ok {")
code.Print(" x.Oneof = &%s_%s{%s: boolValue}", parentTypeName, propertyName, propertyName)
code.Print("}")
} else {
code.Print("v%d := compiler.MapValueForKey(m, \"%s\")", fieldNumber, propertyName)
code.Print("if (v%d != nil) {", fieldNumber)
code.Print(" x.%s, ok = v%d.(bool)", fieldName, fieldNumber)
code.Print(" if !ok {")
code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%+v (%%T)\", v%d, v%d)", propertyName, fieldNumber, fieldNumber)
code.Print(" errors = append(errors, compiler.NewError(context, message))")
code.Print(" }")
code.Print("}")
}
} else {
mapTypeName := propertyModel.MapType
if mapTypeName != "" {
code.Print("// MAP: %s %s", mapTypeName, propertyModel.Pattern)
if mapTypeName == "string" {
code.Print("x.%s = make([]*NamedString, 0)", fieldName)
} else {
code.Print("x.%s = make([]*Named%s, 0)", fieldName, mapTypeName)
}
code.Print("for _, item := range m {")
code.Print("k, ok := compiler.StringValue(item.Key)")
code.Print("if ok {")
code.Print("v := item.Value")
if pattern := propertyModel.Pattern; pattern != "" {
if inline, ok := regexPatterns.SpecialCaseExpression(pattern, "k"); ok {
code.Print("if %s {", inline)
} else {
code.Print("if %s.MatchString(k) {", nameForPattern(regexPatterns, pattern))
}
}
code.Print("pair := &Named" + strings.Title(mapTypeName) + "{}")
code.Print("pair.Name = k")
if mapTypeName == "string" {
code.Print("pair.Value = v.(string)")
} else if mapTypeName == "Any" {
code.Print("result := &Any{}")
code.Print("handled, resultFromExt, err := compiler.HandleExtension(context, v, k)")
code.Print("if handled {")
code.Print(" if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print(" } else {")
code.Print(" bytes, _ := yaml.Marshal(v)")
code.Print(" result.Yaml = string(bytes)")
code.Print(" result.Value = resultFromExt")
code.Print(" pair.Value = result")
code.Print(" }")
code.Print("} else {")
code.Print(" pair.Value, err = NewAny(v, compiler.NewContext(k, context))")
code.Print(" if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print(" }")
code.Print("}")
} else {
code.Print("var err error")
code.Print("pair.Value, err = New%s(v, compiler.NewContext(k, context))", mapTypeName)
code.Print("if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print("}")
}
code.Print("x.%s = append(x.%s, pair)", fieldName, fieldName)
if propertyModel.Pattern != "" {
code.Print("}")
}
code.Print("}")
code.Print("}")
} else {
code.Print("// TODO: %s", propertyType)
}
}
}
if unpackAtTop {
code.Print("}")
}
if oneOfWrapper {
code.Print("if matched {")
code.Print(" // since the oneof matched one of its possibilities, discard any matching errors")
code.Print(" errors = make([]error, 0)")
code.Print("}")
}
}
// assumes that the return value is in a variable named "x"
code.Print(" return x, compiler.NewErrorGroupOrNil(errors)")
code.Print("}\n")
}
// ResolveReferences() methods
func (domain *Domain) generateResolveReferencesMethodsForType(code *printer.Code, typeName string) {
code.Print("// ResolveReferences resolves references found inside %s objects.", typeName)
code.Print("func (m *%s) ResolveReferences(root string) (interface{}, error) {", typeName)
code.Print("errors := make([]error, 0)")
typeModel := domain.TypeModels[typeName]
if typeModel.OneOfWrapper {
// call ResolveReferences on whatever is in the Oneof.
for _, propertyModel := range typeModel.Properties {
propertyType := propertyModel.Type
_, typeFound := domain.TypeModels[propertyType]
if typeFound {
code.Print("{")
code.Print("p, ok := m.Oneof.(*%s_%s)", typeName, propertyType)
code.Print("if ok {")
if propertyType == "JsonReference" { // Special case for OpenAPI
code.Print("info, err := p.%s.ResolveReferences(root)", propertyType)
code.Print("if err != nil {")
code.Print(" return nil, err")
code.Print("} else if info != nil {")
code.Print(" n, err := New%s(info, nil)", typeName)
code.Print(" if err != nil {")
code.Print(" return nil, err")
code.Print(" } else if n != nil {")
code.Print(" *m = *n")
code.Print(" return nil, nil")
code.Print(" }")
code.Print("}")
} else {
code.Print("_, err := p.%s.ResolveReferences(root)", propertyType)
code.Print("if err != nil {")
code.Print(" return nil, err")
code.Print("}")
}
code.Print("}")
code.Print("}")
}
}
} else {
for _, propertyModel := range typeModel.Properties {
propertyName := propertyModel.Name
var displayName = propertyName
if displayName == "$ref" {
displayName = "_ref"
}
if displayName == "$schema" {
displayName = "_schema"
}
displayName = camelCaseToSnakeCase(displayName)
fieldName := strings.Title(propertyName)
if propertyName == "$ref" {
fieldName = "XRef"
code.Print("if m.XRef != \"\" {")
//code.Print("log.Printf(\"%s reference to resolve %%+v\", m.XRef)", typeName)
code.Print("info, err := compiler.ReadInfoForRef(root, m.XRef)")
code.Print("if err != nil {")
code.Print(" return nil, err")
code.Print("}")
//code.Print("log.Printf(\"%%+v\", info)")
if len(typeModel.Properties) > 1 {
code.Print("if info != nil {")
code.Print(" replacement, err := New%s(info, nil)", typeName)
code.Print(" if err == nil {")
code.Print(" *m = *replacement")
code.Print(" return m.ResolveReferences(root)")
code.Print(" }")
code.Print("}")
}
code.Print("return info, nil")
code.Print("}")
}
if !propertyModel.Repeated {
propertyType := propertyModel.Type
typeModel, typeFound := domain.TypeModels[propertyType]
if typeFound && !typeModel.IsPair {
code.Print("if m.%s != nil {", fieldName)
code.Print(" _, err := m.%s.ResolveReferences(root)", fieldName)
code.Print(" if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print(" }")
code.Print("}")
}
} else {
propertyType := propertyModel.Type
_, typeFound := domain.TypeModels[propertyType]
if typeFound {
code.Print("for _, item := range m.%s {", fieldName)
code.Print("if item != nil {")
code.Print(" _, err := item.ResolveReferences(root)")
code.Print(" if err != nil {")
code.Print(" errors = append(errors, err)")
code.Print(" }")
code.Print("}")
code.Print("}")
}
}
}
}
code.Print(" return nil, compiler.NewErrorGroupOrNil(errors)")
code.Print("}\n")
}
// ToRawInfo() methods
func (domain *Domain) generateToRawInfoMethodForType(code *printer.Code, typeName string) {
code.Print("// ToRawInfo returns a description of %s suitable for JSON or YAML export.", typeName)
code.Print("func (m *%s) ToRawInfo() interface{} {", typeName)
typeModel := domain.TypeModels[typeName]
if typeName == "Any" {
code.Print("var err error")
code.Print("var info1 []yaml.MapSlice")
code.Print("err = yaml.Unmarshal([]byte(m.Yaml), &info1)")
code.Print("if err == nil {return info1}")
code.Print("var info2 yaml.MapSlice")
code.Print("err = yaml.Unmarshal([]byte(m.Yaml), &info2)")
code.Print("if err == nil {return info2}")
code.Print("var info3 interface{}")
code.Print("err = yaml.Unmarshal([]byte(m.Yaml), &info3)")
code.Print("if err == nil {return info3}")
code.Print("return nil")
} else if typeName == "StringArray" {
code.Print("return m.Value")
} else if typeModel.OneOfWrapper {
code.Print("// ONE OF WRAPPER")
code.Print("// %s", typeModel.Name)
for i, item := range typeModel.Properties {
code.Print("// %+v", *item)
if item.Type == "float" {
code.Print("if v%d, ok := m.GetOneof().(*%s_Number); ok {", i, typeName)
code.Print("return v%d.Number", i)
code.Print("}")
} else if item.Type == "bool" {
code.Print("if v%d, ok := m.GetOneof().(*%s_Boolean); ok {", i, typeName)
code.Print("return v%d.Boolean", i)
code.Print("}")
} else if item.Type == "string" {
code.Print("if v%d, ok := m.GetOneof().(*%s_String_); ok {", i, typeName)
code.Print("return v%d.String_", i)
code.Print("}")
} else {
code.Print("v%d := m.Get%s()", i, item.Type)
code.Print("if v%d != nil {", i)
code.Print(" return v%d.ToRawInfo()", i)
code.Print("}")
}
}
code.Print("return nil")
} else {
code.Print("info := yaml.MapSlice{}")
for _, propertyModel := range typeModel.Properties {
switch propertyModel.Type {
case "string":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != \"\" {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
}
case "bool":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != false {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
}
case "int":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
}
case "float":
propertyName := propertyModel.Name
if !propertyModel.Repeated {
code.Print("if m.%s != 0.0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s})", propertyName, propertyModel.FieldName())
code.Print("}")
}
default:
propertyName := propertyModel.Name
if propertyName == "value" {
code.Print("// %+v", propertyModel)
} else if !propertyModel.Repeated {
code.Print("if m.%s != nil {", propertyModel.FieldName())
if propertyModel.Type == "TypeItem" {
code.Print("if len(m.Type.Value) == 1 {")
code.Print("info = append(info, yaml.MapItem{\"type\", m.Type.Value[0]})")
code.Print("} else {")
code.Print("info = append(info, yaml.MapItem{\"type\", m.Type.Value})")
code.Print("}")
} else if propertyModel.Type == "ItemsItem" {
code.Print("items := make([]interface{}, 0)")
if domain.Version == "v2" {
code.Print("for _, item := range m.Items.Schema {")
} else {
code.Print("for _, item := range m.Items.SchemaOrReference {")
}
code.Print(" items = append(items, item.ToRawInfo())")
code.Print("}")
code.Print("info = append(info, yaml.MapItem{\"items\", items[0]})")
} else {
code.Print("info = append(info, yaml.MapItem{\"%s\", m.%s.ToRawInfo()})",
propertyName, propertyModel.FieldName())
}
code.Print("}")
code.Print("// %+v", propertyModel)
} else if propertyModel.MapType == "string" {
code.Print("// %+v", propertyModel)
} else if propertyModel.MapType != "" {
code.Print("if m.%s != nil {", propertyModel.FieldName())
code.Print("for _, item := range m.%s {", propertyModel.FieldName())
code.Print("info = append(info, yaml.MapItem{item.Name, item.Value.ToRawInfo()})")
code.Print("}")
code.Print("}")
code.Print("// %+v", propertyModel)
} else {
code.Print("if len(m.%s) != 0 {", propertyModel.FieldName())
code.Print("items := make([]interface{}, 0)")
code.Print("for _, item := range m.%s {", propertyModel.FieldName())
code.Print("items = append(items, item.ToRawInfo())")
code.Print("}")
code.Print("info = append(info, yaml.MapItem{\"%s\", items})", propertyName)
code.Print("}")
code.Print("// %+v", propertyModel)
}
}
}
code.Print("return info")
}
code.Print("}\n")
}
func (domain *Domain) generateConstantVariables(code *printer.Code, regexPatterns *patternNames) {
names := regexPatterns.Names()
var sortedNames []string
for name, _ := range names {
sortedNames = append(sortedNames, name)
}
sort.Strings(sortedNames)
code.Print("var (")
for _, name := range sortedNames {
code.Print("%s = regexp.MustCompile(\"%s\")", name, escapeSlashes(names[name]))
}
code.Print(")\n")
}

View File

@ -0,0 +1,363 @@
// 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.
package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"runtime"
"sort"
"strings"
"github.com/googleapis/gnostic/compiler"
"github.com/googleapis/gnostic/jsonschema"
"github.com/googleapis/gnostic/printer"
)
var protoOptionsForExtensions = []ProtoOption{
ProtoOption{
Name: "java_multiple_files",
Value: "true",
Comment: "// This option lets the proto compiler generate Java code inside the package\n" +
"// name (see below) instead of inside an outer class. It creates a simpler\n" +
"// developer experience by reducing one-level of name nesting and be\n" +
"// consistent with most programming languages that don't support outer classes.",
},
ProtoOption{
Name: "java_outer_classname",
Value: "VendorExtensionProto",
Comment: "// The Java outer classname should be the filename in UpperCamelCase. This\n" +
"// class is only used to hold proto descriptor, so developers don't need to\n" +
"// work with it directly.",
},
}
const additionalCompilerCodeWithMain = "" +
"func handleExtension(extensionName string, yamlInput string) (bool, proto.Message, error) {\n" +
" switch extensionName {\n" +
" // All supported extensions\n" +
" %s\n" +
" default:\n" +
" return false, nil, nil\n" +
" }\n" +
"}\n" +
"\n" +
"func main() {\n" +
" openapiextension_v1.ProcessExtension(handleExtension)\n" +
"}\n"
const caseStringForObjectTypes = "\n" +
"case \"%s\":\n" +
"var info yaml.MapSlice\n" +
"err := yaml.Unmarshal([]byte(yamlInput), &info)\n" +
"if err != nil {\n" +
" return true, nil, err\n" +
"}\n" +
"newObject, err := %s.New%s(info, compiler.NewContext(\"$root\", nil))\n" +
"return true, newObject, err"
const caseStringForWrapperTypes = "\n" +
"case \"%s\":\n" +
"var info %s\n" +
"err := yaml.Unmarshal([]byte(yamlInput), &info)\n" +
"if err != nil {\n" +
" return true, nil, err\n" +
"}\n" +
"newObject := &wrappers.%s{Value: info}\n" +
"return true, newObject, nil"
// generateMainFile generates the main program for an extension.
func generateMainFile(packageName string, license string, codeBody string, imports []string) string {
code := &printer.Code{}
code.Print(license)
code.Print("// THIS FILE IS AUTOMATICALLY GENERATED.\n")
// generate package declaration
code.Print("package %s\n", packageName)
code.Print("import (")
for _, filename := range imports {
code.Print("\"" + filename + "\"")
}
code.Print(")\n")
code.Print(codeBody)
return code.String()
}
func getBaseFileNameWithoutExt(filePath string) string {
tmp := filepath.Base(filePath)
return tmp[0 : len(tmp)-len(filepath.Ext(tmp))]
}
func toProtoPackageName(input string) string {
var out = ""
nonAlphaNumeric := regexp.MustCompile("[^0-9A-Za-z_]+")
input = nonAlphaNumeric.ReplaceAllString(input, "")
for index, character := range input {
if character >= 'A' && character <= 'Z' {
if index > 0 && input[index-1] != '_' {
out += "_"
}
out += string(character - 'A' + 'a')
} else {
out += string(character)
}
}
return out
}
type primitiveTypeInfo struct {
goTypeName string
wrapperProtoName string
}
var supportedPrimitiveTypeInfos = map[string]primitiveTypeInfo{
"string": primitiveTypeInfo{goTypeName: "string", wrapperProtoName: "StringValue"},
"number": primitiveTypeInfo{goTypeName: "float64", wrapperProtoName: "DoubleValue"},
"integer": primitiveTypeInfo{goTypeName: "int64", wrapperProtoName: "Int64Value"},
"boolean": primitiveTypeInfo{goTypeName: "bool", wrapperProtoName: "BoolValue"},
// TODO: Investigate how to support arrays. For now users will not be allowed to
// create extension handlers for arrays and they will have to use the
// plane yaml string as is.
}
type generatedTypeInfo struct {
schemaName string
// if this is not nil, the schema should be treataed as a primitive type.
optionalPrimitiveTypeInfo *primitiveTypeInfo
}
// GenerateExtension generates the implementation of an extension.
func GenerateExtension(schemaFile string, outDir string) error {
outFileBaseName := getBaseFileNameWithoutExt(schemaFile)
extensionNameWithoutXDashPrefix := outFileBaseName[len("x-"):]
outDir = path.Join(outDir, "gnostic-x-"+extensionNameWithoutXDashPrefix)
protoPackage := toProtoPackageName(extensionNameWithoutXDashPrefix)
protoPackageName := strings.ToLower(protoPackage)
goPackageName := protoPackageName
protoOutDirectory := outDir + "/" + "proto"
var err error
projectRoot := os.Getenv("GOPATH") + "/src/github.com/googleapis/gnostic/"
baseSchema, err := jsonschema.NewSchemaFromFile(projectRoot + "jsonschema/schema.json")
if err != nil {
return err
}
baseSchema.ResolveRefs()
baseSchema.ResolveAllOfs()
openapiSchema, err := jsonschema.NewSchemaFromFile(schemaFile)
if err != nil {
return err
}
openapiSchema.ResolveRefs()
openapiSchema.ResolveAllOfs()
// build a simplified model of the types described by the schema
cc := NewDomain(openapiSchema, "v2") // TODO fix for OpenAPI v3
// create a type for each object defined in the schema
extensionNameToMessageName := make(map[string]generatedTypeInfo)
schemaErrors := make([]error, 0)
supportedPrimitives := make([]string, 0)
for key := range supportedPrimitiveTypeInfos {
supportedPrimitives = append(supportedPrimitives, key)
}
sort.Strings(supportedPrimitives)
if cc.Schema.Definitions != nil {
for _, pair := range *(cc.Schema.Definitions) {
definitionName := pair.Name
definitionSchema := pair.Value
// ensure the id field is set
if definitionSchema.ID == nil || len(*(definitionSchema.ID)) == 0 {
schemaErrors = append(schemaErrors,
fmt.Errorf("schema %s has no 'id' field, which must match the "+
"name of the OpenAPI extension that the schema represents",
definitionName))
} else {
if _, ok := extensionNameToMessageName[*(definitionSchema.ID)]; ok {
schemaErrors = append(schemaErrors,
fmt.Errorf("schema %s and %s have the same 'id' field value",
definitionName, extensionNameToMessageName[*(definitionSchema.ID)].schemaName))
} else if (definitionSchema.Type == nil) || (*definitionSchema.Type.String == "object") {
extensionNameToMessageName[*(definitionSchema.ID)] = generatedTypeInfo{schemaName: definitionName}
} else {
// this is a primitive type
if val, ok := supportedPrimitiveTypeInfos[*definitionSchema.Type.String]; ok {
extensionNameToMessageName[*(definitionSchema.ID)] = generatedTypeInfo{schemaName: definitionName, optionalPrimitiveTypeInfo: &val}
} else {
schemaErrors = append(schemaErrors,
fmt.Errorf("Schema %s has type '%s' which is "+
"not supported. Supported primitive types are "+
"%s.\n", definitionName,
*definitionSchema.Type.String,
supportedPrimitives))
}
}
}
typeName := cc.TypeNameForStub(definitionName)
typeModel := cc.BuildTypeForDefinition(typeName, definitionName, definitionSchema)
if typeModel != nil {
cc.TypeModels[typeName] = typeModel
}
}
}
if len(schemaErrors) > 0 {
// error has been reported.
return compiler.NewErrorGroupOrNil(schemaErrors)
}
err = os.MkdirAll(outDir, os.ModePerm)
if err != nil {
return err
}
err = os.MkdirAll(protoOutDirectory, os.ModePerm)
if err != nil {
return err
}
// generate the protocol buffer description
protoOptions := append(protoOptionsForExtensions,
ProtoOption{Name: "java_package", Value: "org.openapi.extension." + strings.ToLower(protoPackage), Comment: "// The Java package name must be proto package name with proper prefix."},
ProtoOption{Name: "objc_class_prefix", Value: strings.ToLower(protoPackage),
Comment: "// A reasonable prefix for the Objective-C symbols generated from the package.\n" +
"// It should at a minimum be 3 characters long, all uppercase, and convention\n" +
"// is to use an abbreviation of the package name. Something short, but\n" +
"// hopefully unique enough to not conflict with things that may come along in\n" +
"// the future. 'GPB' is reserved for the protocol buffer implementation itself.",
})
proto := cc.generateProto(protoPackageName, License, protoOptions, nil)
protoFilename := path.Join(protoOutDirectory, outFileBaseName+".proto")
err = ioutil.WriteFile(protoFilename, []byte(proto), 0644)
if err != nil {
return err
}
// generate the compiler
compiler := cc.GenerateCompiler(goPackageName, License, []string{
"fmt",
"regexp",
"strings",
"github.com/googleapis/gnostic/compiler",
"gopkg.in/yaml.v2",
})
goFilename := path.Join(protoOutDirectory, outFileBaseName+".go")
err = ioutil.WriteFile(goFilename, []byte(compiler), 0644)
if err != nil {
return err
}
err = exec.Command(runtime.GOROOT()+"/bin/gofmt", "-w", goFilename).Run()
// generate the main file.
outDirRelativeToGoPathSrc := strings.Replace(outDir, path.Join(os.Getenv("GOPATH"), "src")+"/", "", 1)
var extensionNameKeys []string
for k := range extensionNameToMessageName {
extensionNameKeys = append(extensionNameKeys, k)
}
sort.Strings(extensionNameKeys)
wrapperTypeIncluded := false
var cases string
for _, extensionName := range extensionNameKeys {
if extensionNameToMessageName[extensionName].optionalPrimitiveTypeInfo == nil {
cases += fmt.Sprintf(caseStringForObjectTypes, extensionName, goPackageName, extensionNameToMessageName[extensionName].schemaName)
} else {
wrapperTypeIncluded = true
cases += fmt.Sprintf(caseStringForWrapperTypes, extensionName, extensionNameToMessageName[extensionName].optionalPrimitiveTypeInfo.goTypeName, extensionNameToMessageName[extensionName].optionalPrimitiveTypeInfo.wrapperProtoName)
}
}
extMainCode := fmt.Sprintf(additionalCompilerCodeWithMain, cases)
imports := []string{
"github.com/golang/protobuf/proto",
"github.com/googleapis/gnostic/extensions",
"github.com/googleapis/gnostic/compiler",
"gopkg.in/yaml.v2",
outDirRelativeToGoPathSrc + "/" + "proto",
}
if wrapperTypeIncluded {
imports = append(imports, "github.com/golang/protobuf/ptypes/wrappers")
}
main := generateMainFile("main", License, extMainCode, imports)
mainFileName := path.Join(outDir, "main.go")
err = ioutil.WriteFile(mainFileName, []byte(main), 0644)
if err != nil {
return err
}
// format the compiler
return exec.Command(runtime.GOROOT()+"/bin/gofmt", "-w", mainFileName).Run()
}
func processExtensionGenCommandline(usage string) error {
outDir := ""
schameFile := ""
extParamRegex, _ := regexp.Compile("--(.+)=(.+)")
for i, arg := range os.Args {
if i == 0 {
continue // skip the tool name
}
var m [][]byte
if m = extParamRegex.FindSubmatch([]byte(arg)); m != nil {
flagName := string(m[1])
flagValue := string(m[2])
switch flagName {
case "out_dir":
outDir = flagValue
default:
fmt.Printf("Unknown option: %s.\n%s\n", arg, usage)
os.Exit(-1)
}
} else if arg == "--extension" {
continue
} else if arg[0] == '-' {
fmt.Printf("Unknown option: %s.\n%s\n", arg, usage)
os.Exit(-1)
} else {
schameFile = arg
}
}
if schameFile == "" {
fmt.Printf("No input json schema specified.\n%s\n", usage)
os.Exit(-1)
}
if outDir == "" {
fmt.Printf("Missing output directive.\n%s\n", usage)
os.Exit(-1)
}
if !strings.HasPrefix(getBaseFileNameWithoutExt(schameFile), "x-") {
fmt.Printf("Schema file name has to start with 'x-'.\n%s\n", usage)
os.Exit(-1)
}
return GenerateExtension(schameFile, outDir)
}

View File

@ -0,0 +1,52 @@
package main
import (
"io/ioutil"
"os"
"os/exec"
"testing"
)
func TestErrorExtensionGeneratorUnsupportedPrimitive(t *testing.T) {
var err error
output, err := exec.Command(
"generator",
"--extension",
"test/x-unsupportedprimitives.json",
"--out_dir=/tmp",
).Output()
outputFile := "x-unsupportedprimitives.errors"
_ = ioutil.WriteFile(outputFile, output, 0644)
err = exec.Command("diff", outputFile, "test/errors/x-unsupportedprimitives.errors").Run()
if err != nil {
t.Logf("Diff failed: %+v", err)
t.FailNow()
} else {
// if the test succeeded, clean up
os.Remove(outputFile)
}
}
func TestErrorExtensionGeneratorNameCollision(t *testing.T) {
var err error
output, err := exec.Command(
"generator",
"--extension",
"test/x-extension-name-collision.json",
"--out_dir=/tmp",
).Output()
outputFile := "x-extension-name-collision.errors"
_ = ioutil.WriteFile(outputFile, output, 0644)
err = exec.Command("diff", outputFile, "test/errors/x-extension-name-collision.errors").Run()
if err != nil {
t.Logf("Diff failed: %+v", err)
t.FailNow()
} else {
// if the test succeeded, clean up
os.Remove(outputFile)
}
}

View File

@ -0,0 +1,119 @@
// 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.
package main
import (
"fmt"
"strings"
"github.com/googleapis/gnostic/printer"
)
// ProtoOption represents an option to be added to generated .proto files.
type ProtoOption struct {
Name string
Value string
Comment string
}
func (domain *Domain) generateProto(packageName string, license string, options []ProtoOption, imports []string) string {
code := &printer.Code{}
code.Print(license)
code.Print("// THIS FILE IS AUTOMATICALLY GENERATED.")
code.Print()
code.Print("syntax = \"proto3\";")
code.Print()
code.Print("package " + packageName + ";")
for _, importString := range imports {
code.Print()
code.Print("import \"" + importString + "\";")
}
code.Print()
// generate option declarations
for _, option := range options {
commentLines := strings.Split(option.Comment, "\n")
for _, commentLine := range commentLines {
code.Print(commentLine)
}
line := "option " + option.Name + " = "
if option.Value == "true" || option.Value == "false" {
line += option.Value
} else {
line += "\"" + option.Value + "\""
}
line += ";\n"
code.Print(line)
}
// generate message definitions
typeNames := domain.sortedTypeNames()
for _, typeName := range typeNames {
typeModel := domain.TypeModels[typeName]
if typeModel.Description != "" {
code.Print("// %s", typeModel.Description)
}
code.Print("message %s {", typeName)
code.Indent()
if typeModel.OneOfWrapper {
code.Print("oneof oneof {")
code.Indent()
}
var fieldNumber = 0
for _, propertyModel := range typeModel.Properties {
if propertyModel.Description != "" {
code.Print("// %s", propertyModel.Description)
}
propertyName := propertyModel.Name
fieldNumber++
propertyType := propertyModel.Type
if propertyType == "int" {
propertyType = "int64"
}
if propertyType == "float" {
propertyType = "double"
}
// TODO may be remove this.
if propertyType == "blob" {
propertyType = "string"
}
var displayName = propertyName
if displayName == "$ref" {
displayName = "_ref"
}
if displayName == "$schema" {
displayName = "_schema"
}
displayName = camelCaseToSnakeCase(displayName)
var line = fmt.Sprintf("%s %s = %d;", propertyType, displayName, fieldNumber)
if propertyModel.Repeated {
line = "repeated " + line
}
code.Print(line)
}
if typeModel.OneOfWrapper {
code.Outdent()
code.Print("}")
}
code.Outdent()
code.Print("}")
code.Print()
}
return code.String()
}

View File

@ -0,0 +1,55 @@
// 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.
package main
import (
"strings"
"unicode"
)
// Returns a "snake case" form of a camel-cased string.
func camelCaseToSnakeCase(input string) string {
out := ""
for index, runeValue := range input {
//fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
if runeValue >= 'A' && runeValue <= 'Z' {
if index > 0 {
out += "_"
}
out += string(runeValue - 'A' + 'a')
} else {
out += string(runeValue)
}
}
return out
}
func snakeCaseToCamelCase(input string) string {
out := ""
words := strings.Split(input, "_")
for i, word := range words {
if (i > 0) && len(word) > 0 {
w := []rune(word)
w[0] = unicode.ToUpper(w[0])
out += string(w)
} else {
out += word
}
}
return out
}

View File

@ -0,0 +1,257 @@
// 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.
// generator generates Protocol Buffer models and support code from
// JSON Schemas. It is used to generate representations of the
// OpenAPI Specification and vendor and specification extensions
// that are added by third-party OpenAPI authors.
package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path"
"runtime"
"strings"
"github.com/googleapis/gnostic/jsonschema"
)
// License is the software license applied to generated code.
const License = "" +
"// Copyright 2017 Google Inc. All Rights Reserved.\n" +
"//\n" +
"// Licensed under the Apache License, Version 2.0 (the \"License\");\n" +
"// you may not use this file except in compliance with the License.\n" +
"// You may obtain a copy of the License at\n" +
"//\n" +
"// http://www.apache.org/licenses/LICENSE-2.0\n" +
"//\n" +
"// Unless required by applicable law or agreed to in writing, software\n" +
"// distributed under the License is distributed on an \"AS IS\" BASIS,\n" +
"// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" +
"// See the License for the specific language governing permissions and\n" +
"// limitations under the License.\n"
func protoOptions(packageName string) []ProtoOption {
return []ProtoOption{
ProtoOption{
Name: "java_multiple_files",
Value: "true",
Comment: "// This option lets the proto compiler generate Java code inside the package\n" +
"// name (see below) instead of inside an outer class. It creates a simpler\n" +
"// developer experience by reducing one-level of name nesting and be\n" +
"// consistent with most programming languages that don't support outer classes.",
},
ProtoOption{
Name: "java_outer_classname",
Value: "OpenAPIProto",
Comment: "// The Java outer classname should be the filename in UpperCamelCase. This\n" +
"// class is only used to hold proto descriptor, so developers don't need to\n" +
"// work with it directly.",
},
ProtoOption{
Name: "java_package",
Value: "org." + packageName,
Comment: "// The Java package name must be proto package name with proper prefix.",
},
ProtoOption{
Name: "objc_class_prefix",
Value: "OAS",
Comment: "// A reasonable prefix for the Objective-C symbols generated from the package.\n" +
"// It should at a minimum be 3 characters long, all uppercase, and convention\n" +
"// is to use an abbreviation of the package name. Something short, but\n" +
"// hopefully unique enough to not conflict with things that may come along in\n" +
"// the future. 'GPB' is reserved for the protocol buffer implementation itself.",
},
}
}
func generateOpenAPIModel(version string) error {
var input string
var filename string
var protoPackageName string
switch version {
case "v2":
input = "openapi-2.0.json"
filename = "OpenAPIv2"
protoPackageName = "openapi.v2"
case "v3":
input = "openapi-3.0.json"
filename = "OpenAPIv3"
protoPackageName = "openapi.v3"
case "discovery":
input = "discovery.json"
filename = "discovery"
protoPackageName = "discovery.v1"
default:
return fmt.Errorf("Unknown OpenAPI version %s", version)
}
goPackageName := strings.Replace(protoPackageName, ".", "_", -1)
projectRoot := os.Getenv("GOPATH") + "/src/github.com/googleapis/gnostic/"
baseSchema, err := jsonschema.NewSchemaFromFile(projectRoot + "jsonschema/schema.json")
if err != nil {
return err
}
baseSchema.ResolveRefs()
baseSchema.ResolveAllOfs()
openapiSchema, err := jsonschema.NewSchemaFromFile(projectRoot + filename + "/" + input)
if err != nil {
return err
}
openapiSchema.ResolveRefs()
openapiSchema.ResolveAllOfs()
// build a simplified model of the types described by the schema
cc := NewDomain(openapiSchema, version)
// generators will map these patterns to the associated property names
// these pattern names are a bit of a hack until we find a more automated way to obtain them
switch version {
case "v2":
cc.TypeNameOverrides = map[string]string{
"VendorExtension": "Any",
}
cc.PropertyNameOverrides = map[string]string{
"PathItem": "Path",
"ResponseValue": "ResponseCode",
}
case "v3":
cc.TypeNameOverrides = map[string]string{
"SpecificationExtension": "Any",
}
cc.PropertyNameOverrides = map[string]string{
"PathItem": "Path",
"ResponseValue": "ResponseCode",
}
case "discovery":
cc.TypeNameOverrides = map[string]string{}
cc.PropertyNameOverrides = map[string]string{}
default:
return fmt.Errorf("Unknown OpenAPI version %s", version)
}
err = cc.Build()
if err != nil {
return err
}
if true {
log.Printf("Type Model:\n%s", cc.Description())
}
// ensure that the target directory exists
err = os.MkdirAll(projectRoot+filename, 0755)
if err != nil {
return err
}
// generate the protocol buffer description
log.Printf("Generating protocol buffer description")
proto := cc.generateProto(protoPackageName, License,
protoOptions(goPackageName), []string{"google/protobuf/any.proto"})
protoFileName := projectRoot + filename + "/" + filename + ".proto"
err = ioutil.WriteFile(protoFileName, []byte(proto), 0644)
if err != nil {
return err
}
// generate the compiler
log.Printf("Generating compiler support code")
compiler := cc.GenerateCompiler(goPackageName, License, []string{
"fmt",
"gopkg.in/yaml.v2",
"strings",
"regexp",
"github.com/googleapis/gnostic/compiler",
})
goFileName := projectRoot + filename + "/" + filename + ".go"
err = ioutil.WriteFile(goFileName, []byte(compiler), 0644)
if err != nil {
return err
}
// format the compiler
log.Printf("Formatting compiler support code")
return exec.Command(runtime.GOROOT()+"/bin/gofmt", "-w", goFileName).Run()
}
func usage() string {
return fmt.Sprintf(`
Usage: %s [OPTIONS]
Options:
--v2
Generate Protocol Buffer representation and support code for OpenAPI v2.
Files are read from and written to appropriate locations in the gnostic
project directory.
--v3
Generate Protocol Buffer representation and support code for OpenAPI v3
Files are read from and written to appropriate locations in the gnostic
project directory.
--extension EXTENSION_SCHEMA [EXTENSIONOPTIONS]
Generate a gnostic extension that reads a set of OpenAPI extensions.
EXTENSION_SCHEMA is the json schema for the OpenAPI extensions to be
supported.
EXTENSION_OPTIONS
--out_dir=PATH: Location for writing extension models and support code.
`, path.Base(os.Args[0]))
}
func main() {
var openapiVersion = ""
var generateExtensions = false
for i, arg := range os.Args {
if i == 0 {
continue // skip the tool name
}
if arg == "--v2" {
openapiVersion = "v2"
} else if arg == "--v3" {
openapiVersion = "v3"
} else if arg == "--discovery" {
openapiVersion = "discovery"
} else if arg == "--extension" {
generateExtensions = true
break
} else {
fmt.Printf("Unknown option: %s.\n%s\n", arg, usage())
os.Exit(-1)
}
}
if openapiVersion != "" {
err := generateOpenAPIModel(openapiVersion)
if err != nil {
fmt.Printf("%+v\n", err)
}
} else if generateExtensions {
err := processExtensionGenCommandline(usage())
if err != nil {
fmt.Printf("%+v\n", err)
}
} else {
fmt.Printf("%s\n", usage())
}
}

View File

@ -0,0 +1,2 @@
Schema SampleCompanyTwoPrimitiveString and SampleCompanyOnePrimitiveString have the same 'id' field value.

View File

@ -0,0 +1,2 @@
Schema SampleCompanyOnePrimitiveString has type 'unsupportedtype' which is not supported. Supported primitive types are [boolean integer number string].

View File

@ -0,0 +1,12 @@
{
"definitions": {
"SampleCompanyOnePrimitiveString": {
"type": "string",
"id": "x-samplecompanyone-mystr"
},
"SampleCompanyTwoPrimitiveString": {
"type": "string",
"id": "x-samplecompanyone-mystr"
}
}
}

View File

@ -0,0 +1,8 @@
{
"definitions": {
"SampleCompanyOnePrimitiveString": {
"type": "unsupportedtype",
"id": "x-samplecompanyone-mystr"
}
}
}

View File

@ -0,0 +1,132 @@
// 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.
package main
import (
"fmt"
"strings"
"github.com/googleapis/gnostic/jsonschema"
)
/// Type Modeling
// TypeRequest models types that we encounter during model-building that have no named schema.
type TypeRequest struct {
Name string // name of type to be created
PropertyName string // name of a property that refers to this type
Schema *jsonschema.Schema // schema for type
OneOfWrapper bool // true if the type wraps "oneOfs"
}
// NewTypeRequest creates a TypeRequest.
func NewTypeRequest(name string, propertyName string, schema *jsonschema.Schema) *TypeRequest {
return &TypeRequest{Name: name, PropertyName: propertyName, Schema: schema}
}
// TypeProperty models type properties, eg. fields.
type TypeProperty struct {
Name string // name of property
Type string // type for property (scalar or message type)
StringEnumValues []string // possible values if this is an enumerated string type
MapType string // if this property is for a map, the name of the mapped type
Repeated bool // true if this property is repeated (an array)
Pattern string // if the property is a pattern property, names must match this pattern.
Implicit bool // true if this property is implied by a pattern or "additional properties" property
Description string // if present, the "description" field in the schema
}
func (typeProperty *TypeProperty) description() string {
result := ""
if typeProperty.Description != "" {
result += fmt.Sprintf("\t// %+s\n", typeProperty.Description)
}
if typeProperty.Repeated {
result += fmt.Sprintf("\t%s %s repeated %s\n", typeProperty.Name, typeProperty.Type, typeProperty.Pattern)
} else {
result += fmt.Sprintf("\t%s %s %s \n", typeProperty.Name, typeProperty.Type, typeProperty.Pattern)
}
return result
}
// NewTypeProperty creates a TypeProperty
func NewTypeProperty() *TypeProperty {
return &TypeProperty{}
}
// NewTypePropertyWithNameAndType creates a TypeProperty
func NewTypePropertyWithNameAndType(name string, typeName string) *TypeProperty {
return &TypeProperty{Name: name, Type: typeName}
}
// NewTypePropertyWithNameTypeAndPattern creates a TypeProperty
func NewTypePropertyWithNameTypeAndPattern(name string, typeName string, pattern string) *TypeProperty {
return &TypeProperty{Name: name, Type: typeName, Pattern: pattern}
}
// FieldName returns the message field name to use for a property.
func (typeProperty *TypeProperty) FieldName() string {
propertyName := typeProperty.Name
if propertyName == "$ref" {
return "XRef"
}
return strings.Title(snakeCaseToCamelCase(propertyName))
}
// TypeModel models types.
type TypeModel struct {
Name string // type name
Properties []*TypeProperty // slice of properties
Required []string // required property names
OneOfWrapper bool // true if this type wraps "oneof" properties
Open bool // open types can have keys outside the specified set
OpenPatterns []string // patterns for properties that we allow
IsStringArray bool // ugly override
IsItemArray bool // ugly override
IsBlob bool // ugly override
IsPair bool // type is a name-value pair used to support ordered maps
PairValueType string // type for pair values (valid if IsPair == true)
Description string // if present, the "description" field in the schema
}
func (typeModel *TypeModel) addProperty(property *TypeProperty) {
if typeModel.Properties == nil {
typeModel.Properties = make([]*TypeProperty, 0)
}
typeModel.Properties = append(typeModel.Properties, property)
}
func (typeModel *TypeModel) description() string {
result := ""
if typeModel.Description != "" {
result += fmt.Sprintf("// %+s\n", typeModel.Description)
}
var wrapperinfo string
if typeModel.OneOfWrapper {
wrapperinfo = " oneof wrapper"
}
result += fmt.Sprintf("%+s%s\n", typeModel.Name, wrapperinfo)
for _, property := range typeModel.Properties {
result += property.description()
}
return result
}
// NewTypeModel creates a TypeModel.
func NewTypeModel() *TypeModel {
typeModel := &TypeModel{}
typeModel.Properties = make([]*TypeProperty, 0)
return typeModel
}