Closed jmattheis closed 1 month ago
Hi jmattheis, thanks for your great project. And I like your new proposed approach very much, would you like to share the progress on this feature?
Hey @zhenzou, there is no progress on this feature.
I think we can keep the original Converter interface. We just need to change the generated code implementation from a struct to a function. The function name can be created by combining interface name + method name.
The downside is that the interface won't have an implementation, which might seem a bit odd.
@KScaesar Only doing this would prevent the feature of using generated methods in custom methods. E.g. https://goverter.jmattheis.de/#/config/extend?id=method-with-converter
// goverter:converter
// goverter:extend ConvertAnimals
type Converter interface {
Convert(source Input) Output
// used only in extend method
ConvertDogs([]Dog) []Animal
ConvertCats([]Cat) []Animal
}
func ConvertAnimals(c Converter, input InputAnimals) []Animal {
dogs := c.ConvertDogs(input.Dogs)
cats := c.ConvertCats(input.Cats)
return append(dogs, cats...)
}
Both the function and interface definition should support the same featureset.
I think we can keep the original Converter interface.
// goverter:converter
// goverter:extend ConvertAnimals
type Converter interface {
Convert(source Input) Output
// used only in extend method
ConvertDogs([]Dog) []Animal
ConvertCats([]Cat) []Animal
}
func ConvertAnimals(input InputAnimals) []Animal {
dogs := ConvertDogs(input.Dogs)
cats := ConvertCats(input.Cats)
return append(dogs, cats...)
}```
At the time of generation ConvertDogs
and ConvertCats
doesn't exist. So the code won't successfully compile. Goverter uses type information for generation and therefore requires that the package with the converter interface compiles.
I'm looking for testers of the new style for generation. The version should work well, but they likely will some changes before this will be released.
go install github.com/jmattheis/goverter/cmd/goverter@variables-v3
go run github.com/jmattheis/goverter/cmd/goverter@variables-v3 [pattern]
go get github.com/jmattheis/goverter@variables-v3
When using the version above, you can declare goverter:variables
on a var
statement containing multiple variables of type func (Source) Target
. When running goverter, it will create a new file in the same package with an func init
initializing the defined variables.
You can define Converter settings directly on the outer var statement. Methods settings can be defined on the separate variables.
Here an complex example in both styles.
// goverter:converter
// goverter:extend SQLStringToPString
type Converter interface {
ConvertHouse(source DBHouse) APIHouse
// goverter:map Name FirstName
// goverter:ignore Age
ConvertPerson(source DBPerson) APIPerson
// goverter:map Owner.Name OwnerName
ConvertApartment(source DBApartment) APIApartment
}
// goverter:variables
// goverter:extend SQLStringToPString
var (
ConvertHouse func(source DBHouse) APIHouse
// goverter:map Name FirstName
// goverter:ignore Age
ConvertPerson func(source DBPerson) APIPerson
// goverter:map Owner.Name OwnerName
ConvertApartment func(source DBApartment) APIApartment
)
If you've tried out the new style, it would be great if you could answer the following questions:
Hi, thanks for this amazing tool. I'm currently trying out the new variables
approach for generating converters, and it's a significant improvement compared to the original converter
.
I've been using goverter for a while now (https://github.com/jmattheis/goverter/issues/100), and the original converter
was indeed very useful. However, there was a minor issue where once I deleted converter_gen.go and then ran goverter gen ..
again, it would fail. (I believe there was an issue in older versions where changing the field types would prevent regeneration without deleting converter_gen.go. Apologies for not investigating and tracking the issue further.)
To work around this, I defined the following function:
func newConverter() converter {
return &converterImpl{}
}
When generating:
func newConverter() converter {
return nil
}
This function helped minimize changes when deleting and regenerating.
The new variables
approach completely eliminates the hassle of regenerating after deleting converter_gen.go. I hope it can be merged into the main branch soon.
Looks great! In my case no bugs found.
Thanks for you feedback. The solution in https://github.com/jmattheis/goverter/issues/77#issuecomment-1892598584 was implemented in v1.5.0. See Guide: Input/output formats.
Furthermore, it's possible to generate functions without a struct when defining conversions via an interface as described in https://github.com/jmattheis/goverter/issues/77#issuecomment-1804546324. See Guide: Input/output formats.
Goverter generates an implementation for a user defined interface with conversion methods. The usability of that could be better, because now the user has to initialize the Converter struct and call methods from there. Goverter could generate functions directly. If this ticket will be included into Goverter, then the old interface approach will most likely be removed.
Current example (click me)
```go // goverter:converter // goverter:extend ConvertAnimals type Converter interface { Convert(source Input) Output // used only in extend method ConvertDogs([]Dog) []Animal ConvertCats([]Cat) []Animal } type Input struct { Animals InputAnimals } type InputAnimals struct { Cats []Cat Dogs []Dog } type Output struct { Animals []Animal } type Cat struct { Name string } type Dog struct { Name string } type Animal struct { Name string } func ConvertAnimals(c Converter, input InputAnimals) []Animal { dogs := c.ConvertDogs(input.Dogs) cats := c.ConvertCats(input.Cats) return append(dogs, cats...) } ```
Proposed approach
Variables marked with
goverter:func
would allow all configs that can be added to the conversion methods on interfaces. Global goverter flags likegoverter:extend
could be configured on the package declaration.When executing goverter on this input file, it could generate an init() method and set the generated implementation. The generated code for the example above could look like this:
Generated Code Example (click me)
```go func init() { ConvertInput = func(source *Input) *Output { var pStructsOutput *Output if source != nil { var structsOutput Output structsOutput.Animals = CombineAnimals((*source)) pStructsOutput = &structsOutput } return pStructsOutput } ConvertCats = func(source []Cat) []Animal { var structsAnimalList []Animal if source != nil { structsAnimalList = make([]Animal, len(source)) for i := 0; i < len(source); i++ { structsAnimalList[i] = structsCatToStructsAnimal(source[i]) } } return structsAnimalList } ConvertDogs = func(source []Dog) []Animal { var structsAnimalList []Animal if source != nil { structsAnimalList = make([]Animal, len(source)) for i := 0; i < len(source); i++ { structsAnimalList[i] = structsDogToStructsAnimal(source[i]) } } return structsAnimalList } } func structsCatToStructsAnimal(source Cat) Animal { var structsAnimal Animal structsAnimal.Name = source.Name return structsAnimal } func structsDogToStructsAnimal(source Dog) Animal { var structsAnimal Animal structsAnimal.Name = source.Name return structsAnimal } ```
Testable version available: https://github.com/jmattheis/goverter/issues/77#issuecomment-1892598584
Please :+1: this issue if you want this functionality. If you have a specific use-case in mind, feel free to comment it.