I would like to use a setter on several methods of AdminAPI such as Update
. To do so I created a method type that could match with other methods.
Should I use interface instead of func type
?
type AdminAPI struct {
}
type ToAdminCtx func(ctx context.Context, req interface{}) (interface{}, error)
func (a AdminAPI) AdminM2MSetter(s ToAdminCtx) ToAdminCtx {
return func(ctx context.Context, arg interface{}) (interface{}, error) {
m2mPrincipal, _ := a.GetM2MPrincipal(ctx)
ctxM2M := extlib.SetPrincipal(ctx, m2mPrincipal)
return s(ctxM2M, arg)
}
}
func (a AdminAPI) Update(ctx context.Context, req *ReqType) (RespType, error) {}
updateWithAdminCtx := a.adminAPI.AdminM2MSetter(s.adminAPI.Update)
// ERROR => cannot use s.adminAPI.Update (value of type func(ctx
// context.Context, req *ReqType) (RespType, error)) as grpcAdmin.ToGetAdminCtx value in
// argument to s.adminAPI.AdminM2MSetter
_, err := updateWithAdminCtx(ctx context.Context, req *ReqType)
The error you're getting is pretty self-explanatory I think:
a.adminAPI.AdminM2MSetter(s.adminAPI.Update)
This is calling
func (a AdminAPI) AdminM2MSetter(s ToAdminCtx) ToAdminCtx {
Passing in s.adminAPI.Update
as the argument, which is expected to be of the type ToAdminCtx
. That type you is defined as:
type ToAdminCtx func(ctx context.Context, req interface{}) (interface{}, error)
Yet your Update
function's second argument is a *ReqType
, and its first return value is a RespType
value, and therefore Update
is not a ToAdminCtx
. The ToAdminCtx
function type is a function that can be called with a context and literally ANY type. Your Update
function cannot be guaranteed to work in all cases the ToAdminCtx
function can.
What you're looking for is a way to "wrap" any function, add do some work on the ctx
argument (probably setting some value), and then pass on the call. Before go 1.19, we did this by adding some kind of wrapper types like this:
type Wrapper struct {
UpdateReqType *ReqType
AnotherType *ReqType2 // for some other call you want to wrap
}
The change all the relevant functions, like your Update
function to take the wrapper argument type:
func (a AdminAPI) Update(ctx context.Context, req Wrapper) (Resp, error) {
realReq := req.UpdateReqType // get the actual request used here
}
The response types would either be similarly wrapped and/or composed.
Now, though, go supports generics, and this is a situation where they can be quite useful, let's change your AdminM2MSetter
function to something like this:
func AdminM2MSetter[T any, R any](s func(context.Context, T) (R, error)) func(context.Context, T) (R, error) {
return func (ctx context.Context, arg T) (R, error) {
m2mPrincipal, _ := a.GetM2MPrincipal(ctx)
ctxM2M := extlib.SetPrincipal(ctx, m2mPrincipal)
return s(ctxM2M, arg)
}
}
This way, we only have to define this function once, but rely on the compiler to generate a tailor-made function for all the types we need. In case of your Update
function, we'd do something like this:
a.adminAPI.AdminM2MSetter[*ReqType, RespType](s.adminAPI.Update)
Essentially replacing the generic T
and R
types with the specific types used by your Update
function. Because I don't really know what functions you want to wrap in this way, I used T any, R any
, but because it looks to me like you're trying to wrap request handlers of some sort, you could create your own constraints:
type Requests interface {
*ReqType1 | *ReqType2 | *ReqType3 // and so on
}
type Responses interface {
Resp1 | Resp2 | Resp3
}
And just replace [T any, R any]
with [T Requests, R Responses]