Skip to content

Commit

Permalink
Merge pull request #175 from jmattheis/default-update
Browse files Browse the repository at this point in the history
default:update
  • Loading branch information
jmattheis authored Dec 13, 2024
2 parents 079f6e7 + 6517e11 commit 2edc4a0
Show file tree
Hide file tree
Showing 46 changed files with 854 additions and 184 deletions.
12 changes: 9 additions & 3 deletions builder/assignto.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import (
)

type AssignTo struct {
Must bool
Stmt *jen.Statement
Stmt *jen.Statement
Must bool
Update bool
}

func AssignOf(s *jen.Statement) *AssignTo {
Expand All @@ -20,11 +21,16 @@ func (a *AssignTo) WithIndex(s *jen.Statement) *AssignTo {
}
}

func (a *AssignTo) WithMust() *AssignTo {
func (a *AssignTo) MustAssign() *AssignTo {
a.Must = true
return a
}

func (a *AssignTo) IsUpdate() *AssignTo {
a.Update = true
return a
}

func ToAssignable(assignTo *AssignTo) func(stmt []jen.Code, nextID *xtype.JenID, err *Error) ([]jen.Code, *Error) {
return func(stmt []jen.Code, nextID *xtype.JenID, err *Error) ([]jen.Code, *Error) {
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type MethodContext struct {
Conf *config.Method
FieldsTarget string
OutputPackagePath string
UseConstructor bool
Signature xtype.Signature
TargetType *xtype.Type
HasMethod func(*MethodContext, types.Type, types.Type) bool
Expand Down
9 changes: 6 additions & 3 deletions builder/default.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package builder

import (
"go/types"

"github.com/dave/jennifer/jen"
"github.com/jmattheis/goverter/xtype"
)

func buildTargetVar(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *jen.Statement, *Error) {
if ctx.Conf.Constructor == nil ||
ctx.Conf.Source.String != source.String ||
ctx.Conf.Target.String != target.String {
if !ctx.UseConstructor ||
!types.Identical(ctx.Conf.Source.T, source.T) ||
!types.Identical(ctx.Conf.Target.T, target.T) {
name := ctx.Name(target.ID())
variable := jen.Var().Id(name).Add(target.TypeAsJen())
ctx.SetErrorTargetVar(jen.Id(name))
return []jen.Code{variable}, jen.Id(name), nil
}
ctx.UseConstructor = false

callTarget := target
toPointer := target.Pointer && !ctx.Conf.Constructor.Target.Pointer
Expand Down
2 changes: 1 addition & 1 deletion builder/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (*Map) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, source
})
}
valueStmt, err := gen.Assign(
ctx, assignTo.WithIndex(keyID.Code).WithMust(), xtype.VariableID(jen.Id(value)), source.MapValue, target.MapValue, errPath)
ctx, assignTo.WithIndex(keyID.Code).MustAssign(), xtype.VariableID(jen.Id(value)), source.MapValue, target.MapValue, errPath)
if err != nil {
return nil, err.Lift(&Path{
SourceID: "[]",
Expand Down
90 changes: 64 additions & 26 deletions builder/pointer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,34 @@ func (*Pointer) Matches(_ *MethodContext, source, target *xtype.Type) bool {
// Build creates conversion source code for the given source and target type.
func (p *Pointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *xtype.JenID, *Error) {
ctx.SetErrorTargetVar(jen.Nil())
if ctx.UseConstructor && ctx.Conf.DefaultUpdate {
buildStmt, valueVar, err := buildTargetVar(gen, ctx, sourceID, source, target, errPath)
if err != nil {
return nil, nil, err
}

stmt, err := gen.Assign(ctx, AssignOf(jen.Parens(jen.Op("*").Add(valueVar))).IsUpdate(), sourceID.Deref(source), source.PointerInner, target.PointerInner, errPath)
if err != nil {
return nil, nil, err.Lift(&Path{
SourceID: "*",
SourceType: source.PointerInner.String,
TargetID: "*",
TargetType: target.PointerInner.String,
})
}

buildStmt = append(buildStmt, jen.If(sourceID.Code.Clone().Op("!=").Nil()).Block(stmt...))

return buildStmt, xtype.VariableID(valueVar), nil
}

return BuildByAssign(p, gen, ctx, sourceID, source, target, errPath)
}

func (*Pointer) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *Error) {
ctx.SetErrorTargetVar(jen.Nil())

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, innerID, source.PointerInner, target.PointerInner, errPath)
nextBlock, id, err := gen.Build(ctx, sourceID.Deref(source), source.PointerInner, target.PointerInner, errPath)
if err != nil {
return nil, err.Lift(&Path{
SourceID: "*",
Expand All @@ -45,13 +58,8 @@ func (*Pointer) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, so
ifBlock := append(nextBlock, pstmt...)
ifBlock = append(ifBlock, assignTo.Stmt.Clone().Op("=").Add(tmpID.Code))

var elseCase []jen.Code
if assignTo.Must {
elseCase = append(elseCase, jen.Else().Block(assignTo.Stmt.Clone().Op("=").Nil()))
}

stmt := []jen.Code{
jen.If(sourceID.Code.Clone().Op("!=").Nil()).Block(ifBlock...).Add(elseCase...),
jen.If(sourceID.Code.Clone().Op("!=").Nil()).Block(ifBlock...),
}

return stmt, err
Expand All @@ -67,19 +75,30 @@ func (*SourcePointer) Matches(ctx *MethodContext, source, target *xtype.Type) bo

// Build creates conversion source code for the given source and target type.
func (s *SourcePointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *xtype.JenID, *Error) {
if ctx.UseConstructor && ctx.Conf.DefaultUpdate {
buildStmt, valueVar, err := buildTargetVar(gen, ctx, sourceID, source, target, path)
if err != nil {
return nil, nil, err
}

stmt, err := gen.Assign(ctx, AssignOf(valueVar).IsUpdate(), sourceID.Deref(source), source.PointerInner, target, path)
if err != nil {
return nil, nil, err.Lift(&Path{
SourceID: "*",
SourceType: source.PointerInner.String,
})
}

buildStmt = append(buildStmt, jen.If(sourceID.Code.Clone().Op("!=").Nil()).Block(stmt...))

return buildStmt, xtype.VariableID(valueVar), nil
}

return BuildByAssign(s, gen, ctx, sourceID, source, target, path)
}

func (*SourcePointer) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *Error) {
valueSourceID := jen.Op("*").Add(sourceID.Code.Clone())
if !source.PointerInner.Basic {
valueSourceID = jen.Parens(valueSourceID)
}

innerID := xtype.OtherID(valueSourceID)
innerID.ParentPointer = sourceID

nextInner, nextID, err := gen.Build(ctx, innerID, source.PointerInner, target, path)
nextInner, nextID, err := gen.Build(ctx, sourceID.Deref(source), source.PointerInner, target, path)
if err != nil {
return nil, err.Lift(&Path{
SourceID: "*",
Expand Down Expand Up @@ -107,18 +126,37 @@ func (*TargetPointer) Matches(_ *MethodContext, 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, path ErrorPath) ([]jen.Code, *xtype.JenID, *Error) {
ctx.SetErrorTargetVar(jen.Nil())

if ctx.UseConstructor {
buildStmt, valueVar, err := buildTargetVar(gen, ctx, sourceID, source, target, path)
if err != nil {
return nil, nil, err
}

stmt, err := gen.Assign(ctx, AssignOf(jen.Parens(jen.Op("*").Add(valueVar))).IsUpdate(), sourceID, source, target.PointerInner, path)
if err != nil {
return nil, nil, err.Lift(&Path{
TargetID: "*",
TargetType: target.PointerInner.String,
})
}

buildStmt = append(buildStmt, stmt...)

return buildStmt, xtype.VariableID(valueVar), nil
}

stmt, id, err := gen.Build(ctx, sourceID, source, target.PointerInner, path)
if err != nil {
return nil, nil, err.Lift(&Path{
SourceID: "*",
SourceType: source.String,
TargetID: "*",
TargetType: target.PointerInner.String,
})
}

pstmt, nextID := id.Pointer(target.PointerInner, ctx.Name)
stmt = append(stmt, pstmt...)

return stmt, nextID, nil
}

Expand Down
27 changes: 6 additions & 21 deletions builder/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,10 @@ func (s *Struct) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID,
if !source.Named && !target.Named && source.StructType.NumFields() == 0 && target.StructType.NumFields() == 0 {
return nil, sourceID, nil
}
stmt, nameVar, err := buildTargetVar(gen, ctx, sourceID, source, target, errPath)
if err != nil {
return nil, nil, err
}

stmtAssign, err := s.ConvertTo(gen, ctx, AssignOf(nameVar), sourceID, source, target, errPath)
if err != nil {
return nil, nil, err
}
stmt = append(stmt, stmtAssign...)

return stmt, xtype.VariableID(nameVar), nil
return BuildByAssign(s, gen, ctx, sourceID, source, target, errPath)
}

func (s *Struct) ConvertTo(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *Error) {
func (s *Struct) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, errPath ErrorPath) ([]jen.Code, *Error) {
additionalFieldSources, err := parseAutoMap(ctx, source)
if err != nil {
return nil, err
Expand Down Expand Up @@ -90,7 +79,7 @@ func (s *Struct) ConvertTo(gen Generator, ctx *MethodContext, assignTo *AssignTo
if err != nil {
return nil, err.Lift(lift...)
}
if shouldCheckAgainstZero(ctx, nextSource, targetFieldType, false) {
if shouldCheckAgainstZero(ctx, nextSource, targetFieldType, assignTo.Update, false) {
stmt = append(stmt, jen.If(nextID.Code.Clone().Op("!=").Add(xtype.ZeroValue(nextSource.T))).Block(fieldStmt...))
} else {
stmt = append(stmt, fieldStmt...)
Expand Down Expand Up @@ -132,7 +121,7 @@ func (s *Struct) ConvertTo(gen Generator, ctx *MethodContext, assignTo *AssignTo
}
callStmt = append(callStmt, assignTo.Stmt.Clone().Dot(targetField.Name()).Op("=").Add(callReturnID.Code))

if shouldCheckAgainstZero(ctx, functionCallSourceType, targetFieldType, true) {
if shouldCheckAgainstZero(ctx, functionCallSourceType, targetFieldType, assignTo.Update, true) {
stmt = append(stmt, jen.If(functionCallSourceID.Code.Clone().Op("!=").Add(xtype.ZeroValue(functionCallSourceType.T))).Block(callStmt...))
} else {
stmt = append(stmt, callStmt...)
Expand All @@ -154,9 +143,9 @@ func (s *Struct) ConvertTo(gen Generator, ctx *MethodContext, assignTo *AssignTo
return stmt, nil
}

func shouldCheckAgainstZero(ctx *MethodContext, s, t *xtype.Type, call bool) bool {
func shouldCheckAgainstZero(ctx *MethodContext, s, t *xtype.Type, isUpdate, call bool) bool {
switch {
case !ctx.Conf.UpdateTarget:
case !ctx.Conf.UpdateTarget && !isUpdate:
return false
case s.Struct && ctx.Conf.IgnoreStructZeroValueField:
return true
Expand All @@ -175,10 +164,6 @@ func shouldCheckAgainstZero(ctx *MethodContext, s, t *xtype.Type, call bool) boo
}
}

func (s *Struct) Assign(gen Generator, ctx *MethodContext, assignTo *AssignTo, sourceID *xtype.JenID, source, target *xtype.Type, path ErrorPath) ([]jen.Code, *Error) {
return AssignByBuild(s, gen, ctx, assignTo, sourceID, source, target, path)
}

func mapField(
gen Generator,
ctx *MethodContext,
Expand Down
6 changes: 3 additions & 3 deletions config/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Common struct {
SkipCopySameType bool
UseZeroValueOnPointerInconsistency bool
UseUnderlyingTypeMethods bool
DefaultUpdate bool
ArgContextRegex *regexp.Regexp
Enum enum.Config
}
Expand All @@ -45,14 +46,13 @@ func parseCommon(c *Common, cmd, rest string) (fieldSetting bool, err error) {
c.IgnoreStructZeroValueField = c.IgnoreBasicZeroValueField
c.IgnoreNillableZeroValueField = c.IgnoreBasicZeroValueField
case "update:ignoreZeroValueField:basic":
fieldSetting = true
c.IgnoreBasicZeroValueField, err = parseBool(rest)
case "update:ignoreZeroValueField:struct":
fieldSetting = true
c.IgnoreStructZeroValueField, err = parseBool(rest)
case "update:ignoreZeroValueField:nillable":
fieldSetting = true
c.IgnoreNillableZeroValueField, err = parseBool(rest)
case "default:update":
c.DefaultUpdate, err = parseBool(rest)
case "matchIgnoreCase":
fieldSetting = true
c.MatchIgnoreCase, err = parseBool(rest)
Expand Down
3 changes: 2 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import GH from './GH.vue';

## unreleased

- tbd
- Fix not setting `nil` on map when value is `nil`. <GH issue="173" pr="175"/>
- Add [`default:update`](/reference/default.md#default-update-yes-no) <GH issue="171" pr="175"/>

## v1.6.0

Expand Down
26 changes: 26 additions & 0 deletions docs/reference/default.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Setting: default

[[toc]]

## default [PACKAGE:]FUNC

`default [PACKAGE:]FUNC` can be defined as [method
comment](./define-settings.md#method).

Expand All @@ -18,3 +22,25 @@ package of the conversion method is used.
<<< @../../example/default/input.go
<<< @../../example/default/generated/generated.go [generated/generated.go]
:::

## default:update [yes|no]

`default:update [yes,no]` is a
[boolean setting](./define-settings.md#boolean) and can be defined as
[CLI argument](./define-settings.md#cli),
[conversion comment](./define-settings.md#conversion) or
[method comment](./define-settings.md#method). This setting is
[inheritable](./define-settings.md#inheritance).

> [!WARNING]
> If enabled, goverter requires you to return a **non nil** value in the
> `default` `FUNC`.

If _enabled_ goverter will update the existing instance returned by the
`default` `FUNC`.

::: code-group
<<< @../../example/default-update/input.go
<<< @../../example/default-update/generated/generated.go [generated/generated.go]
:::
20 changes: 20 additions & 0 deletions example/default-update/generated/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions example/default-update/input.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package example

// goverter:converter
// goverter:default:update
type Converter interface {
// goverter:default NewOutput
Convert(*Input) *Output
}

type Input struct {
Age int
Name *string
}
type Output struct {
Age int
Name *string
}

func NewOutput() *Output {
name := "jmattheis"
return &Output{Age: 42, Name: &name}
}
Loading

0 comments on commit 2edc4a0

Please sign in to comment.