英文:
Which arguments for Golang decorator functions
问题
我想在AdminAPI的几个方法(如Update
)上使用一个setter。为此,我创建了一个方法类型,可以与其他方法匹配。
我应该使用接口而不是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)
英文:
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)
答案1
得分: 1
你得到的错误信息已经很明显了:
a.adminAPI.AdminM2MSetter(s.adminAPI.Update)
这里调用了
func (a AdminAPI) AdminM2MSetter(s ToAdminCtx) ToAdminCtx {
将s.adminAPI.Update
作为参数传入,而它期望的类型是ToAdminCtx
。而你定义的ToAdminCtx
类型是这样的:
type ToAdminCtx func(ctx context.Context, req interface{}) (interface{}, error)
然而你的Update
函数的第二个参数是*ReqType
类型,第一个返回值是RespType
类型,因此Update
不是一个ToAdminCtx
类型。ToAdminCtx
函数类型是一个可以接受上下文和任意类型的函数。你的Update
函数不能保证在所有情况下都能正常工作。
你想要的是一种方法来“包装”任何函数,在ctx
参数上进行一些操作(可能是设置某个值),然后继续调用。在Go 1.19之前,我们可以通过添加某种包装类型来实现这一点,例如:
type Wrapper struct {
UpdateReqType *ReqType
AnotherType *ReqType2 // 用于包装其他要包装的调用
}
然后修改所有相关的函数,比如你的Update
函数,使其接受包装类型的参数:
func (a AdminAPI) Update(ctx context.Context, req Wrapper) (Resp, error) {
realReq := req.UpdateReqType // 获取实际使用的请求
}
响应类型可能也需要类似地进行包装和/或组合。
现在,Go支持泛型,这是一个可以非常有用的情况,让我们将你的AdminM2MSetter
函数改为这样:
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)
}
}
这样,我们只需要定义这个函数一次,然后依靠编译器为我们生成适合所有需要的类型的定制函数。对于你的Update
函数,我们可以这样做:
a.adminAPI.AdminM2MSetter[*ReqType, RespType](s.adminAPI.Update)
将泛型的T
和R
类型替换为Update
函数使用的具体类型。因为我不太清楚你想要以这种方式包装哪些函数,我使用了T any, R any
,但是根据我看到的情况,你似乎想要包装某种类型的请求处理程序,你可以创建自己的约束:
type Requests interface {
*ReqType1 | *ReqType2 | *ReqType3 // 以此类推
}
type Responses interface {
Resp1 | Resp2 | Resp3
}
然后将[T any, R any]
替换为[T Requests, R Responses]
。
英文:
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]
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论