Skip to content

Commit

Permalink
Merge pull request #140 from jmattheis/alias
Browse files Browse the repository at this point in the history
Alias
  • Loading branch information
jmattheis authored Feb 23, 2024
2 parents 7deef0f + 23a40a6 commit 5357080
Show file tree
Hide file tree
Showing 15 changed files with 162 additions and 51 deletions.
4 changes: 2 additions & 2 deletions builder/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (*Enum) Build(gen Generator, ctx *MethodContext, sourceID *xtype.JenID, sou

enumUnknown := ctx.Conf.Common.Enum.Unknown
if enumUnknown == "" {
return nil, nil, NewError("No enum:unknown configured.\nSee https://goverter.jmattheis.de/guide/enum#unknown-enum-values")
return nil, nil, NewError("Enum detected but enum:unknown is not configured.\nSee https://goverter.jmattheis.de/guide/enum")
}

body, err := caseAction(gen, ctx, nameVar, target, targetEnum, enumUnknown, sourceID, path)
Expand Down Expand Up @@ -114,7 +114,7 @@ func caseAction(gen Generator, ctx *MethodContext, nameVar *jen.Statement, targe
}
_, ok := targetEnum.Members[targetName]
if !ok {
return nil, NewError(fmt.Sprintf("Enum %s does not exist on\n %s", targetName, target.String))
return nil, NewError(fmt.Sprintf("Enum %s does not exist on\n %s\n\nSee https://goverter.jmattheis.de/guide/enum", targetName, target.String))
}

targetQual := jen.Qual(target.NamedType.Obj().Pkg().Path(), targetName)
Expand Down
1 change: 1 addition & 0 deletions docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export default defineConfig({
],
sidebar: [
{ text: "Intro", link: "/" },
{ text: "Use-Cases", link: "/use-case" },
{
text: "Guides",
items: [
Expand Down
5 changes: 3 additions & 2 deletions docs/guide/enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ of these actions.
## Mapping enum keys

If your source and target enum have differently named keys, you can use
[`enum:map`](../reference/enum.md#enummap-source-target) to define the mapping.
[`enum:map`](../reference/enum.md#enum-map-source-target) to define the mapping.

::: details Example (click me)
::: code-group
Expand Down Expand Up @@ -118,7 +118,8 @@ transformer](../reference/enum.md#enum-transform-custom)

## Disable enum detection and conversion

To disable enum detection and conversion, add `enum no` to the converter.
To disable enum detection and conversion, add `enum no` to the converter or a
method converting enums.

::: details Example (click me)
::: code-group
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ These settings can be defined as [CLI argument](./define-settings.md#cli),
[method comment](./define-settings.md#method) and are
[inheritable](./define-settings.md#inheritance).

- [`enum:unknown ACTION|KEY` handle unexpected enum values](./enum.md#enum-default-action)
- [`enum:unknown ACTION|KEY` handle unexpected enum values](./enum.md#enum-unknown-action)
- [`ignoreMissing [yes,no]` ignore missing struct fields](./ignoreMissing.md)
- [`ignoreUnexported [yes,no]` ignore unexported struct fields](./ignoreUnexported.md)
- [`matchIgnoreCase [yes,no]` case-insensitive field matching](./matchIgnoreCase.md)
Expand Down
42 changes: 42 additions & 0 deletions docs/use-case.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Use-Cases

Goverter generates boilerplate code for you to convert types to other types.
This can be useful if you have types representing similar data. Use-Cases can
be:

[[toc]]

## Converting between database and API types

Sometimes you have different database and API types because they are:

* Generated with e.g. via [sqlc-dev/sqlc](https://github.com/sqlc-dev/sqlc),
[volatiletech/sqlboiler](https://github.com/volatiletech/sqlboiler)
[99designs/gqlgen](https://github.com/99designs/gqlgen),
[protobuf](https://protobuf.dev/getting-started/gotutorial/) or
[deepmap/oapi-codegen](https://github.com/deepmap/oapi-codegen)
* or because the database ORM requires different typing than the api types
* or because the database models are just different from the API because the
API must stay backwards compatible

Given any of these reasons it may be useful to generate the conversion via
goverter because it validates that all fields are mapped and reduces the need
for manually converting these types.

## Converting between API versions

When you have to support older API version you could structure you app so it
always uses the latest api types and then convert the latest api types to older
api types via Goverter. The types may have a lot of similarities and therefore,
goverter should be able to automatically convert most of the types.

## Deep copy instances

Goverter allows you to deep copy types by defining a conversion where the
source and target type is the same. This is useful when you want to adjust a
copy of a instance without affecting the source instance.

This can be done at runtime with a library like
[github.com/jinzhu/copier](ttps://github.com/jinzhu/copier) but using generated
code will be much faster and you'll have a compile-time guarantee that the
types can be coverted.
39 changes: 39 additions & 0 deletions scenario/alias_gomap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
input:
input.go: |
package alias
// goverter:converter
type Converter interface {
Convert(source InputAlias) OutputAlias
}
type InputAlias = map[string]Input
type OutputAlias = map[string]Output
type Input struct { Name string }
type Output struct { 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 map[string]execution.Input) map[string]execution.Output {
var mapStringAliasOutput map[string]execution.Output
if source != nil {
mapStringAliasOutput = make(map[string]execution.Output, len(source))
for key, value := range source {
mapStringAliasOutput[key] = c.aliasInputToAliasOutput(value)
}
}
return mapStringAliasOutput
}
func (c *ConverterImpl) aliasInputToAliasOutput(source execution.Input) execution.Output {
var aliasOutput execution.Output
aliasOutput.Name = source.Name
return aliasOutput
}
29 changes: 29 additions & 0 deletions scenario/alias_named.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
input:
input.go: |
package alias
// goverter:converter
type Converter interface {
Convert(source InputAlias) OutputAlias
}
type InputAlias = Input
type OutputAlias = Output
type Input struct { Name string }
type Output struct { 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 {
var aliasOutput execution.Output
aliasOutput.Name = source.Name
return aliasOutput
}
2 changes: 2 additions & 0 deletions scenario/enum_map_missing_target.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ error: |-
Enum Oops does not exist on
github.com/jmattheis/goverter/execution/output.Color
See https://goverter.jmattheis.de/guide/enum
4 changes: 2 additions & 2 deletions scenario/enum_unknown_missing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,5 @@ error: |-
|
| github.com/jmattheis/goverter/execution/output.Color
No enum:unknown configured.
See https://goverter.jmattheis.de/guide/enum#unknown-enum-values
Enum detected but enum:unknown is not configured.
See https://goverter.jmattheis.de/guide/enum
23 changes: 12 additions & 11 deletions xtype/tocode.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,28 @@ func toCodeSignature(t *types.Signature) *jen.Statement {
}

func toCodeNamed(t *types.Named) *jen.Statement {
var name *jen.Statement
if t.Obj().Pkg() == nil {
name = jen.Id(t.Obj().Name())
} else {
name = jen.Qual(t.Obj().Pkg().Path(), t.Obj().Name())
}

args := getTypeArgs(t)
name := toCodeObj(t.Obj())

if len(args) == 0 {
args := t.TypeArgs()
if args.Len() == 0 {
return name
}

jenArgs := []jen.Code{}
for _, arg := range args {
jenArgs = append(jenArgs, toCode(arg))
for i := 0; i < args.Len(); i++ {
jenArgs = append(jenArgs, toCode(args.At(i)))
}

return name.Index(jen.List(jenArgs...))
}

func toCodeObj(obj types.Object) *jen.Statement {
if obj.Pkg() == nil {
return jen.Id(obj.Name())
}
return jen.Qual(obj.Pkg().Path(), obj.Name())
}

func toCodeStruct(t *types.Struct) *jen.Statement {
fields := []jen.Code{}
for i := 0; i < t.NumFields(); i++ {
Expand Down
16 changes: 9 additions & 7 deletions xtype/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,9 +206,15 @@ func OtherID(code *jen.Statement) *JenID {

// TypeOf creates a Type.
func TypeOf(t types.Type) *Type {
t = Unalias(t)
rt := &Type{}
rt.T = t
rt.String = t.String()
applyTo(rt, t)
return rt
}

func applyTo(rt *Type, t types.Type) {
switch value := t.(type) {
case *types.Pointer:
rt.Pointer = true
Expand All @@ -230,12 +236,9 @@ func TypeOf(t types.Type) *Type {
rt.ListFixed = true
rt.ListInner = TypeOf(value.Elem())
case *types.Named:
underlying := TypeOf(value.Underlying())
underlying.T = rt.T
underlying.String = rt.String
underlying.Named = true
underlying.NamedType = value
return underlying
rt.Named = true
rt.NamedType = value
applyTo(rt, value.Underlying())
case *types.Struct:
rt.Struct = true
rt.StructType = value
Expand All @@ -250,7 +253,6 @@ func TypeOf(t types.Type) *Type {
default:
panic("unknown types.Type " + t.String())
}
return rt
}

// ID returns a deteministically generated id that may be used as variable.
Expand Down
16 changes: 0 additions & 16 deletions xtype/typeargs.go

This file was deleted.

10 changes: 0 additions & 10 deletions xtype/typeargs_go116.go

This file was deleted.

10 changes: 10 additions & 0 deletions xtype/unalias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build go1.22
// +build go1.22

package xtype

import "go/types"

func Unalias(t types.Type) types.Type {
return types.Unalias(t)
}
10 changes: 10 additions & 0 deletions xtype/unalias_pre122.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//go:build !go1.22
// +build !go1.22

package xtype

import "go/types"

func Unalias(t types.Type) (types.Type) {
return t
}

0 comments on commit 5357080

Please sign in to comment.