Proposal: Allow exported functions with receivers other than *Service
eriktate opened this issue · 0 comments
Suppose you have a handlers.go
file that looks something like this:
package handlers
import (
"context"
"database/sql"
pb "github.com/eriktate/thing"
)
// NewService returns a naïve, stateless implementation of Service.
func NewService() pb.ContextServer {
db, err := connectToProdDB()
if err != nil {
panic("Failed to connect to database")
}
return BuildService(db)
}
// BuildService returns a dependency injected thingService. Useful for unit testing.
func BuildService(db *sql.DB) thingService {
return thingService{db: db}
}
type thingService struct {
db *sql.DB
}
// DoThing does a thing with the sql.DB pointer housed in thingService.
func (s thingService) DoThing(ctx context.Context, in *pb.ThingPayload) (*pb.ThingResponse, error) {
return doSomethingWithDatbase(s.db)
}
This type of pattern doesn't seem to be possible with truss at the moment, as the BuildService
function will be stripped on the next truss run. A workaround might be to add a file in the same package and define the BuildService
function there, but it feels like it should be included with the service definition.
A solution I've found is a slight change to the pruneDecls
function in gengokit/handlers/handlers.go
:
func (m methodMap) pruneDecls(decls []ast.Decl, svcName string, allowExports bool) []ast.Decl {
var newDecls []ast.Decl
for _, d := range decls {
switch x := d.(type) {
case *ast.FuncDecl:
name := x.Name.Name
// Special case NewService and ignore unexported
if name == ignoredFunc || !ast.IsExported(name) {
log.WithField("Func", name).
Debug("Ignoring")
newDecls = append(newDecls, x)
continue
}
if ok := isValidFunc(x, m, svcName); ok == true {
updateParams(x, m[name])
updateResults(x, m[name])
newDecls = append(newDecls, x)
delete(m, name)
} else if allowExports {
if ok := isIgnored(x, m, svcName); ok == true {
newDecls = append(newDecls, x)
}
}
default:
newDecls = append(newDecls, d)
}
}
return newDecls
}
// potential new function
func isIgnored(f *ast.FuncDecl, m methodMap, svcName string) bool {
name := f.Name.String()
rName := recvTypeToString(f.Recv)
if rName != svcName+"Service" {
log.WithField("Func", name).WithField("Receiver", rName).
Info("Exported func is not on service receiver; ignoring")
return true
}
return false
}
The allowExports
bool in the function signature would allow for this functionality to be placed behind a flag. This change just checks if the exported function declaration has the service struct as a receiver. If it DOES have the service struct as a receiver than it will be handled normally. If it DOESN'T have the service struct as a receiver, than it will ignore that function when pruning.
Thoughts about adding this sort of functionality? Or the proposed solution? If the desired workflow for something like this is to use a different file than handlers.go
that's fine too, just wondering if anyone else would benefit from something like this.