Skip to content

Commit

Permalink
Generics support (#4)
Browse files Browse the repository at this point in the history
* generics support

* bump golangci-lint to 1.54

* address comments

* ok i see why ElementsMatch
  • Loading branch information
sgtsquiggs authored Jan 31, 2024
1 parent 36dac2e commit eba1a29
Show file tree
Hide file tree
Showing 17 changed files with 521 additions and 188 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Run linters
uses: golangci/golangci-lint-action@v2
with:
version: v1.52
version: v1.54

test:
strategy:
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/clear-street/reinforcer
go 1.20

require (
github.com/dave/jennifer v1.4.1
github.com/dave/jennifer v1.7.0
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/errors v0.9.1
github.com/rs/zerolog v1.29.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/dave/jennifer v1.4.1 h1:XyqG6cn5RQsTj3qlWQTKlRGAyrTcsk1kUmWdZBzRjDw=
github.com/dave/jennifer v1.4.1/go.mod h1:7jEdnm+qBcxl8PC0zyp7vxcpSRnzXSt9r39tpTVGlwA=
github.com/dave/jennifer v1.7.0 h1:uRbSBH9UTS64yXbh4FrMHfgfY762RD+C7bUPKODpSJE=
github.com/dave/jennifer v1.7.0/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down
2 changes: 1 addition & 1 deletion internal/generator/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func createFileConfigs(discoveredSet map[string]struct{}, match map[string]*load
return nil, errors.Errorf("multiple types with same name discovered with name %s", typName)
}
discoveredSet[typName] = struct{}{}
cfg = append(cfg, generator.NewFileConfig(typName, typName, res.Methods))
cfg = append(cfg, generator.NewFileConfig(typName, typName, res.TypeParams, res.TypeArgs, res.Methods))
}
return cfg, nil
}
28 changes: 17 additions & 11 deletions internal/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,16 @@ type FileConfig struct {
srcTypeName string
// outTypeName is the desired output type name
outTypeName string
// typeParams is the list of generic type parameters
typeParams []jen.Code
// typeArgs is the list of generic type arguments
typeArgs []jen.Code
// methods that should be in the generated type
methods []*method.Method
}

