| // 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{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName()) |
| code.Print("}") |
| } else { |
| code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) |
| code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value: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{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName()) |
| code.Print("}") |
| } else { |
| code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) |
| code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value: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{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName()) |
| code.Print("}") |
| } else { |
| code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) |
| code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value: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{Key:\"%s\", Value:m.%s})", propertyName, propertyModel.FieldName()) |
| code.Print("}") |
| } else { |
| code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) |
| code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value: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{Key:\"type\", Value:m.Type.Value[0]})") |
| code.Print("} else {") |
| code.Print("info = append(info, yaml.MapItem{Key:\"type\", Value: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{Key:\"items\", Value:items[0]})") |
| } else { |
| code.Print("info = append(info, yaml.MapItem{Key:\"%s\", Value: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{Key:item.Name, Value: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{Key:\"%s\", Value: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") |
| } |