jmattheis / goverter

Generate type-safe Go converters by simply defining an interface
https://goverter.jmattheis.de/
MIT License
487 stars 46 forks source link

Allow functions calls in `goverter:map` #91

Closed s1berc0de closed 9 months ago

s1berc0de commented 10 months ago

I think it would be a great idea to map source structure method result to a target structure field (it can help to map private structure fields)

jmattheis commented 10 months ago

Could you give an example? With goverter comments and your preferred solution.

s1berc0de commented 10 months ago

Smth like this

type Input struct {
 id uint64
}

func (i Input) ID() uint64 {
 return i.id
}

type Output struct {
 ID uint64
}

// goverter:converter
type Converter interface {
  // goverter:map ID() ID
  Convert(source Input) Output
}
jmattheis commented 10 months ago

Yeah, sounds good. I'd say goverter should automatically try to map methods to properties, so your example above should be automatically handled. Still it should be possible to manually define it, when the names on the source and target doesn't match

type Input struct {
 id uint64
}

func (i Input) Identifier() uint64 {
 return i.id
}

type Output struct {
 ID uint64
}

// goverter:converter
type Converter interface {
  // goverter:map Identifier() ID
  Convert(source Input) Output
}
s1berc0de commented 10 months ago

Exactly, method and field can't have the same names, so if goverter can't find struct field with this name, it should to search a method, and now we have one restriction that is one method parameter, I think an idea to convert multi inputs in one output also good, it's pretty good if you use domain aggreagate of few entities

Now goverter can't map fields of anonymous nested struct, dunno why, but it is

jmattheis commented 10 months ago

now we have one restriction that is one method parameter, I think an idea to convert multi inputs in one output also good, it's pretty good if you use domain aggreagate of few entities

Could you elaborate this with an example? I don't understand what you mean with multiple inputs.

Goverter should be able to map fields on anonymous structs, but the definition is a bit cumbersome, could you give an example that doesn't work?

s1berc0de commented 10 months ago

About multi inputs: type EntityMixin struct { CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time ID uint64 }

type Account struct { Version time.Time ConfirmedAt sql.NullTime Email string Password string EntityMixin Status enums.AccountStatus }

type Role struct { ID uint64 Type enums.RoleType }

type Group struct { gorm.EntityMixin Name string }

// goverter:converter type Converter interface { // goverter:map accountEntity.EntityMixin.ID ID // goverter:map accountEntity.Email Email // goverter:map accountEntity.Version Version // goverter:map roleEntity.Type RoleType // goverter:map groupEntity.EntityMixin.ID GroupID DatabaseToEntities(accountEntity entities.Account, roleEntity entities.Role, groupEntity entities.Group) identity.Identity }

Error while creating converter method: ...

expected signature to have only one parameter exit status 1

s1berc0de commented 10 months ago

About one input, but nested EntityMixin struct, entities the same as upper, but contract is:

type DatabaseToEntitiesInput struct {
    AccountEntity *entities.Account
    RoleEntity    *entities.Role
    GroupEntity   *entities.Group
}

// goverter:converter
type Converter interface {
    // goverter:map AccountEntity.EntityMixin.ID ID
    // goverter:map AccountEntity.Email              Email
    // goverter:map AccountEntity.Version           Version
    // goverter:map RoleEntity.Type                     RoleType
    // goverter:map GroupEntity.EntityMixin.ID    GroupID
    DatabaseToEntities() *identity.Identity
}

Error while creating converter method: ....

source.??? target.ID uint64

...

Cannot match the target field with the source entry: "ID" does not exist. exit status 1

Can't find AccountEntity.EntityMixin.ID source, EntityMixin is anonimus nested struct

jmattheis commented 10 months ago

Multiple inputs will probably be supported with #68 I'll keep this in mind when implementing it.

Do you mean in the second example DatabaseToEntities(*DatabaseToEntitiesInput) *identity.Identity?

If yes, then you can use autoMap for this

// goverter:converter
type Converter interface {
    // goverter:autoMap AccountEntity
    // goverter:autoMap RoleEntity
    // goverter:autoMap GroupEntity
    DatabaseToEntities(*DatabaseToEntitiesInput) *identity.Identity
}

See https://goverter.jmattheis.de/#/conversion/mapping?id=auto-map & https://github.com/jmattheis/goverter/issues/55

s1berc0de commented 10 months ago

autoMap doesn't work because of anonymous nested EntityMixin, still error: Cannot match the target field with the source entry: "ID" does not exist. I see it works nice if all fields have names, but here is

type EntityMixin struct { CreatedAt time.Time UpdatedAt time.Time DeletedAt *time.Time ID uint64 }

type Account struct { Version time.Time ConfirmedAt sql.NullTime Email string Password string EntityMixin Status enums.AccountStatus }

jmattheis commented 10 months ago

Can you provide a smaller example that is self contained? You should be able to access the anonymous or rather embedded struct by the type name in your case EntityMixin in goverter. See https://github.com/jmattheis/goverter/issues/24

s1berc0de commented 10 months ago

Yes, if i write the mapping function i can get access to the ID field through the EntityMixin, but goverter doesn't see this ability

s1berc0de commented 10 months ago
type A struct {
    ID uint64
}

type B struct {
    A
    Test string
}

type C struct {
    ID   uint64
    Test string
}

type DatabaseToEntitiesInput struct {
    B B
}

// goverter:converter
type Converter interface {
    // goverter:autoMap B
    DatabaseToEntities(*DatabaseToEntitiesInput) *C
}

Gets error too

s1berc0de commented 10 months ago

Oh, if i don't use pointers inside of DatabaseToEntitiesInput it can map field from embedded struct

jmattheis commented 10 months ago

ID is on the A struct so it isn't seen by just automapping B, you need properties from both B and B.A:

// goverter:converter
type Converter interface {
    // goverter:autoMap B.A
    // goverter:autoMap B
    DatabaseToEntities(*DatabaseToEntitiesInput) *C
}

This builds without problems.

s1berc0de commented 10 months ago

Yeah, got now problems with mapping time.Time type

time.Time
source.AccountEntity.Version.??? target .Version.wall uint64
time.Time
s1berc0de commented 10 months ago

type A struct { ID uint64 }

type B struct { A Version time.Time Test string }

type C struct { ID uint64 Version time.Time Test string }

type DatabaseToEntitiesInput struct { B B }

// goverter:converter type Converter interface { // goverter:map B.A.ID ID // goverter:map B.Test Test // goverter:map B.Version Version DatabaseToEntities(DatabaseToEntitiesInput) C }

goverter tries to watch inside of time.Time structure instead of copy as object

jmattheis commented 10 months ago

Goverter doesn't know that see https://github.com/jmattheis/goverter/issues/4

s1berc0de commented 10 months ago

Yeah i see, thnq for the discussion, so now we have 2 features: #68 and that is in the title

jmattheis commented 9 months ago

Feature added in v1.1.0. Example:

Input:

package structs

// goverter:converter
type Converter interface {
    Convert(source Input) Output
}

type Input struct {
    Name string
}
func (Input) Age() int {
    return 42
}
type Output struct {
    Name string
    Age int
}

Output:

// 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 structsOutput execution.Output
    structsOutput.Name = source.Name
    structsOutput.Age = source.Age()
    return structsOutput
}

Fixes #91