// NewFileConfig creates a new instance of the FileConfig which holds code generation configuration
func NewFileConfig(srcTypeName, outTypeName string, methods []*method.Method) *FileConfig {
func NewFileConfig(srcTypeName, outTypeName string, typeParams []jen.Code, typeArgs []jen.Code, methods []*method.Method) *FileConfig {
// cannot use cases.Title as it will lowercase MyService to Myservice
if len(srcTypeName) > 0 {
srcTypeName = strings.ToUpper(string(srcTypeName[0])) + srcTypeName[1:]
Expand All @@ -37,6 +41,8 @@ func NewFileConfig(srcTypeName, outTypeName string, methods []*method.Method) *F
return &FileConfig{
srcTypeName: srcTypeName,
outTypeName: outTypeName,
typeParams: typeParams,
typeArgs: typeArgs,
methods: methods,
}
}
Expand Down Expand Up @@ -136,22 +142,22 @@ func generateFile(outPkg string, ignoreNoReturnMethods bool, fileCfg *FileConfig
for _, meth := range methods {
declMethods = append(declMethods, jen.Id(meth.Name).Params(meth.ParametersNameAndType...).Params(meth.ReturnTypes...))
}
f.Add(jen.Type().Id(fileCfg.targetName()).Interface(
f.Add(jen.Type().Id(fileCfg.targetName()).Types(fileCfg.typeParams...).Interface(
declMethods...,
))

// Declare the proxy implementation
f.Add(jen.Type().Id(fileCfg.outTypeName).Struct(
f.Add(jen.Type().Id(fileCfg.outTypeName).Types(fileCfg.typeParams...).Struct(
jen.Op("*").Id("base"),
jen.Id("delegate").Id(fileCfg.targetName()),
jen.Id("delegate").Id(fileCfg.targetName()).Types(fileCfg.typeArgs...),
))

// Declare the ctor
f.Add(jen.Func().Id("New"+fileCfg.outTypeName).Params(
jen.Id("delegate").Id(fileCfg.targetName()),
f.Add(jen.Func().Id("New"+fileCfg.outTypeName).Types(fileCfg.typeParams...).Params(
jen.Id("delegate").Id(fileCfg.targetName()).Types(fileCfg.typeArgs...),
jen.Id("runnerFactory").Id("runnerFactory"),
jen.Id("options").Op("...").Id("Option"),
).Op("*").Id(fileCfg.outTypeName).Block(
).Op("*").Id(fileCfg.outTypeName).Types(fileCfg.typeArgs...).Block(
// if delegate == nil
jen.If(jen.Id("delegate").Op("==").Nil().Block(
// panic("...")
Expand All @@ -163,7 +169,7 @@ func generateFile(outPkg string, ignoreNoReturnMethods bool, fileCfg *FileConfig
jen.Panic(jen.Lit("provided nil runner factory")),
)),
// c:= &OutTypeName{...}
jen.Id("c").Op(":=").Add(jen.Op("&").Id(fileCfg.outTypeName).Values(jen.Dict{
jen.Id("c").Op(":=").Add(jen.Op("&").Id(fileCfg.outTypeName).Types(fileCfg.typeArgs...).Values(jen.Dict{
// embed the base struct
jen.Id("base"): jen.Op("&").Id("base").Values(jen.Dict{
jen.Id("errorPredicate"): jen.Id("RetryAllErrors"),
Expand All @@ -181,7 +187,7 @@ func generateFile(outPkg string, ignoreNoReturnMethods bool, fileCfg *FileConfig
// Declare all of our proxy methods
for _, mm := range methods {
if mm.ReturnsError {
r := retryable.NewRetryable(mm, fileCfg.outTypeName, fileCfg.receiverName())
r := retryable.NewRetryable(mm, fileCfg.outTypeName, fileCfg.typeArgs, fileCfg.receiverName())
s, err := r.Statement()
if err != nil {
return "", err
Expand All @@ -190,9 +196,9 @@ func generateFile(outPkg string, ignoreNoReturnMethods bool, fileCfg *FileConfig
} else {
var p statement
if ignoreNoReturnMethods {
p = passthrough.NewPassThrough(mm, fileCfg.outTypeName, fileCfg.receiverName())
p = passthrough.NewPassThrough(mm, fileCfg.outTypeName, fileCfg.typeArgs, fileCfg.receiverName())
} else {
p = noret.NewNoReturn(mm, fileCfg.outTypeName, fileCfg.receiverName())
p = noret.NewNoReturn(mm, fileCfg.outTypeName, fileCfg.typeArgs, fileCfg.receiverName())
}
s, err := p.Statement()
if err != nil {
Expand Down
124 changes: 124 additions & 0 deletions internal/generator/generator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,128 @@ func (g *GeneratedService) SayHello(arg0 string) error {
}
return err
}
`,
},
},
},
},
{
name: "Generic Type Parameters",
ignoreNoReturnMethods: true,
inputs: map[string]input{
"users_service.go": {
interfaceName: "Service",
code: `package fake
type Service[T any] interface {
SayHello(name T) error
DoNothing()
}
`,
},
},
outCode: &generator.Generated{
Common: `// Code generated by reinforcer, DO NOT EDIT.
package resilient
import (
"context"
goresilience "github.com/slok/goresilience"
)
type base struct {
errorPredicate func(string, error) bool
runnerFactory runnerFactory
}
type runnerFactory interface {
GetRunner(name string) goresilience.Runner
}
var RetryAllErrors = func(_ string, _ error) bool {
return true
}
type Option func(*base)
func WithRetryableErrorPredicate(fn func(string, error) bool) Option {
return func(o *base) {
o.errorPredicate = fn
}
}
func (b *base) run(ctx context.Context, name string, fn func(ctx context.Context) error) error {
return b.runnerFactory.GetRunner(name).Run(ctx, fn)
}
`,
Constants: `// Code generated by reinforcer, DO NOT EDIT.
package resilient
// GeneratedServiceMethods are the methods in GeneratedService
var GeneratedServiceMethods = struct {
DoNothing string
SayHello string
}{
DoNothing: "DoNothing",
SayHello: "SayHello",
}
`,
Files: []*generator.GeneratedFile{
{
TypeName: "GeneratedService",
Contents: `// Code generated by reinforcer, DO NOT EDIT.
package resilient
import "context"
type targetService[T any] interface {
DoNothing()
SayHello(arg0 T) error
}
type GeneratedService[T any] struct {
*base
delegate targetService[T]
}
func NewGeneratedService[T any](delegate targetService[T], runnerFactory runnerFactory, options ...Option) *GeneratedService[T] {
if delegate == nil {
panic("provided nil delegate")
}
if runnerFactory == nil {
panic("provided nil runner factory")
}
c := &GeneratedService[T]{
base: &base{
errorPredicate: RetryAllErrors,
runnerFactory: runnerFactory,
},
delegate: delegate,
}
for _, o := range options {
o(c.base)
}
return c
}
func (g *GeneratedService[T]) DoNothing() {
g.delegate.DoNothing()
}
func (g *GeneratedService[T]) SayHello(arg0 T) error {
var nonRetryableErr error
err := g.run(context.Background(), GeneratedServiceMethods.SayHello, func(_ context.Context) error {
var err error
err = g.delegate.SayHello(arg0)
if g.errorPredicate(GeneratedServiceMethods.SayHello, err) {
return err
}
nonRetryableErr = err
return nil
})
if nonRetryableErr != nil {
return nonRetryableErr
}
return err
}
`,
},
},
Expand Down Expand Up @@ -947,6 +1069,8 @@ func loadInterface(t *testing.T, filesCode map[string]input) []*generator.FileCo
}
loadedTypes = append(loadedTypes, generator.NewFileConfig(in.interfaceName,
fmt.Sprintf("Generated%s", in.interfaceName),
svc.TypeParams,
svc.TypeArgs,
svc.Methods,
))
}
Expand Down
Loading

0 comments on commit eba1a29

Please sign in to comment.