Skip to content

Commit

Permalink
Merge pull request #56 from jmattheis/struct-pointer
Browse files Browse the repository at this point in the history
Struct pointer
  • Loading branch information
jmattheis authored Feb 11, 2023
2 parents d4c79e6 + 69ccaec commit 7752ba3
Show file tree
Hide file tree
Showing 25 changed files with 441 additions and 161 deletions.
8 changes: 6 additions & 2 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,19 @@ type Generator interface {
type MethodContext struct {
*namer.Namer
Fields map[string]*FieldMapping
FieldsTarget string
IgnoreUnexportedFields bool
Signature xtype.Signature
TargetType *xtype.Type
PointerChange bool
MatchIgnoreCase bool
WrapErrors bool
}

func (ctx *MethodContext) Field(name string) *FieldMapping {
func (ctx *MethodContext) Field(target *xtype.Type, name string) *FieldMapping {
if ctx.FieldsTarget != target.T.String() {
return emptyMapping
}

prop, ok := ctx.Fields[name]
if !ok {
return emptyMapping
Expand Down
9 changes: 4 additions & 5 deletions builder/pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ func (*Pointer) Matches(source, target *xtype.Type) bool {

// Build creates conversion source code for the given source and target type.
func (*Pointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type) ([]jen.Code, *xtype.JenID, *Error) {
ctx.PointerChange = true

outerVar := ctx.Name(target.ID())
ifBlock := []jen.Code{}

valueSourceID := jen.Op("*").Add(sourceID.Code.Clone())
if !source.PointerInner.Basic {
valueSourceID = jen.Parens(valueSourceID)
}

innerID := xtype.OtherID(valueSourceID)
innerID.ParentPointer = sourceID
nextBlock, id, err := gen.Build(
ctx, xtype.OtherID(valueSourceID), source.PointerInner, target.PointerInner, NoWrap)
ctx, innerID, source.PointerInner, target.PointerInner, NoWrap)
if err != nil {
return nil, nil, err.Lift(&Path{
SourceID: "*",
Expand Down Expand Up @@ -60,8 +61,6 @@ func (*TargetPointer) Matches(source, target *xtype.Type) bool {

// Build creates conversion source code for the given source and target type.
func (*TargetPointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type) ([]jen.Code, *xtype.JenID, *Error) {
ctx.PointerChange = true

stmt, id, err := gen.Build(ctx, sourceID, source, target.PointerInner, NoWrap)
if err != nil {
return nil, nil, err.Lift(&Path{
Expand Down
25 changes: 14 additions & 11 deletions builder/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s
for i := 0; i < target.StructType.NumFields(); i++ {
targetField := target.StructType.Field(i)

fieldMapping := ctx.Field(targetField.Name())
fieldMapping := ctx.Field(target, targetField.Name())

if fieldMapping.Ignore {
continue
Expand Down Expand Up @@ -68,10 +68,6 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s
stmt = append(stmt, jen.Id(name).Dot(targetField.Name()).Op("=").Add(fieldID.Code))
} else {
def := fieldMapping.Function
params := []jen.Code{}
if def.SelfAsFirstParam {
params = append(params, jen.Id(xtype.ThisVar))
}

sourceLift := []*Path{}
var functionCallSourceID *xtype.JenID
Expand All @@ -84,7 +80,15 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s
sourceLift = mapLift
stmt = append(stmt, mapStmt...)

if def.Source.T.String() != nextSource.T.String() {
switch {
case def.Source.T.String() == nextSource.T.String():
functionCallSourceID = xtype.VariableID(nextID.Clone())
functionCallSourceType = nextSource
case fieldMapping.Source == "." && sourceID.ParentPointer != nil &&
def.Source.T.String() == source.AsPointer().T.String():
functionCallSourceID = sourceID.ParentPointer
functionCallSourceType = source.AsPointer()
default:
cause := fmt.Sprintf("cannot not use\n\t%s\nbecause source type mismatch\n\nExtend method param type: %s\nConverter source type: %s", def.ID, def.Source.T.String(), nextSource.T.String())
return nil, nil, NewError(cause).Lift(&Path{
Prefix: "(",
Expand All @@ -96,8 +100,6 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s
SourceType: def.ID,
}).Lift(sourceLift...)
}
functionCallSourceID = xtype.VariableID(nextID.Clone())
functionCallSourceType = nextSource
}

if def.Target.T.String() != targetFieldType.T.String() {
Expand Down Expand Up @@ -129,14 +131,15 @@ func (*Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, s
func mapField(gen Generator, ctx *MethodContext, targetField *types.Var, sourceID *xtype.JenID, source, target *xtype.Type) (*jen.Statement, *xtype.Type, []jen.Code, []*Path, *Error) {
lift := []*Path{}
ignored := func(name string) bool {
return ctx.Field(name).Ignore
return ctx.Field(target, name).Ignore
}

def := ctx.Field(targetField.Name())
def := ctx.Field(target, targetField.Name())
mappedName := def.Source

hasOverride := mappedName != ""
if ctx.Signature.Target != target.T.String() || !hasOverride {

if !hasOverride {
sourceMatch, err := source.StructField(targetField.Name(), ctx.MatchIgnoreCase, ignored)
if err == nil {
nextID := sourceID.Code.Clone().Dot(sourceMatch.Name)
Expand Down
4 changes: 4 additions & 0 deletions generator/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ func Generate(pattern string, mapping []comments.Converter, config Config) (*jen
}
}

if err := gen.validateMethods(); err != nil {
return nil, err
}

if err := gen.createMethods(); err != nil {
return nil, err
}
Expand Down
83 changes: 61 additions & 22 deletions generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,26 @@ func (g *generator) registerMethod(converter types.Type, scope *types.Scope, met
ReturnTypeOrigin: methodType.FullName(),
}

g.lookup[xtype.Signature{
Source: source.String(),
Target: target.String(),
}] = m
g.lookup[xtype.SignatureOf(m.Source, m.Target)] = m
g.namer.Register(m.Name)
return nil
}

func (g *generator) validateMethods() error {
for _, method := range g.lookup {
if method.Explicit && len(method.Fields) > 0 {
isTargetStructPointer := method.Target.Pointer && method.Target.PointerInner.Struct
if !method.Target.Struct && !isTargetStructPointer {
return fmt.Errorf("Invalid struct field mapping on method:\n %s\n\nField mappings like goverter:map or goverter:ignore may only be set on struct or struct pointers.\nSee https://goverter.jmattheis.de/#/conversion/configure-nested", method.ID)
}
if overlapping, ok := g.findOverlappingExplicitStructMethod(method); ok {
return fmt.Errorf("Overlapping field mapping found.\n\nPlease move the field related goverter:* comments from this method:\n %s\n\nto this method:\n %s\n\nGoverter will use %s inside the implementation of %s, thus, field related settings would be ignored.", method.ID, overlapping.ID, overlapping.Name, method.Name)
}
}
}
return nil
}

func (g *generator) createMethods() error {
methods := []*methodDefinition{}
for _, method := range g.lookup {
Expand Down Expand Up @@ -159,6 +171,29 @@ func (g *generator) appendToFile() {
}
}

func (g *generator) findOverlappingExplicitStructMethod(method *methodDefinition) (*methodDefinition, bool) {
source := method.Source
target := method.Target

switch {
case source.Struct && target.Pointer && target.PointerInner.Struct:
method, ok := g.lookup[xtype.SignatureOf(source, target.PointerInner)]
if ok && method.Explicit {
return method, true
}
case source.Pointer && source.PointerInner.Struct && target.Pointer && target.PointerInner.Struct:
method, ok := g.lookup[xtype.SignatureOf(source.PointerInner, target)]
if ok && method.Explicit {
return method, true
}
method, ok = g.lookup[xtype.SignatureOf(source.PointerInner, target.PointerInner)]
if ok && method.Explicit {
return method, true
}
}
return nil, false
}

func (g *generator) buildMethod(method *methodDefinition, errWrapper builder.ErrorWrapper) *builder.Error {
sourceID := jen.Id("source")
source := method.Source
Expand All @@ -169,21 +204,20 @@ func (g *generator) buildMethod(method *methodDefinition, errWrapper builder.Err
returns = append(returns, jen.Id("error"))
}

fieldsTarget := method.Target.T.String()
if method.Target.Pointer && method.Target.PointerInner.Struct {
fieldsTarget = method.Target.PointerInner.T.String()
}

ctx := &builder.MethodContext{
Namer: namer.New(),
Fields: method.Fields,
FieldsTarget: fieldsTarget,
IgnoreUnexportedFields: method.IgnoreUnexportedFields,
MatchIgnoreCase: method.MatchIgnoreCase,
WrapErrors: method.WrapErrors,
TargetType: method.Target,
Signature: xtype.Signature{Source: method.Source.T.String(), Target: method.Target.T.String()},
}

if method.Explicit && len(method.Fields) > 0 {
isStructPointer := method.Target.Pointer && method.Target.PointerInner.Struct
if !method.Target.Struct && !isStructPointer {
return builder.NewError("Invalid struct field mapping. Field mappings like goverter:map or goverter:ignore may only be set on struct or struct pointers.\nSee https://goverter.jmattheis.de/#/conversion/configure-nested")
}
Signature: xtype.SignatureOf(method.Source, method.Target),
}

var stmt []jen.Code
Expand Down Expand Up @@ -318,16 +352,27 @@ func (g *generator) Build(
source, target *xtype.Type,
errWrapper builder.ErrorWrapper,
) ([]jen.Code, *xtype.JenID, *builder.Error) {
method, ok := g.extend[xtype.Signature{Source: source.T.String(), Target: target.T.String()}]
signature := xtype.SignatureOf(source, target)
method, ok := g.extend[signature]
if !ok {
method, ok = g.lookup[xtype.Signature{Source: source.T.String(), Target: target.T.String()}]
method, ok = g.lookup[signature]
}

if ok {
return g.callByDefinition(ctx, method, sourceID, source, target, errWrapper)
}

if (source.Named && !source.Basic) || (target.Named && !target.Basic) {
hasPointerStructMethod := false
if source.Struct && target.Struct {
pointerSignature := xtype.SignatureOf(source.AsPointer(), target.AsPointer())

_, hasPointerStructMethod = g.extend[pointerSignature]
if !hasPointerStructMethod {
_, hasPointerStructMethod = g.lookup[pointerSignature]
}
}

if !hasPointerStructMethod && ((source.Named && !source.Basic) || (target.Named && !target.Basic) || (source.Pointer && source.PointerInner.Named && !source.PointerInner.Basic)) {
name := g.namer.Name(source.UnescapedID() + "To" + strings.Title(target.UnescapedID()))

method := &methodDefinition{
Expand All @@ -339,14 +384,8 @@ func (g *generator) Build(
IgnoreUnexportedFields: g.ignoreUnexportedFields,
Call: jen.Id(xtype.ThisVar).Dot(name),
}
if ctx.PointerChange {
ctx.PointerChange = false
method.Fields = ctx.Fields
method.MatchIgnoreCase = ctx.MatchIgnoreCase
method.WrapErrors = ctx.WrapErrors
}

g.lookup[xtype.Signature{Source: source.T.String(), Target: target.T.String()}] = method
g.lookup[signature] = method
g.namer.Register(method.Name)
if err := g.buildMethod(method, errWrapper); err != nil {
return nil, nil, err
Expand Down
8 changes: 2 additions & 6 deletions scenario/map_custom_self.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ success: |
func (c *ConverterImpl) Convert(source *execution.Input) *execution.Output {
var pStructsOutput *execution.Output
if source != nil {
structsOutput := c.structsInputToStructsOutput((*source))
var structsOutput execution.Output
structsOutput.Animals = execution.Combine(c, (*source))
pStructsOutput = &structsOutput
}
return pStructsOutput
Expand Down Expand Up @@ -74,8 +75,3 @@ success: |
structsAnimal.Name = source.Name
return structsAnimal
}
func (c *ConverterImpl) structsInputToStructsOutput(source execution.Input) execution.Output {
var structsOutput execution.Output
structsOutput.Animals = execution.Combine(c, source)
return structsOutput
}
10 changes: 3 additions & 7 deletions scenario/map_custom_struct_pointer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,10 @@ success: |
func (c *ConverterImpl) Convert(source *execution.Input) *execution.Output {
var pStructsOutput *execution.Output
if source != nil {
structsOutput := c.structsInputToStructsOutput((*source))
var structsOutput execution.Output
structsOutput.Name = (*source).Name
structsOutput.Age = execution.DefaultAge()
pStructsOutput = &structsOutput
}
return pStructsOutput
}
func (c *ConverterImpl) structsInputToStructsOutput(source execution.Input) execution.Output {
var structsOutput execution.Output
structsOutput.Name = source.Name
structsOutput.Age = execution.DefaultAge()
return structsOutput
}
10 changes: 3 additions & 7 deletions scenario/map_identity_pointer2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ success: |
func (c *ConverterImpl) Convert(source *execution.In) *execution.Out {
var pStructsOut *execution.Out
if source != nil {
structsOut := c.structsInToStructsOut((*source))
var structsOut execution.Out
structsOut.Base = c.structsInToStructsBase((*source))
structsOut.Age = (*source).Age
pStructsOut = &structsOut
}
return pStructsOut
Expand All @@ -43,9 +45,3 @@ success: |
structsBase.Name = source.Name
return structsBase
}
func (c *ConverterImpl) structsInToStructsOut(source execution.In) execution.Out {
var structsOut execution.Out
structsOut.Base = c.structsInToStructsBase(source)
structsOut.Age = source.Age
return structsOut
}
40 changes: 40 additions & 0 deletions scenario/map_identity_pointer_source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
input:
input.go: |
package structs
// goverter:converter
type Converter interface {
// goverter:map . FullName | ToFullName
ConvertPerson(source *Person) *APIPerson
}
type Person struct {
FirstName string
LastName string
}
type APIPerson struct {
FullName string
}
func ToFullName(input *Person) string {
return input.FirstName + " " + input.LastName
}
success: |
// Code generated by github.com/jmattheis/goverter, DO NOT EDIT.
package generated
import execution "github.com/jmattheis/goverter/execution"
type ConverterImpl struct{}
func (c *ConverterImpl) ConvertPerson(source *execution.Person) *execution.APIPerson {
var pStructsAPIPerson *execution.APIPerson
if source != nil {
var structsAPIPerson execution.APIPerson
structsAPIPerson.FullName = execution.ToFullName(source)
pStructsAPIPerson = &structsAPIPerson
}
return pStructsAPIPerson
}
Loading

0 comments on commit 7752ba3

Please sign in to comment.