From be6c0637980a348372cc700291bae7521ea91e06 Mon Sep 17 00:00:00 2001 From: Jannis Mattheis Date: Fri, 13 Dec 2024 09:50:14 +0100 Subject: [PATCH] feat: add `default:update` --- builder/builder.go | 1 + builder/default.go | 3 +- builder/pointer.go | 83 ++++++++++++++----- config/common.go | 6 +- docs/changelog.md | 1 + docs/reference/default.md | 26 ++++++ example/default-update/generated/generated.go | 20 +++++ example/default-update/input.go | 22 +++++ example/default/input.go | 5 +- generator/generator.go | 1 + scenario/default_on_basic_pointer.yml | 47 +++++++++++ scenario/default_on_pointer.yml | 33 +++++--- scenario/default_on_pointer_error.yml | 47 +++++++++++ scenario/default_on_pointer_with_error.yml | 26 ++++-- ...ult_on_pointer_with_non_pointer_method.yml | 24 ++++-- ...ter_with_non_pointer_method_with_error.yml | 27 ++++-- ...on_source_pointer_struct_target_struct.yml | 45 ++++++++++ ...rce_pointer_struct_target_struct_error.yml | 45 ++++++++++ ...inter_struct_target_struct_invalid_sig.yml | 36 ++++++++ ...efault_on_source_struct_target_pointer.yml | 53 ++++++++++++ ...struct_target_pointer_default_mismatch.yml | 29 +++++++ ...urce_struct_target_pointer_inner_error.yml | 46 ++++++++++ scenario/default_pointer_inconsistency.yml | 28 +++++-- scenario/map_overlapping_config2.yml | 4 +- scenario/setting_ignored_recursive2.yml | 12 ++- scenario/type_mismatch_pointer.yml | 8 +- xtype/type.go | 10 +++ 27 files changed, 611 insertions(+), 77 deletions(-) create mode 100644 example/default-update/generated/generated.go create mode 100644 example/default-update/input.go create mode 100644 scenario/default_on_basic_pointer.yml create mode 100644 scenario/default_on_pointer_error.yml create mode 100644 scenario/default_on_source_pointer_struct_target_struct.yml create mode 100644 scenario/default_on_source_pointer_struct_target_struct_error.yml create mode 100644 scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml create mode 100644 scenario/default_on_source_struct_target_pointer.yml create mode 100644 scenario/default_on_source_struct_target_pointer_default_mismatch.yml create mode 100644 scenario/default_on_source_struct_target_pointer_inner_error.yml diff --git a/builder/builder.go b/builder/builder.go index 64d03eee..d40e548e 100644 --- a/builder/builder.go +++ b/builder/builder.go @@ -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 diff --git a/builder/default.go b/builder/default.go index e8d03f0e..e13376e5 100644 --- a/builder/default.go +++ b/builder/default.go @@ -8,7 +8,7 @@ import ( ) 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 || + if !ctx.UseConstructor || !types.Identical(ctx.Conf.Source.T, source.T) || !types.Identical(ctx.Conf.Target.T, target.T) { name := ctx.Name(target.ID()) @@ -16,6 +16,7 @@ func buildTargetVar(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, so 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 diff --git a/builder/pointer.go b/builder/pointer.go index 6da834cb..bde0d772 100644 --- a/builder/pointer.go +++ b/builder/pointer.go @@ -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))), 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: "*", @@ -62,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), 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: "*", @@ -102,11 +126,29 @@ 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))), 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, }) @@ -114,6 +156,7 @@ func (*TargetPointer) Build(gen Generator, ctx *MethodContext, sourceID *xtype.J pstmt, nextID := id.Pointer(target.PointerInner, ctx.Name) stmt = append(stmt, pstmt...) + return stmt, nextID, nil } diff --git a/config/common.go b/config/common.go index e8bf832c..8da02556 100644 --- a/config/common.go +++ b/config/common.go @@ -20,6 +20,7 @@ type Common struct { SkipCopySameType bool UseZeroValueOnPointerInconsistency bool UseUnderlyingTypeMethods bool + DefaultUpdate bool ArgContextRegex *regexp.Regexp Enum enum.Config } @@ -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) diff --git a/docs/changelog.md b/docs/changelog.md index c0301044..0d554760 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,6 +7,7 @@ import GH from './GH.vue'; ## unreleased - Fix not setting `nil` on map when value is `nil`. +- Add [`default:update`](/reference/default.md#default-update-yes-no) ## v1.6.0 diff --git a/docs/reference/default.md b/docs/reference/default.md index acda603f..8eaf944e 100644 --- a/docs/reference/default.md +++ b/docs/reference/default.md @@ -1,5 +1,9 @@ # Setting: default +[[toc]] + +## default [PACKAGE:]FUNC + `default [PACKAGE:]FUNC` can be defined as [method comment](./define-settings.md#method). @@ -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] +::: diff --git a/example/default-update/generated/generated.go b/example/default-update/generated/generated.go new file mode 100644 index 00000000..6e2480c0 --- /dev/null +++ b/example/default-update/generated/generated.go @@ -0,0 +1,20 @@ +// Code generated by github.com/jmattheis/goverter, DO NOT EDIT. +//go:build !goverter + +package generated + +import defaultupdate "github.com/jmattheis/goverter/example/default-update" + +type ConverterImpl struct{} + +func (c *ConverterImpl) Convert(source *defaultupdate.Input) *defaultupdate.Output { + pExampleOutput := defaultupdate.NewOutput() + if source != nil { + (*pExampleOutput).Age = (*source).Age + if (*source).Name != nil { + xstring := *(*source).Name + (*pExampleOutput).Name = &xstring + } + } + return pExampleOutput +} diff --git a/example/default-update/input.go b/example/default-update/input.go new file mode 100644 index 00000000..e62c758e --- /dev/null +++ b/example/default-update/input.go @@ -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} +} diff --git a/example/default/input.go b/example/default/input.go index 1bc45ab1..6ba46a24 100644 --- a/example/default/input.go +++ b/example/default/input.go @@ -17,8 +17,5 @@ type Output struct { func NewOutput() *Output { name := "jmattheis" - return &Output{ - Age: 42, - Name: &name, - } + return &Output{Age: 42, Name: &name} } diff --git a/generator/generator.go b/generator/generator.go index 63a54fea..8c216a75 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -142,6 +142,7 @@ func (g *generator) buildMethod(genMethod *generatedMethod, context map[string]* Signature: genMethod.Signature, HasMethod: g.hasMethod, OutputPackagePath: g.conf.OutputPackagePath, + UseConstructor: genMethod.Constructor != nil, } var targetAssign *jen.Statement diff --git a/scenario/default_on_basic_pointer.yml b/scenario/default_on_basic_pointer.yml new file mode 100644 index 00000000..a8f99868 --- /dev/null +++ b/scenario/default_on_basic_pointer.yml @@ -0,0 +1,47 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewStringP + One(source string) *string + + // goverter:default NewStringP + Two(source *string) *string + + // goverter:default NewString + // goverter:useZeroValueOnPointerInconsistency + Three(source *string) string + } + func NewStringP() *string { return nil } + func NewString() string { return "" } +success: + - generated/generated.go: | + // 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) One(source string) *string { + pString := source + return &pString + } + func (c *ConverterImpl) Three(source *string) string { + xstring := execution.NewString() + if source != nil { + xstring = *source + } + return xstring + } + func (c *ConverterImpl) Two(source *string) *string { + pString := execution.NewStringP() + if source != nil { + xstring := *source + pString = &xstring + } + return pString + } diff --git a/scenario/default_on_pointer.yml b/scenario/default_on_pointer.yml index 37b3c7a2..84b51a47 100644 --- a/scenario/default_on_pointer.yml +++ b/scenario/default_on_pointer.yml @@ -3,21 +3,22 @@ input: package execution // goverter:converter - type Converter interface { + type Default interface { // goverter:default NewOutputWithDefaults Convert(source *Input) (*Output, error) } - type Input struct { - Name string - } - type Output struct { - Name string + + // goverter:converter + type Update interface { + // goverter:default NewOutputWithDefaults + // goverter:default:update + Update(source *Input) (*Output, error) } + type Input struct { Name string } + type Output struct { Name string } func NewOutputWithDefaults() *Output { - return &Output{ - Name: "string", - } + return &Output{ Name: "string" } } success: - generated/generated.go: | @@ -27,9 +28,9 @@ success: import execution "github.com/jmattheis/goverter/execution" - type ConverterImpl struct{} + type DefaultImpl struct{} - func (c *ConverterImpl) Convert(source *execution.Input) (*execution.Output, error) { + func (c *DefaultImpl) Convert(source *execution.Input) (*execution.Output, error) { pExecutionOutput := execution.NewOutputWithDefaults() if source != nil { var executionOutput execution.Output @@ -38,3 +39,13 @@ success: } return pExecutionOutput, nil } + + type UpdateImpl struct{} + + func (c *UpdateImpl) Update(source *execution.Input) (*execution.Output, error) { + pExecutionOutput := execution.NewOutputWithDefaults() + if source != nil { + (*pExecutionOutput).Name = (*source).Name + } + return pExecutionOutput, nil + } diff --git a/scenario/default_on_pointer_error.yml b/scenario/default_on_pointer_error.yml new file mode 100644 index 00000000..f39b4803 --- /dev/null +++ b/scenario/default_on_pointer_error.yml @@ -0,0 +1,47 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source *Input) (*Output, error) + } + type Input struct { + Name int + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return &Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:6 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source *github.com/jmattheis/goverter/execution.Input) (*github.com/jmattheis/goverter/execution.Output, error) + [source] *github.com/jmattheis/goverter/execution.Input + [target] *github.com/jmattheis/goverter/execution.Output + + | *github.com/jmattheis/goverter/execution.Input + | + | | github.com/jmattheis/goverter/execution.Input + | | + | | | int + | | | + source*.Name + target*.Name + | | | + | | | string + | | + | | github.com/jmattheis/goverter/execution.Output + | + | *github.com/jmattheis/goverter/execution.Output + + TypeMismatch: Cannot convert int to string + + You can define a custom conversion method with extend: + https://goverter.jmattheis.de/reference/extend diff --git a/scenario/default_on_pointer_with_error.yml b/scenario/default_on_pointer_with_error.yml index 4a627f6b..e7bd71e7 100644 --- a/scenario/default_on_pointer_with_error.yml +++ b/scenario/default_on_pointer_with_error.yml @@ -8,12 +8,15 @@ input: Convert(source *Input) (*Output, error) } - type Input struct { - ID int - } - type Output struct { - ID int + // goverter:converter + type Update interface { + // goverter:default NewOutput + // goverter:default:update + Update(source *Input) (*Output, error) } + + type Input struct { ID int } + type Output struct { ID int } func NewOutput() (*Output, error) { return &Output{}, nil } @@ -39,3 +42,16 @@ success: } return pExecutionOutput, nil } + + type UpdateImpl struct{} + + func (c *UpdateImpl) Update(source *execution.Input) (*execution.Output, error) { + pExecutionOutput, err := execution.NewOutput() + if err != nil { + return nil, err + } + if source != nil { + (*pExecutionOutput).ID = (*source).ID + } + return pExecutionOutput, nil + } diff --git a/scenario/default_on_pointer_with_non_pointer_method.yml b/scenario/default_on_pointer_with_non_pointer_method.yml index 3de14a90..f282aa87 100644 --- a/scenario/default_on_pointer_with_non_pointer_method.yml +++ b/scenario/default_on_pointer_with_non_pointer_method.yml @@ -8,13 +8,16 @@ input: Convert(source *Input) (*Output) } - type Input struct { - Name string - } - type Output struct { - Name string + // goverter:converter + type Update interface { + // goverter:default NewOutput + // goverter:default:update + Update(source *Input) (*Output) } + type Input struct { Name string } + type Output struct { Name string } + func NewOutput() Output { return Output{Name: "string"} } @@ -38,3 +41,14 @@ success: } return pExecutionOutput } + + type UpdateImpl struct{} + + func (c *UpdateImpl) Update(source *execution.Input) *execution.Output { + executionOutput := execution.NewOutput() + pExecutionOutput := &executionOutput + if source != nil { + (*pExecutionOutput).Name = (*source).Name + } + return pExecutionOutput + } diff --git a/scenario/default_on_pointer_with_non_pointer_method_with_error.yml b/scenario/default_on_pointer_with_non_pointer_method_with_error.yml index 3326b3e1..17c6f2b7 100644 --- a/scenario/default_on_pointer_with_non_pointer_method_with_error.yml +++ b/scenario/default_on_pointer_with_non_pointer_method_with_error.yml @@ -8,13 +8,16 @@ input: Convert(source *Input) (*Output, error) } - type Input struct { - Name string - } - type Output struct { - Name string + // goverter:converter + type Update interface { + // goverter:default NewOutput + // goverter:default:update + Update(source *Input) (*Output, error) } + type Input struct { Name string } + type Output struct { Name string } + func NewOutput() (Output, error) { return Output{Name: "string"}, nil } @@ -41,3 +44,17 @@ success: } return pExecutionOutput, nil } + + type UpdateImpl struct{} + + func (c *UpdateImpl) Update(source *execution.Input) (*execution.Output, error) { + executionOutput, err := execution.NewOutput() + if err != nil { + return nil, err + } + pExecutionOutput := &executionOutput + if source != nil { + (*pExecutionOutput).Name = (*source).Name + } + return pExecutionOutput, nil + } diff --git a/scenario/default_on_source_pointer_struct_target_struct.yml b/scenario/default_on_source_pointer_struct_target_struct.yml new file mode 100644 index 00000000..fe8d3e83 --- /dev/null +++ b/scenario/default_on_source_pointer_struct_target_struct.yml @@ -0,0 +1,45 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source Input) (*Output, error) + } + + // goverter:converter + type Update interface { + // goverter:default:update + // goverter:default NewOutputWithDefaults + Update(source Input) (*Output, error) + } + type Input struct { Name string } + type Output struct { Name string } + + func NewOutputWithDefaults() *Output { + return &Output{ Name: "string" } + } +success: + - generated/generated.go: | + // 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) Convert(source execution.Input) (*execution.Output, error) { + pExecutionOutput := execution.NewOutputWithDefaults() + (*pExecutionOutput).Name = source.Name + return pExecutionOutput, nil + } + + type UpdateImpl struct{} + + func (c *UpdateImpl) Update(source execution.Input) (*execution.Output, error) { + pExecutionOutput := execution.NewOutputWithDefaults() + (*pExecutionOutput).Name = source.Name + return pExecutionOutput, nil + } diff --git a/scenario/default_on_source_pointer_struct_target_struct_error.yml b/scenario/default_on_source_pointer_struct_target_struct_error.yml new file mode 100644 index 00000000..85270a82 --- /dev/null +++ b/scenario/default_on_source_pointer_struct_target_struct_error.yml @@ -0,0 +1,45 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source Input) (*Output, error) + } + type Input struct { + Name int + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return &Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:6 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source github.com/jmattheis/goverter/execution.Input) (*github.com/jmattheis/goverter/execution.Output, error) + [source] github.com/jmattheis/goverter/execution.Input + [target] *github.com/jmattheis/goverter/execution.Output + + | github.com/jmattheis/goverter/execution.Input + | + | | int + | | + source .Name + target*.Name + | | | + | | | string + | | + | | github.com/jmattheis/goverter/execution.Output + | + | *github.com/jmattheis/goverter/execution.Output + + TypeMismatch: Cannot convert int to string + + You can define a custom conversion method with extend: + https://goverter.jmattheis.de/reference/extend diff --git a/scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml b/scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml new file mode 100644 index 00000000..6f1d1ed6 --- /dev/null +++ b/scenario/default_on_source_pointer_struct_target_struct_invalid_sig.yml @@ -0,0 +1,36 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + Convert(source Input) (*Output, error) + } + + type Input struct { Name string } + type Output struct { Name string } + + func NewOutputWithDefaults(string) *Output { + return &Output{ Name: "string" } + } +error: |- + Error while creating converter method: + @workdir/input.go:6 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source github.com/jmattheis/goverter/execution.Input) (*github.com/jmattheis/goverter/execution.Output, error) + [source] github.com/jmattheis/goverter/execution.Input + [target] *github.com/jmattheis/goverter/execution.Output + + | github.com/jmattheis/goverter/execution.Input + | + source + target + | + | *github.com/jmattheis/goverter/execution.Output + + Error using method: + func github.com/jmattheis/goverter/execution.NewOutputWithDefaults(string) *github.com/jmattheis/goverter/execution.Output + [source] string + [target] *github.com/jmattheis/goverter/execution.Output + + Method source type mismatches with conversion source: string != github.com/jmattheis/goverter/execution.Input diff --git a/scenario/default_on_source_struct_target_pointer.yml b/scenario/default_on_source_struct_target_pointer.yml new file mode 100644 index 00000000..bd924b45 --- /dev/null +++ b/scenario/default_on_source_struct_target_pointer.yml @@ -0,0 +1,53 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + // goverter:useZeroValueOnPointerInconsistency + Convert(source *Input) (Output, error) + } + + // goverter:converter + type Update interface { + // goverter:default NewOutputWithDefaults + // goverter:default:update + // goverter:useZeroValueOnPointerInconsistency + Update(source *Input) (Output, error) + } + type Input struct { Name string } + type Output struct { Name string } + + func NewOutputWithDefaults() Output { + return Output{ Name: "string" } + } +success: + - generated/generated.go: | + // 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) Convert(source *execution.Input) (execution.Output, error) { + executionOutput := execution.NewOutputWithDefaults() + if source != nil { + var executionOutput2 execution.Output + executionOutput2.Name = (*source).Name + executionOutput = executionOutput2 + } + return executionOutput, nil + } + + type UpdateImpl struct{} + + func (c *UpdateImpl) Update(source *execution.Input) (execution.Output, error) { + executionOutput := execution.NewOutputWithDefaults() + if source != nil { + executionOutput.Name = (*source).Name + } + return executionOutput, nil + } diff --git a/scenario/default_on_source_struct_target_pointer_default_mismatch.yml b/scenario/default_on_source_struct_target_pointer_default_mismatch.yml new file mode 100644 index 00000000..12b91cea --- /dev/null +++ b/scenario/default_on_source_struct_target_pointer_default_mismatch.yml @@ -0,0 +1,29 @@ +version_dependent: true +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + // goverter:useZeroValueOnPointerInconsistency + Convert(source *Input) (Output, error) + } + type Input struct { + Name string + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() *Output { + return Output{ Name: "string" } + } +error: |- + could not load package github.com/jmattheis/goverter/execution + + -: # github.com/jmattheis/goverter/execution + ./input.go:17:12: cannot use Output{…} (value of type Output) as *Output value in return statement + + Goverter cannot generate converters when there are compile errors because it + requires the type information from the compiled sources. diff --git a/scenario/default_on_source_struct_target_pointer_inner_error.yml b/scenario/default_on_source_struct_target_pointer_inner_error.yml new file mode 100644 index 00000000..42bf5928 --- /dev/null +++ b/scenario/default_on_source_struct_target_pointer_inner_error.yml @@ -0,0 +1,46 @@ +input: + input.go: | + package execution + + // goverter:converter + type Converter interface { + // goverter:default NewOutputWithDefaults + // goverter:useZeroValueOnPointerInconsistency + Convert(source *Input) (Output, error) + } + type Input struct { + Name int + } + type Output struct { + Name string + } + + func NewOutputWithDefaults() Output { + return Output{ + Name: "string", + } + } +error: |- + Error while creating converter method: + @workdir/input.go:7 + func (github.com/jmattheis/goverter/execution.Converter).Convert(source *github.com/jmattheis/goverter/execution.Input) (github.com/jmattheis/goverter/execution.Output, error) + [source] *github.com/jmattheis/goverter/execution.Input + [target] github.com/jmattheis/goverter/execution.Output + + | *github.com/jmattheis/goverter/execution.Input + | + | | github.com/jmattheis/goverter/execution.Input + | | + | | | int + | | | + source*.Name + target .Name + | | + | | string + | + | github.com/jmattheis/goverter/execution.Output + + TypeMismatch: Cannot convert int to string + + You can define a custom conversion method with extend: + https://goverter.jmattheis.de/reference/extend diff --git a/scenario/default_pointer_inconsistency.yml b/scenario/default_pointer_inconsistency.yml index 7cc44a53..2baefdea 100644 --- a/scenario/default_pointer_inconsistency.yml +++ b/scenario/default_pointer_inconsistency.yml @@ -9,16 +9,18 @@ input: Convert(source *Input) Output } - type Input struct { - ID int - } - type Output struct { - ID int + // goverter:converter + // goverter:useZeroValueOnPointerInconsistency + type Update interface { + // goverter:default NewOutput + // goverter:default:update + ConvertUpdate(source *Input) Output } + + type Input struct { ID int } + type Output struct { ID int } func NewOutput() Output { - return Output{ - ID: 0, - } + return Output{ ID: 0 } } success: - generated/generated.go: | @@ -39,3 +41,13 @@ success: } return executionOutput } + + type UpdateImpl struct{} + + func (c *UpdateImpl) ConvertUpdate(source *execution.Input) execution.Output { + executionOutput := execution.NewOutput() + if source != nil { + executionOutput.ID = (*source).ID + } + return executionOutput + } diff --git a/scenario/map_overlapping_config2.yml b/scenario/map_overlapping_config2.yml index 292133e7..d71866f6 100644 --- a/scenario/map_overlapping_config2.yml +++ b/scenario/map_overlapping_config2.yml @@ -25,9 +25,7 @@ error: |- | github.com/jmattheis/goverter/execution.Input | - | | github.com/jmattheis/goverter/execution.Input - | | - source* + source target* | | | | github.com/jmattheis/goverter/execution.Output diff --git a/scenario/setting_ignored_recursive2.yml b/scenario/setting_ignored_recursive2.yml index 90a4edf1..ae82c677 100644 --- a/scenario/setting_ignored_recursive2.yml +++ b/scenario/setting_ignored_recursive2.yml @@ -23,13 +23,11 @@ error: |- | github.com/jmattheis/goverter/execution.Input | - | | github.com/jmattheis/goverter/execution.Input - | | - | | | []github.com/jmattheis/goverter/execution.Input - | | | - | | | | github.com/jmattheis/goverter/execution.Input - | | | | - source*.Inputs [] + | | []github.com/jmattheis/goverter/execution.Input + | | + | | | github.com/jmattheis/goverter/execution.Input + | | | + source .Inputs [] target*.Outputs[] | | | | | | | | github.com/jmattheis/goverter/execution.Output diff --git a/scenario/type_mismatch_pointer.yml b/scenario/type_mismatch_pointer.yml index 96afd5a9..434126aa 100644 --- a/scenario/type_mismatch_pointer.yml +++ b/scenario/type_mismatch_pointer.yml @@ -24,11 +24,9 @@ error: |- | github.com/jmattheis/goverter/execution.Input | - | | github.com/jmattheis/goverter/execution.Input - | | - | | | int - | | | - source*.Age + | | int + | | + source .Age target*.Age | | | | | | int64 diff --git a/xtype/type.go b/xtype/type.go index 163c190a..365f2f8c 100644 --- a/xtype/type.go +++ b/xtype/type.go @@ -196,6 +196,16 @@ func (j *JenID) Pointer(t *Type, namer func(string) string) ([]jen.Code, *JenID) return stmt, OtherID(jen.Op("&").Id(name)) } +func (j *JenID) Deref(source *Type) *JenID { + valueSourceID := jen.Op("*").Add(j.Code.Clone()) + if !source.PointerInner.Basic { + valueSourceID = jen.Parens(valueSourceID) + } + innerID := OtherID(valueSourceID) + innerID.ParentPointer = j + return innerID +} + // VariableID is used, when the ID can be referenced. F.ex it is not a function call. func VariableID(code *jen.Statement) *JenID { return &JenID{Code: code, Variable: true}