codegen/type_converter.go (1,051 lines of code) (raw):
// Copyright (c) 2023 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
package codegen
import (
"fmt"
"sort"
"strings"
"github.com/pkg/errors"
"go.uber.org/thriftrw/compile"
)
// LineBuilder struct just appends/builds lines.
type LineBuilder struct {
lines []string
}
// append helper will add a line to TypeConverter
func (l *LineBuilder) append(parts ...string) {
line := strings.Join(parts, "")
l.lines = append(l.lines, line)
}
// appendf helper will add a formatted line to TypeConverter
func (l *LineBuilder) appendf(format string, parts ...interface{}) {
line := fmt.Sprintf(format, parts...)
l.lines = append(l.lines, line)
}
// GetLines returns the lines in the line builder
func (l *LineBuilder) GetLines() []string {
return l.lines
}
type fieldStruct struct {
Identifier string
TypeName string
}
// PackageNameResolver interface allows for resolving what the
// package name for a thrift file is. This depends on where the
// thrift-based structs are generated.
type PackageNameResolver interface {
TypePackageName(thriftFile string) (string, error)
}
// TypeConverter can generate a function body that converts two thriftrw
// FieldGroups from one to another. It's assumed that the converted code
// operates on two variables, "in" and "out" and that both are a go struct.
type TypeConverter struct {
LineBuilder
Helper PackageNameResolver
uninitialized map[string]*fieldStruct
fieldCounter int
convStructMap map[string]string
useRecurGen bool
optionalEntries map[string]FieldMapperEntry
}
type toFieldParam struct {
Type compile.TypeSpec
Name string
Required bool
Identifier string
}
type fromFieldParam struct {
Type compile.TypeSpec
Name string
Identifier string
ValueIdentifier string
}
type overriddenFieldParam struct {
Type compile.TypeSpec
Name string
Identifier string
}
// NewTypeConverter returns *TypeConverter (tc)
// @optionalEntries contains Entries that already set for tc fromFields
// entry set in @optionalEntries, tc will not raise error when this entry
// is required for toFields but missing from fromFields
func NewTypeConverter(h PackageNameResolver, optionalEntries map[string]FieldMapperEntry) *TypeConverter {
return &TypeConverter{
LineBuilder: LineBuilder{},
Helper: h,
uninitialized: make(map[string]*fieldStruct),
convStructMap: make(map[string]string),
optionalEntries: optionalEntries,
}
}
func (c *TypeConverter) makeUniqIdentifier(prefix string) string {
c.fieldCounter++
return fmt.Sprintf("%s%d", prefix, c.fieldCounter)
}
func (c *TypeConverter) getGoTypeName(valueType compile.TypeSpec) (string, error) {
return GoType(c.Helper, valueType)
}
// input of "A.B.C.D" returns ["A","A.B", "A.B.C", "A.B.C.D"]
func getMiddleIdentifiers(identifier string) []string {
subIds := strings.Split(identifier, ".")
middleIds := make([]string, 0, len(subIds))
middleIds = append(middleIds, subIds[0])
for i := 1; i < len(subIds); i++ {
middleIds = append(middleIds, fmt.Sprintf("%s.%s", middleIds[i-1], subIds[i]))
}
return middleIds
}
// converts a list of identifier paths into boolean nil check expressions on those paths
func convertIdentifiersToNilChecks(identifiers []string) []string {
checks := make([]string, 0, len(identifiers)-1)
for i, ident := range identifiers {
if i == 0 {
continue // discard the check on "in" object
}
checks = append(checks, ident+" != nil")
}
return checks
}
func (c *TypeConverter) getIdentifierName(fieldType compile.TypeSpec) (string, error) {
t, err := GoCustomType(c.Helper, fieldType)
if err != nil {
return "", errors.Wrapf(
err,
"could not lookup fieldType when building converter for %s",
fieldType.ThriftName(),
)
}
return t, nil
}
func (c *TypeConverter) genConverterForStruct(
toFieldName string,
toFieldType *compile.StructSpec,
toRequired bool,
fromFieldType compile.TypeSpec,
fromIdentifier string,
keyPrefix string,
fromPrefix string,
indent string,
fieldMap map[string]FieldMapperEntry,
prevKeyPrefixes []string,
) error {
toIdentifier := "out"
if keyPrefix != "" {
toIdentifier += "." + keyPrefix
}
typeName, _ := c.getIdentifierName(toFieldType)
subToFields := toFieldType.Fields
// if no fromFieldType assume we're constructing from transform fieldMap TODO: make this less subtle
if fromFieldType == nil {
// in the direct assignment we do a nil check on fromField here. its hard for unknown number of transforms
// initialize the toField with an empty struct only if it's required, otherwise send to uninitialized map
if toRequired {
c.append(indent, toIdentifier, " = &", typeName, "{}")
} else {
id := "out"
if c.useRecurGen {
id = "outOriginal"
}
if keyPrefix != "" {
id += "." + keyPrefix
}
c.uninitialized[id] = &fieldStruct{
Identifier: id,
TypeName: typeName,
}
}
if keyPrefix != "" {
keyPrefix += "."
}
// if fromPrefix != "" {
// fromPrefix += "."
// }
// recursive call
err := c.genStructConverter(
keyPrefix,
fromPrefix,
indent,
nil,
subToFields,
fieldMap,
prevKeyPrefixes,
)
if err != nil {
return err
}
return nil
}
fromFieldStruct, ok := fromFieldType.(*compile.StructSpec)
if !ok {
return errors.Errorf(
"could not convert struct fields, "+
"incompatible type for %s :",
toFieldName,
)
}
c.append(indent, "if ", fromIdentifier, " != nil {")
c.append(indent, "\t", toIdentifier, " = &", typeName, "{}")
if keyPrefix != "" {
keyPrefix += "."
}
if fromPrefix != "" {
fromPrefix += "."
}
subFromFields := fromFieldStruct.Fields
err := c.genStructConverter(
keyPrefix,
fromPrefix,
indent+"\t",
subFromFields,
subToFields,
fieldMap,
prevKeyPrefixes,
)
if err != nil {
return err
}
c.append(indent, "} else {")
c.append(indent, "\t", toIdentifier, " = nil")
c.append(indent, "}")
return nil
}
func (c *TypeConverter) genConverterForList(
toField toFieldParam,
fromField fromFieldParam,
overriddenField overriddenFieldParam,
indent string,
) error {
toFieldType := toField.Type.(*compile.ListSpec)
typeName, err := c.getGoTypeName(toFieldType.ValueSpec)
if err != nil {
return err
}
valueStruct, isStruct := compile.RootTypeSpec(toFieldType.ValueSpec).(*compile.StructSpec)
valueList, isList := compile.RootTypeSpec(toFieldType.ValueSpec).(*compile.ListSpec)
valueMap, isMap := compile.RootTypeSpec(toFieldType.ValueSpec).(*compile.MapSpec)
sourceIdentifier := fromField.ValueIdentifier
checkOverride := false
sourceListID := ""
isOverriddenID := ""
if overriddenField.Identifier != "" {
sourceListID = c.makeUniqIdentifier("sourceList")
isOverriddenID = c.makeUniqIdentifier("isOverridden")
// Determine which map (from or overrride) to use
c.appendf("%s := %s", sourceListID, overriddenField.Identifier)
if isStruct {
c.appendf("%s := false", isOverriddenID)
}
// TODO(sindelar): Verify how optional thrift lists are defined.
c.appendf("if %s != nil {", fromField.Identifier)
c.appendf("\t%s = %s", sourceListID, fromField.Identifier)
if isStruct {
c.appendf("\t%s = true", isOverriddenID)
}
c.append("}")
sourceIdentifier = sourceListID
checkOverride = true
}
if isStruct {
c.appendf(
"%s = make([]*%s, len(%s))",
toField.Identifier, typeName, sourceIdentifier,
)
} else {
c.appendf(
"%s = make([]%s, len(%s))",
toField.Identifier, typeName, sourceIdentifier,
)
}
indexID := c.makeUniqIdentifier("index")
valID := c.makeUniqIdentifier("value")
c.appendf(
"for %s, %s := range %s {",
indexID, valID, sourceIdentifier,
)
if isStruct || isList || isMap {
nestedIndent := "\t" + indent
fromFieldListType, ok := compile.RootTypeSpec(fromField.Type).(*compile.ListSpec)
if !ok {
return errors.Errorf(
"Could not convert field (%s): type is not list",
fromField.Name,
)
}
if isStruct {
if checkOverride {
nestedIndent = "\t" + nestedIndent
c.appendf("\tif %s {", isOverriddenID)
}
err = c.genConverterForStruct(
toField.Name,
valueStruct,
toField.Required,
fromFieldListType.ValueSpec,
valID,
trimAnyPrefix(toField.Identifier, "out.", "outOriginal.")+"["+indexID+"]",
trimAnyPrefix(fromField.Identifier, "in.", "inOriginal.")+"["+indexID+"]",
nestedIndent,
nil,
nil,
)
if err != nil {
return err
}
if checkOverride {
c.append("\t", "} else {")
overriddenFieldListType, ok := overriddenField.Type.(*compile.ListSpec)
if !ok {
return errors.Errorf(
"Could not convert field (%s): type is not list",
overriddenField.Name,
)
}
err = c.genConverterForStruct(
toField.Name,
valueStruct,
toField.Required,
overriddenFieldListType.ValueSpec,
valID,
trimAnyPrefix(toField.Identifier, "out.", "outOriginal.")+"["+indexID+"]",
trimAnyPrefix(overriddenField.Identifier, "in.", "inOriginal.")+"["+indexID+"]",
nestedIndent,
nil,
nil,
)
if err != nil {
return err
}
c.append("\t", "}")
}
} else if isList {
err = c.genConverterForList(
toFieldParam{
valueList,
toField.Name,
toField.Required,
toField.Identifier + "[" + indexID + "]",
},
fromFieldParam{
fromFieldListType.ValueSpec,
fromField.Name,
fromField.Identifier + "[" + indexID + "]",
valID,
},
overriddenField,
nestedIndent)
if err != nil {
return err
}
} else if isMap {
err = c.genConverterForMap(
toFieldParam{
valueMap,
toField.Name,
toField.Required,
toField.Identifier + "[" + indexID + "]",
},
fromFieldParam{
fromFieldListType.ValueSpec,
fromField.Name,
fromField.Identifier + "[" + indexID + "]",
valID,
},
overriddenField,
nestedIndent)
if err != nil {
return err
}
}
} else {
c.appendf(
"\t%s[%s] = %s(%s)",
toField.Identifier, indexID, typeName, valID,
)
}
c.append("}")
return nil
}
func (c *TypeConverter) genConverterForMap(
toField toFieldParam,
fromField fromFieldParam,
overriddenField overriddenFieldParam,
indent string,
) error {
toFieldType := toField.Type.(*compile.MapSpec)
typeName, err := c.getGoTypeName(toFieldType.ValueSpec)
if err != nil {
return err
}
valueStruct, isStruct := compile.RootTypeSpec(toFieldType.ValueSpec).(*compile.StructSpec)
valueList, isList := compile.RootTypeSpec(toFieldType.ValueSpec).(*compile.ListSpec)
valueMap, isMap := compile.RootTypeSpec(toFieldType.ValueSpec).(*compile.MapSpec)
sourceIdentifier := fromField.ValueIdentifier
_, isStringKey := toFieldType.KeySpec.(*compile.StringSpec)
keyType := "string"
if !isStringKey {
realType := compile.RootTypeSpec(toFieldType.KeySpec)
switch realType.(type) {
case *compile.StringSpec:
keyType, _ = c.getGoTypeName(toFieldType.KeySpec)
default:
return errors.Errorf(
"could not convert key (%s), map is not string-keyed.", toField.Name)
}
}
checkOverride := false
sourceListID := ""
isOverriddenID := ""
if overriddenField.Identifier != "" {
sourceListID = c.makeUniqIdentifier("sourceList")
isOverriddenID = c.makeUniqIdentifier("isOverridden")
// Determine which map (from or overrride) to use
c.appendf("%s := %s", sourceListID, overriddenField.Identifier)
if isStruct {
c.appendf("%s := false", isOverriddenID)
}
// TODO(sindelar): Verify how optional thrift map are defined.
c.appendf("if %s != nil {", fromField.Identifier)
c.appendf("\t%s = %s", sourceListID, fromField.Identifier)
if isStruct {
c.appendf("\t%s = true", isOverriddenID)
}
c.append("}")
sourceIdentifier = sourceListID
checkOverride = true
}
if isStruct {
c.appendf(
"%s = make(map[%s]*%s, len(%s))",
toField.Identifier, keyType, typeName, sourceIdentifier,
)
} else {
c.appendf(
"%s = make(map[%s]%s, len(%s))",
toField.Identifier, keyType, typeName, sourceIdentifier,
)
}
keyID := c.makeUniqIdentifier("key")
valID := c.makeUniqIdentifier("value")
c.appendf(
"for %s, %s := range %s {",
keyID, valID, sourceIdentifier,
)
if isStruct || isList || isMap {
nestedIndent := "\t" + indent
fromFieldMapType, ok := compile.RootTypeSpec(fromField.Type).(*compile.MapSpec)
if !ok {
return errors.Errorf(
"Could not convert field (%s): type is not map",
fromField.Name,
)
}
toFieldKeyID := keyID
toTypeKeyName := keyType
fromTypeKeyName, _ := c.getGoTypeName(fromFieldMapType.KeySpec)
if fromTypeKeyName != toTypeKeyName {
toFieldKeyID = toTypeKeyName + "(" + keyID + ")"
}
if isStruct {
if checkOverride {
nestedIndent = "\t" + nestedIndent
c.appendf("\tif %s {", isOverriddenID)
}
err = c.genConverterForStruct(
toField.Name,
valueStruct,
toField.Required,
fromFieldMapType.ValueSpec,
valID,
trimAnyPrefix(toField.Identifier, "out.", "outOriginal.")+"["+toFieldKeyID+"]",
trimAnyPrefix(fromField.Identifier, "in.", "inOriginal.")+"["+keyID+"]",
nestedIndent,
nil,
nil,
)
if err != nil {
return err
}
if checkOverride {
c.append("\t", "} else {")
overriddenFieldMapType, ok := overriddenField.Type.(*compile.MapSpec)
if !ok {
return errors.Errorf(
"Could not convert field (%s): type is not map",
overriddenField.Name,
)
}
err = c.genConverterForStruct(
toField.Name,
valueStruct,
toField.Required,
overriddenFieldMapType.ValueSpec,
valID,
trimAnyPrefix(toField.Identifier, "out.", "outOriginal.")+"["+toFieldKeyID+"]",
trimAnyPrefix(overriddenField.Identifier, "in.", "inOriginal.")+"["+keyID+"]",
nestedIndent,
nil,
nil,
)
if err != nil {
return err
}
c.append("\t", "}")
}
} else if isList {
err = c.genConverterForList(
toFieldParam{
valueList,
toField.Name,
toField.Required,
toField.Identifier + "[" + toFieldKeyID + "]",
},
fromFieldParam{
fromFieldMapType.ValueSpec,
fromField.Name,
fromField.Identifier + "[" + keyID + "]",
valID,
},
overriddenField,
nestedIndent)
if err != nil {
return err
}
} else if isMap {
err = c.genConverterForMap(
toFieldParam{
valueMap,
toField.Name,
toField.Required,
toField.Identifier + "[" + toFieldKeyID + "]",
},
fromFieldParam{
fromFieldMapType.ValueSpec,
fromField.Name,
fromField.Identifier + "[" + keyID + "]",
valID,
},
overriddenField,
nestedIndent)
if err != nil {
return err
}
}
} else {
if keyType == "string" {
c.appendf(
"\t%s[%s] = %s(%s)",
toField.Identifier, keyID, typeName, valID,
)
} else {
c.appendf(
"\t %s[%s(%s)] = %s(%s)",
toField.Identifier, keyType, keyID, typeName, valID,
)
}
}
c.append("}")
return nil
}
// recursive function to walk a DFS on toFields and try to assign fromFields or fieldMap tranforms
// generated code is appended as we traverse the toFields thrift type structure
// keyPrefix - the identifier (path) of the current position in the "to" struct
// fromPrefix - the identifier (path) of the corresponding position in the "from" struct
// indent - a string of tabs for current block scope
// fromFields - fields in the current from struct, can be nil if only fieldMap transforms are applicable in the path
// toFields - fields in the current to struct
// fieldMap - a data structure specifying configured transforms Map[toIdentifier ] -> fromField FieldMapperEntry
func (c *TypeConverter) genStructConverter(
keyPrefix string,
fromPrefix string,
indent string,
fromFields []*compile.FieldSpec,
toFields []*compile.FieldSpec,
fieldMap map[string]FieldMapperEntry,
prevKeyPrefixes []string,
) error {
for i := 0; i < len(toFields); i++ {
toField := toFields[i]
// Check for same named field
var fromField *compile.FieldSpec
for j := 0; j < len(fromFields); j++ {
if fromFields[j].Name == toField.Name {
fromField = fromFields[j]
break
}
}
toSubIdentifier := keyPrefix + PascalCase(toField.Name)
toIdentifier := "out." + toSubIdentifier
overriddenIdentifier := ""
fromIdentifier := ""
// Check for mapped field
var overriddenField *compile.FieldSpec
// check if this toField satisfies a fieldMap transform
transformFrom, ok := fieldMap[toSubIdentifier]
if ok {
// no existing direct fromField, just assign the transform
if fromField == nil {
fromField = transformFrom.Field
if c.useRecurGen {
fromIdentifier = "inOriginal." + transformFrom.QualifiedName
} else {
fromIdentifier = "in." + transformFrom.QualifiedName
}
// else there is a conflicting direct fromField
} else {
// depending on Override flag either the direct fromField or transformFrom is the OverrideField
if transformFrom.Override {
// check for required/optional setting
if !transformFrom.Field.Required {
overriddenField = fromField
overriddenIdentifier = "in." + fromPrefix +
PascalCase(overriddenField.Name)
}
// If override is true and the new field is required,
// there's a default instantiation value and will always
// overwrite.
fromField = transformFrom.Field
if c.useRecurGen {
fromIdentifier = "inOriginal." + transformFrom.QualifiedName
} else {
fromIdentifier = "in." + transformFrom.QualifiedName
}
} else {
// If override is false and the from field is required,
// From is always populated and will never be overwritten.
if !fromField.Required {
overriddenField = transformFrom.Field
if c.useRecurGen {
fromIdentifier = "inOriginal." + transformFrom.QualifiedName
} else {
overriddenIdentifier = "in." + transformFrom.QualifiedName
}
}
}
}
}
// neither direct or transform fromField was found
if fromField == nil {
// search the fieldMap toField identifiers for matching identifier prefix
// e.g. the current toField is a struct and something within it has a transform
// a full match identifiers for transform non-struct types would have been caught above
hasStructFieldMapping := false
for toID := range fieldMap {
if strings.HasPrefix(toID, toSubIdentifier) {
hasStructFieldMapping = true
}
}
// if there's no fromField and no fieldMap transform that could be applied
if !hasStructFieldMapping {
var bypass bool
// check if required field is filled from other resources
// it can be used to set system default (customized tracing /auth required for clients),
// or header propagating
if c.optionalEntries != nil {
for toID := range c.optionalEntries {
if strings.HasPrefix(toID, toSubIdentifier) {
bypass = true
break
}
}
}
// the toField is either covered by optionalEntries, or optional and
// there's nothing that maps to it or its sub-fields so we should skip it
if bypass || !toField.Required {
continue
}
// unrecoverable error
return errors.Errorf(
"required toField %s does not have a valid fromField mapping",
toField.Name,
)
}
}
if fromIdentifier == "" && fromField != nil {
// should we set this if no fromField ??
fromIdentifier = "in." + fromPrefix + PascalCase(fromField.Name)
}
if prevKeyPrefixes == nil {
prevKeyPrefixes = []string{}
}
var overriddenFieldName string
var overriddenFieldType compile.TypeSpec
if overriddenField != nil {
overriddenFieldName = overriddenField.Name
overriddenFieldType = overriddenField.Type
}
// Override thrift type names to avoid naming collisions between endpoint
// and client types.
switch toFieldType := compile.RootTypeSpec(toField.Type).(type) {
case
*compile.BoolSpec,
*compile.I8Spec,
*compile.I16Spec,
*compile.I32Spec,
*compile.EnumSpec,
*compile.I64Spec,
*compile.DoubleSpec,
*compile.StringSpec:
err := c.genConverterForPrimitive(
toField,
toIdentifier,
fromField,
fromIdentifier,
overriddenField,
overriddenIdentifier,
indent,
prevKeyPrefixes,
)
if err != nil {
return err
}
case *compile.BinarySpec:
for _, line := range checkOptionalNil(indent, c.uninitialized, toIdentifier, prevKeyPrefixes, c.useRecurGen) {
c.append(line)
}
c.append(toIdentifier, " = []byte(", fromIdentifier, ")")
case *compile.StructSpec:
var (
stFromPrefix = fromPrefix
stFromType compile.TypeSpec
fromTypeName string
)
if fromField != nil {
stFromType = fromField.Type
stFromPrefix = fromPrefix + PascalCase(fromField.Name)
fromTypeName, _ = c.getIdentifierName(stFromType)
}
toTypeName, err := c.getIdentifierName(toFieldType)
if err != nil {
return err
}
if converterMethodName, ok := c.convStructMap[toFieldType.Name]; ok {
// the converter for this struct has already been generated, so just use it
c.append(indent, "out.", keyPrefix+PascalCase(toField.Name), " = ", converterMethodName, "(", fromIdentifier, ")")
} else if c.useRecurGen && fromTypeName != "" {
// generate a callable converter inside function literal
err = c.genConverterForStructWrapped(
toField,
toFieldType,
toTypeName,
toSubIdentifier,
fromTypeName,
fromIdentifier,
stFromType,
fieldMap,
prevKeyPrefixes,
indent,
)
} else {
err = c.genConverterForStruct(
toField.Name,
toFieldType,
toField.Required,
stFromType,
fromIdentifier,
keyPrefix+PascalCase(toField.Name),
stFromPrefix,
indent,
fieldMap,
prevKeyPrefixes,
)
}
if err != nil {
return err
}
case *compile.ListSpec:
err := c.genConverterForList(
toFieldParam{
toFieldType,
toField.Name,
toField.Required,
toIdentifier,
},
fromFieldParam{
fromField.Type,
fromField.Name,
fromIdentifier,
fromIdentifier,
},
overriddenFieldParam{
overriddenFieldType,
overriddenFieldName,
overriddenIdentifier,
},
indent,
)
if err != nil {
return err
}
case *compile.MapSpec:
err := c.genConverterForMap(
toFieldParam{
toFieldType,
toField.Name,
toField.Required,
toIdentifier,
},
fromFieldParam{
fromField.Type,
fromField.Name,
fromIdentifier,
fromIdentifier,
},
overriddenFieldParam{
overriddenFieldType,
overriddenFieldName,
overriddenIdentifier,
},
indent,
)
if err != nil {
return err
}
default:
// fmt.Printf("Unknown type %s for field %s \n",
// toField.Type.TypeCode().String(), toField.Name,
// )
// pkgName, err := h.TypePackageName(toField.Type.IDLFile())
// if err != nil {
// return nil, err
// }
// typeName := pkgName + "." + toField.Type.ThriftName()
// line := toIdentifier + "(*" + typeName + ")" + postfix
// c.Lines = append(c.Lines, line)
}
}
return nil
}
// GenStructConverter will add lines to the TypeConverter for mapping
// from one go struct to another based on two thriftrw.FieldGroups.
// fieldMap is a may from keys that are the qualified field path names for
// destination fields (sent to the downstream client) and the entries are source
// fields (from the incoming request)
func (c *TypeConverter) GenStructConverter(
fromFields []*compile.FieldSpec,
toFields []*compile.FieldSpec,
fieldMap map[string]FieldMapperEntry,
) error {
// Add compiled FieldSpecs to the FieldMapperEntry
fieldMap = addSpecToMap(fieldMap, fromFields, "")
// Check for vlaues not populated recursively by addSpecToMap
for k, v := range fieldMap {
if fieldMap[k].Field == nil {
return errors.Errorf(
"Failed to find field ( %s ) for transform.",
v.QualifiedName,
)
}
}
c.useRecurGen = c.isRecursiveStruct(toFields) || c.isRecursiveStruct(fromFields)
if c.useRecurGen && len(fieldMap) != 0 {
c.append("inOriginal := in; _ = inOriginal")
c.append("outOriginal := out; _ = outOriginal")
}
err := c.genStructConverter("", "", "", fromFields, toFields, fieldMap, nil)
if err != nil {
return err
}
return nil
}
// FieldMapperEntry defines a source field and optional arguments
// converting and overriding fields.
type FieldMapperEntry struct {
QualifiedName string //
Field *compile.FieldSpec
Override bool
typeConverter string // TODO: implement. i.e string(int) etc
transform string // TODO: implement. i.e. camelCasing, Title, etc
}
func addSpecToMap(
overrideMap map[string]FieldMapperEntry,
fields compile.FieldGroup,
prefix string,
) map[string]FieldMapperEntry {
for k, v := range overrideMap {
for _, spec := range fields {
fieldQualName := prefix + PascalCase(spec.Name)
if v.QualifiedName == fieldQualName {
v.Field = spec
overrideMap[k] = v
break
} else if strings.HasPrefix(v.QualifiedName, fieldQualName) {
structSpec := spec.Type.(*compile.StructSpec)
overrideMap = addSpecToMap(overrideMap, structSpec.Fields, fieldQualName+".")
}
}
}
return overrideMap
}
// FieldAssignment is responsible to generate an assignment
type FieldAssignment struct {
From *compile.FieldSpec
FromIdentifier string
To *compile.FieldSpec
ToIdentifier string
TypeCast string
ExtraNilCheck bool
}
// IsTransform returns true if field assignment is not proxy
func (f *FieldAssignment) IsTransform() bool {
fromI := strings.Index(f.FromIdentifier, ".") + 1
toI := strings.Index(f.ToIdentifier, ".") + 1
// make sure FromIdentifier and ToIdentifier have valid prefixes
if (f.FromIdentifier[:fromI] != "in." && f.FromIdentifier[:fromI] != "inOriginal.") || f.ToIdentifier[:toI] != "out." {
panic("isTransform check called on unexpected input fromIdentifer " + f.FromIdentifier + " toIdentifer " + f.ToIdentifier)
}
// it's a transform when FromIdentifier != ToIdentifier (after removing prefix)
return f.FromIdentifier[fromI:] != f.ToIdentifier[toI:]
}
// checkOptionalNil nil-checks fields that may not be initialized along
// the assign path
func checkOptionalNil(
indent string,
uninitialized map[string]*fieldStruct,
toIdentifier string,
prevKeyPrefixes []string,
useRecurGen bool,
) []string {
var ret = make([]string, 0)
if len(uninitialized) < 1 {
return ret
}
keys := make([]string, 0, len(uninitialized))
for k := range uninitialized {
keys = append(keys, k)
}
toIdentifier = trimAnyPrefix(toIdentifier, "out.", "outOriginal.")
completeID := "out."
if useRecurGen {
completeID = "outOriginal."
}
completeID += strings.Join(append(prevKeyPrefixes, toIdentifier), ".")
sort.Strings(keys)
for _, id := range keys {
if strings.HasPrefix(completeID, id) {
v := uninitialized[id]
ret = append(ret, indent+"if "+v.Identifier+" == nil {")
ret = append(ret, indent+"\t"+v.Identifier+" = &"+v.TypeName+"{}")
ret = append(ret, indent+"}")
}
}
return ret
}
// dealing with primary type pointer formatting: (*float64)&Param -> (*float64)(&(Param))
func formatAssign(indent, to, typeCast, from string) string {
if string(typeCast[len(typeCast)-1]) == "&" {
typeCast = typeCast[:len(typeCast)-1]
return fmt.Sprintf("%s%s = %s(&(%s))", indent, to, typeCast, from)
}
return fmt.Sprintf("%s%s = %s(%s)", indent, to, typeCast, from)
}
// Generate generates assignment
func (f *FieldAssignment) Generate(indent string, uninitialized map[string]*fieldStruct, prevKeyPrefixes []string, useRecurGen bool) string {
var lines = make([]string, 0)
if !f.IsTransform() {
// do an extra nil check for Overrides
if (!f.From.Required && f.To.Required) || f.ExtraNilCheck {
// need to nil check otherwise we could be cast or deref nil
lines = append(lines, indent+"if "+f.FromIdentifier+" != nil {")
lines = append(lines, checkOptionalNil(indent+"\t", uninitialized, f.ToIdentifier, prevKeyPrefixes, useRecurGen)...)
lines = append(lines, formatAssign(indent+"\t", f.ToIdentifier, f.TypeCast, f.FromIdentifier))
lines = append(lines, indent+"}")
} else {
lines = append(lines, checkOptionalNil(indent, uninitialized, f.ToIdentifier, prevKeyPrefixes, useRecurGen)...)
lines = append(lines, formatAssign(indent, f.ToIdentifier, f.TypeCast, f.FromIdentifier))
}
return strings.Join(lines, "\n")
}
// generates nil checks for intermediate objects on the f.FromIdentifier path
checks := convertIdentifiersToNilChecks(getMiddleIdentifiers(f.FromIdentifier))
if !f.ExtraNilCheck {
// if from is required or from/to are both optional then we don't need the final outer check
if f.From.Required || !f.From.Required && !f.To.Required {
checks = checks[:len(checks)-1]
}
if len(checks) == 0 {
lines = append(lines, checkOptionalNil(indent, uninitialized, f.ToIdentifier, prevKeyPrefixes, useRecurGen)...)
lines = append(lines, formatAssign(indent, f.ToIdentifier, f.TypeCast, f.FromIdentifier))
return strings.Join(lines, "\n")
}
}
lines = append(lines, indent+"if "+strings.Join(checks, " && ")+" {")
lines = append(lines, checkOptionalNil(indent+"\t", uninitialized, f.ToIdentifier, prevKeyPrefixes, useRecurGen)...)
lines = append(lines, formatAssign(indent+"\t", f.ToIdentifier, f.TypeCast, f.FromIdentifier))
lines = append(lines, indent+"}")
// TODO else? log? should this silently eat intermediate nils as none-assignment, should set nil?
return strings.Join(lines, "\n")
}
func (c *TypeConverter) assignWithOverride(
indent string,
defaultAssign *FieldAssignment,
overrideAssign *FieldAssignment,
prevKeyPrefixes []string,
) {
if overrideAssign != nil && overrideAssign.FromIdentifier != "" {
c.append(overrideAssign.Generate(indent, c.uninitialized, prevKeyPrefixes, c.useRecurGen))
defaultAssign.ExtraNilCheck = true
c.append(defaultAssign.Generate(indent, c.uninitialized, prevKeyPrefixes, c.useRecurGen))
return
}
c.append(defaultAssign.Generate(indent, c.uninitialized, prevKeyPrefixes, c.useRecurGen))
}
func (c *TypeConverter) genConverterForPrimitive(
toField *compile.FieldSpec,
toIdentifier string,
fromField *compile.FieldSpec,
fromIdentifier string,
overriddenField *compile.FieldSpec,
overriddenIdentifier string,
indent string,
prevKeyPrefixes []string,
) error {
var (
typeName string
toTypeCast string
overrideTypeCast string
defaultAssignment *FieldAssignment
overrideAssignment *FieldAssignment
err error
)
// resolve type
if _, ok := toField.Type.(*compile.TypedefSpec); ok {
typeName, err = c.getIdentifierName(toField.Type)
} else {
typeName, err = c.getGoTypeName(toField.Type)
}
if err != nil {
return err
}
// set assignment
if toField.Required {
toTypeCast = typeName
overrideTypeCast = typeName
if !fromField.Required {
toTypeCast = "*"
}
if overriddenField != nil {
if !overriddenField.Required {
overrideTypeCast = "*"
} else {
overriddenField.Required = true
}
}
} else {
toTypeCast = fmt.Sprintf("(*%s)", typeName)
overrideTypeCast = fmt.Sprintf("(*%s)", typeName)
if fromField.Required {
toTypeCast = fmt.Sprintf("(*%s)&", typeName)
}
if overriddenField != nil && overriddenField.Required {
overrideTypeCast = fmt.Sprintf("(*%s)&", typeName)
overriddenField.Required = true
}
}
defaultAssignment = &FieldAssignment{
From: fromField,
FromIdentifier: fromIdentifier,
To: toField,
ToIdentifier: toIdentifier,
TypeCast: toTypeCast,
}
if overriddenField != nil {
overrideAssignment = &FieldAssignment{
From: overriddenField,
FromIdentifier: overriddenIdentifier,
To: toField,
ToIdentifier: toIdentifier,
TypeCast: overrideTypeCast,
}
}
// generate assignment
c.assignWithOverride(indent, defaultAssignment, overrideAssignment, prevKeyPrefixes)
return nil
}
func (c *TypeConverter) updateFieldMap(currentMap map[string]FieldMapperEntry, toPrefix string) map[string]FieldMapperEntry {
newMap := make(map[string]FieldMapperEntry)
for key, value := range currentMap {
if strings.HasPrefix(key, toPrefix) && len(key) > len(toPrefix) {
newMap[key[len(toPrefix)+1:]] = value
}
}
return newMap
}
// Generate a wrapper for genConverterForStruct so its generated code can be re-used later
func (c *TypeConverter) genConverterForStructWrapped(
toField *compile.FieldSpec,
toFieldType *compile.StructSpec,
toTypeName string,
toSubIdentifier string,
fromTypeName string,
fromIdentifier string,
stFromType compile.TypeSpec,
fieldMap map[string]FieldMapperEntry,
prevKeyPrefixes []string,
indent string,
) error {
converterMethodName := c.makeUniqIdentifier("convert" + toFieldType.Name + "Helper")
c.convStructMap[toFieldType.Name] = converterMethodName
funcSignature := "func(in *" + fromTypeName + ") (out *" + toTypeName + ")"
c.append(indent, "var ", converterMethodName, " ", funcSignature) // function declaration
c.append(indent, converterMethodName, " = ", funcSignature, " {") // begin function definition
err := c.genConverterForStruct(
toField.Name,
toFieldType,
toField.Required,
stFromType,
"in",
"",
"",
indent+"\t",
c.updateFieldMap(fieldMap, toSubIdentifier),
append(prevKeyPrefixes, toSubIdentifier),
)
if err != nil {
return err
}
c.append(indent+"\t", "return")
c.append(indent, "}") // end of function definition
c.append(indent, "out.", toSubIdentifier, " = ", converterMethodName, "(", fromIdentifier, ")")
delete(c.convStructMap, toFieldType.Name)
return nil
}
// Helper function to detect a recursive struct, looking at its fields, fields of its fields, etc. to see if
// the same type is encountered more than once down a path
func isRecursiveStruct(spec compile.TypeSpec, seenSoFar map[string]bool) bool {
switch t := spec.(type) {
case *compile.StructSpec:
// detected cycle; second time seeing this type
if _, found := seenSoFar[t.Name]; found {
return true
}
// mark this type as seen
seenSoFar[t.Name] = true
// search all fields of this struct
for _, field := range t.Fields {
if isRecursiveStruct(field.Type, seenSoFar) {
return true
}
}
// unmark
delete(seenSoFar, t.Name)
// for lists and maps, check element/key types the same way
case *compile.MapSpec:
if isRecursiveStruct(t.KeySpec, seenSoFar) || isRecursiveStruct(t.ValueSpec, seenSoFar) {
return true
}
case *compile.ListSpec:
if isRecursiveStruct(t.ValueSpec, seenSoFar) {
return true
}
}
return false
}
// Returns true if any of the fields of a struct form a cycle anywhere down the line
// e.g. struct A has optional field of type A -> cycle of length 0
// struct A has optional field of type B; struct B has optional field of type A -> cycle of length 2
// struct A has optional field of type B; struct B has optional field of type B -> cycle of length 0 downstream
func (c *TypeConverter) isRecursiveStruct(fields []*compile.FieldSpec) bool {
for _, field := range fields {
if isRecursiveStruct(field.Type, make(map[string]bool)) {
return true
}
}
return false
}
func trimAnyPrefix(str string, prefixes ...string) string {
for _, p := range prefixes {
if strings.HasPrefix(str, p) {
str = strings.TrimPrefix(str, p)
break
}
}
return str
}