// Copyright 2020 Google LLC. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package 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) } // generate precompiled regexps for use during parsing 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 *yaml.Node, 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("v1 := in") code.Print("switch v1.Kind {") code.Print("case yaml.ScalarNode:") code.Print(" x.Value = make([]string, 0)") code.Print(" x.Value = append(x.Value, v1.Value)") code.Print("case yaml.SequenceNode:") code.Print(" x.Value = make([]string, 0)") code.Print(" for _, v := range v1.Content {") code.Print(" value := v.Value") code.Print(" ok := v.Kind == yaml.ScalarNode") 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(\"\", m, 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(\"\", m, 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 := compiler.Marshal(in)") code.Print("x.Yaml = string(bytes)") } else if typeModel.Name == "StringArray" { code.Print("x := &StringArray{}") code.Print("x.Value = make([]string, 0)") code.Print("for _, node := range in.Content {") code.Print(" s, _ := compiler.StringForScalarNode(node)") code.Print(" x.Value = append(x.Value, s)") code.Print("}") } else if typeModel.Name == "SpecificationExtension" { code.Print(" x := &SpecificationExtension{}") code.Print(" matched := false") code.Print(" switch in.Tag {") code.Print(" case \"!!bool\":") code.Print(" var v bool") code.Print(" v, matched = compiler.BoolForScalarNode(in)") code.Print(" x.Oneof = &SpecificationExtension_Boolean{Boolean: v}") code.Print(" case \"!!str\":") code.Print(" var v string") code.Print(" v, matched = compiler.StringForScalarNode(in)") code.Print(" x.Oneof = &SpecificationExtension_String_{String_: v}") code.Print(" case \"!!float\":") code.Print(" var v float64") code.Print(" v, matched = compiler.FloatForScalarNode(in)") code.Print(" x.Oneof = &SpecificationExtension_Number{Number: v}") code.Print(" case \"!!int\":") code.Print(" var v int64") code.Print(" v, matched = compiler.IntForScalarNode(in)") code.Print(" x.Oneof = &SpecificationExtension_Number{Number: float64(v)}") 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.Tag {") code.Print(" case \"!!bool\":") code.Print(" var v bool") code.Print(" v, matched = compiler.BoolForScalarNode(in)") code.Print(" x.Oneof = &DefaultType_Boolean{Boolean: v}") code.Print(" case \"!!str\":") code.Print(" var v string") code.Print(" v, matched = compiler.StringForScalarNode(in)") code.Print(" x.Oneof = &DefaultType_String_{String_: v}") code.Print(" case \"!!float\":") code.Print(" var v float64") code.Print(" v, matched = compiler.FloatForScalarNode(in)") code.Print(" x.Oneof = &DefaultType_Number{Number: v}") code.Print(" case \"!!int\":") code.Print(" var v int64") code.Print(" v, matched = compiler.IntForScalarNode(in)") code.Print(" x.Oneof = &DefaultType_Number{Number: float64(v)}") 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 := compiler.SequenceNodeForNode(v%d)", fieldNumber) code.Print(" if ok {") code.Print(" for _, item := range a.Content {") code.Print(" y, err := New%s(item, compiler.NewContext(\"%s\", item, 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\", m, 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\", v%d, context))", fieldName, typeModel.Name, fieldNumber, propertyName, fieldNumber) 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 := compiler.SequenceNodeForNode(v%d)", fieldNumber) code.Print(" if ok {") code.Print(" x.%s = compiler.StringArrayForSequenceNode(v)", fieldName) code.Print(" } else {") code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%s\", compiler.Display(v%d))", propertyName, 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: %%s\", compiler.Display(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 = compiler.StringForScalarNode(v%d)", fieldName, fieldNumber) code.Print(" if !ok {") code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%s\", compiler.Display(v%d))", propertyName, 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: %%s\", compiler.Display(v%d))", propertyName, 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(" v, ok := compiler.FloatForScalarNode(v%d)", fieldNumber) code.Print(" if ok {") code.Print(" x.%s = v", fieldName) code.Print(" } else {") code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%s\", compiler.Display(v%d))", propertyName, 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 := compiler.IntForScalarNode(v%d)", 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: %%s\", compiler.Display(v%d))", propertyName, 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 := compiler.BoolForScalarNode(in)") code.Print("if ok {") code.Print(" x.Oneof = &%s_%s{%s: boolValue}", parentTypeName, propertyName, propertyName) code.Print(" matched = true") 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 = compiler.BoolForScalarNode(v%d)", fieldName, fieldNumber) code.Print(" if !ok {") code.Print(" message := fmt.Sprintf(\"has unexpected value for %s: %%s\", compiler.Display(v%d))", propertyName, 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 i := 0; i < len(m.Content); i += 2 {") code.Print("k, ok := compiler.StringForScalarNode(m.Content[i])") code.Print("if ok {") code.Print("v := m.Content[i+1]") 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, _ = compiler.StringForScalarNode(v)") } else if mapTypeName == "Any" { code.Print("result := &Any{}") code.Print("handled, resultFromExt, err := compiler.CallExtension(context, v, k)") code.Print("if handled {") code.Print(" if err != nil {") code.Print(" errors = append(errors, err)") code.Print(" } else {") code.Print(" bytes := compiler.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, v, 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, v, 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)") generateMatchErrors := true // TODO: enable this and update tests for new error messages if generateMatchErrors { code.Print("} else {") code.Print(" message := fmt.Sprintf(\"contains an invalid %s\")", typeName) code.Print(" err := compiler.NewError(context, message)") code.Print(" errors = []error{err}") } 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) (*yaml.Node, 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() *yaml.Node {", typeName) typeModel := domain.TypeModels[typeName] if typeName == "Any" { code.Print("var err error") code.Print("var node yaml.Node") code.Print("err = yaml.Unmarshal([]byte(m.Yaml), &node)") code.Print("if err == nil {") code.Print(" if node.Kind == yaml.DocumentNode {") code.Print(" return node.Content[0]") code.Print(" }") code.Print(" return &node") code.Print("}") code.Print("return compiler.NewNullNode()") } else if typeName == "StringArray" { code.Print("return compiler.NewSequenceNodeForStringArray(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 compiler.NewScalarNodeForFloat(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 compiler.NewScalarNodeForBool(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 compiler.NewScalarNodeForString(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 compiler.NewNullNode()") } else { code.Print("info := compiler.NewMappingNode()") code.Print("if m == nil {return info}") for _, propertyModel := range typeModel.Properties { isRequired := typeModel.IsRequired(propertyModel.Name) switch propertyModel.Type { case "string": propertyName := propertyModel.Name if !propertyModel.Repeated { code.PrintIf(isRequired, "// always include this required field.") code.PrintIf(!isRequired, "if m.%s != \"\" {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(m.%s))", propertyModel.FieldName()) code.PrintIf(!isRequired, "}") } else { code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewSequenceNodeForStringArray(m.%s))", propertyModel.FieldName()) code.Print("}") } case "bool": propertyName := propertyModel.Name if !propertyModel.Repeated { code.PrintIf(isRequired, "// always include this required field.") code.PrintIf(!isRequired, "if m.%s != false {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForBool(m.%s))", propertyModel.FieldName()) code.PrintIf(!isRequired, "}") } else { code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewSequenceNodeForBoolArray(m.%s))", propertyModel.FieldName()) code.Print("}") } case "int": propertyName := propertyModel.Name if !propertyModel.Repeated { code.PrintIf(isRequired, "// always include this required field.") code.PrintIf(!isRequired, "if m.%s != 0 {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForInt(m.%s))", propertyModel.FieldName()) code.PrintIf(!isRequired, "}") } else { code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewSequenceNodeForIntArray(m.%s))", propertyModel.FieldName()) code.Print("}") } case "float": propertyName := propertyModel.Name if !propertyModel.Repeated { code.PrintIf(isRequired, "// always include this required field.") code.PrintIf(!isRequired, "if m.%s != 0.0 {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForFloat(m.%s))", propertyModel.FieldName()) code.PrintIf(!isRequired, "}") } else { code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, compiler.NewSequenceNodeForFloatArray(m.%s))", propertyModel.FieldName()) code.Print("}") } default: propertyName := propertyModel.Name if propertyName == "value" && propertyModel.Type != "Any" { code.Print("// %+v", propertyModel) } else if !propertyModel.Repeated { code.PrintIf(isRequired, "// always include this required field.") code.PrintIf(!isRequired, "if m.%s != nil {", propertyModel.FieldName()) if propertyModel.Type == "TypeItem" { code.Print("if len(m.Type.Value) == 1 {") code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"type\"))") code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(m.Type.Value[0]))") code.Print("} else {") code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"type\"))") code.Print("info.Content = append(info.Content, compiler.NewSequenceNodeForStringArray(m.Type.Value))") code.Print("}") } else if propertyModel.Type == "ItemsItem" { code.Print("items := compiler.NewSequenceNode()") if domain.Version == "v2" { code.Print("for _, item := range m.Items.Schema {") } else { code.Print("for _, item := range m.Items.SchemaOrReference {") } code.Print(" items.Content = append(items.Content, item.ToRawInfo())") code.Print("}") code.Print("if len(items.Content) == 1 {items = items.Content[0]}") code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"items\"))") code.Print("info.Content = append(info.Content, items)") } else { code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, m.%s.ToRawInfo())", propertyModel.FieldName()) } code.PrintIf(!isRequired, "}") } 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.Content = append(info.Content, compiler.NewScalarNodeForString(item.Name))") code.Print("info.Content = append(info.Content, item.Value.ToRawInfo())") code.Print("}") code.Print("}") } else { code.Print("if len(m.%s) != 0 {", propertyModel.FieldName()) code.Print("items := compiler.NewSequenceNode()") code.Print("for _, item := range m.%s {", propertyModel.FieldName()) code.Print("items.Content = append(items.Content, item.ToRawInfo())") code.Print("}") code.Print("info.Content = append(info.Content, compiler.NewScalarNodeForString(\"%s\"))", propertyName) code.Print("info.Content = append(info.Content, items)") code.Print("}") } } } code.Print("return info") } code.Print("}\n") } func (domain *Domain) generateConstantVariables(code *printer.Code, regexPatterns *patternNames) { names := regexPatterns.Names() if len(names) == 0 { return } 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") }