如何用usecase.Interactor在Go服务中定义多类型响应生成OpenAPI3文档
问题:Go Web服务中通过usecase.Interactor生成支持oneOf的OpenAPI文档
我正在用Go编写Web服务,通过usecase.Interactor自动生成OpenAPI 3标准文档,现在需要让接口输出支持多种不同响应类型,符合OpenAPI Schema里的oneOf定义。
目标OpenAPI Schema片段
... "/phonenumber/get/" : { "post" : { "operationId" : "getInfo", "requestBody" : { "required" : true, "content" : { "application/json" : { "schema" : { "$ref" : "#/components/schemas/RequestInfo" } } } }, "responses" : { "200" : { "content" : { "application/json" : { "schema" : { "oneOf" : [ { "$ref" : "#/components/schemas/ResponsePhoneExist" }, { "$ref" : "#/components/schemas/ResponsePhoneNoExist" }, { "$ref" : "#/components/schemas/ResponsePhoneInArchive" } ] } } } }, ...
Go中定义的响应结构体
type ResponsePhoneExist struct { Result string `json:"result,omitempty" required:"true" enum:"ok" example:"ok"` Info string `json:"info,omitempty" required:"true" enum:"exist" example:"exist"` Phonenumber string `json:"phonenumber,omitempty" required:"true" minLength:"11" maxLength:"11" example:"79146764408"` Count int `json:"count,omitempty" required:"true" minimum:"0" maximum:"3" example:"1"` Activated string `json:"activated,omitempty" required:"true" example:"27.11.2022"` } type ResponsePhoneNoExist struct { Result string `json:"result,omitempty" required:"true" enum:"ok" example:"ok"` Info string `json:"info,omitempty" required:"true" enum:"no exist" example:"no exist"` Phonenumber string `json:"phonenumber,omitempty" required:"true" minLength:"11" maxLength:"11" example:"79146764408"` } type ResponsePhoneInArchive struct { Result string `json:"result,omitempty" required:"true" enum:"ok" example:"ok"` Info string `json:"info,omitempty" required:"true" enum:"archive" example:"archive"` Phonenumber string `json:"phonenumber,omitempty" required:"true" minLength:"11" maxLength:"11" example:"79146764408"` }
当前代码疑问
在定义接口处理函数时,不知道该用什么类型替换???,才能让自动生成的文档符合oneOf要求:
func main() { service := web.DefaultService() ... service.Docs("/docs", v4emb.New) service.Post("/phonenumber/get", getPhoneInfo()) err := http.ListenAndServe(":3400", service) ... } func getPhoneInfo() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, request RequestInfo, response ???) error { *response, err = getInfoIn1C(request.PhoneNumber) if err != nil { return status.Wrap(errors.New(err.Error()), status.Internal) } return nil }) u.SetName("getInfo") u.SetExpectedErrors(status.Unauthenticated, status.InvalidArgument, status.Internal) return u } func getInfoIn1C(tel string) (???, error) { var out ??? var err error ... return out, err }
之前的尝试及错误
- 尝试定义带类型约束的接口:
type ResponsePhone interface { ResponsePhoneExist | ResponsePhoneNoExist | ResponsePhoneInArchive }
报错:
cannot use type ResponsePhone outside a type constraint: interface contains type constraints
- 尝试泛型结构体:
type ResponsePhoneConstraint interface { ResponsePhoneExist | ResponsePhoneNoExist | ResponsePhoneInArchive } type ResponsePhone[T ResponsePhoneConstraint] struct { Response T }
报错:
cannot use generic type ResponsePhone[T ResponsePhoneConstraint] without instantiation.
解决方案
思路说明
Go的泛型类型约束不能直接作为普通接口使用,而usecase.Interactor通常需要明确的类型来生成OpenAPI文档。要实现oneOf的多响应类型,有两种可行方案:
方案一:使用空接口+自定义Schema标签
通过定义一个空接口类型,并给它添加指定oneOf引用的Schema标签,让文档生成工具识别这是多类型响应。
- 定义统一的响应类型:
// 使用空接口,并通过schema标签指定oneOf的引用 type PhoneResponse interface{} `schema:"oneOf=ResponsePhoneExist,ResponsePhoneNoExist,ResponsePhoneInArchive"`
- 修改处理函数:
func getPhoneInfo() usecase.Interactor { u := usecase.NewInteractor(func(ctx context.Context, request RequestInfo, response *PhoneResponse) error { res, err := getInfoIn1C(request.PhoneNumber) if err != nil { return status.Wrap(errors.New(err.Error()), status.Internal) } *response = res return nil }) u.SetName("getInfo") u.SetExpectedErrors(status.Unauthenticated, status.InvalidArgument, status.Internal) return u } func getInfoIn1C(tel string) (interface{}, error) { // 根据业务逻辑返回对应的结构体实例 if /* 号码存在逻辑 */ { return ResponsePhoneExist{ Result: "ok", Info: "exist", Phonenumber: tel, Count: 1, Activated: "27.11.2022", }, nil } else if /* 号码归档逻辑 */ { return ResponsePhoneInArchive{ Result: "ok", Info: "archive", Phonenumber: tel, }, nil } else { return ResponsePhoneNoExist{ Result: "ok", Info: "no exist", Phonenumber: tel, }, nil } }
方案二:利用结构体嵌入+判别字段(更符合OpenAPI oneOf规范)
OpenAPI的oneOf通常建议配合判别字段(discriminator)使用,你可以先定义一个基础结构体,然后让所有响应结构体嵌入它,再通过判别字段区分类型。
- 定义基础结构体和判别字段:
// 基础响应结构体,包含公共字段和判别字段 type BasePhoneResponse struct { Result string `json:"result" required:"true" enum:"ok" example:"ok"` Info string `json:"info" required:"true" example:"exist"` // 用Info作为判别字段 Phonenumber string `json:"phonenumber" required:"true" minLength:"11" maxLength:"11" example:"79146764408"` } // 嵌入基础结构体,保留各自的独有字段 type ResponsePhoneExist struct { BasePhoneResponse Count int `json:"count,omitempty" required:"true" minimum:"0" maximum:"3" example:"1"` Activated string `json:"activated,omitempty" required:"true" example:"27.11.2022"` } type ResponsePhoneNoExist struct { BasePhoneResponse } type ResponsePhoneInArchive struct { BasePhoneResponse } // 定义统一响应类型,通过schema标签指定oneOf和判别器 type PhoneResponse interface{} `schema:"oneOf=ResponsePhoneExist,ResponsePhoneNoExist,ResponsePhoneInArchive,discriminator=Info"`
- 处理函数修改同方案一,返回对应的结构体实例即可。这种方式更符合OpenAPI的最佳实践,自动生成的文档会包含判别器信息,更易读。
关键注意事项
- 确保你的
usecase库支持通过结构体标签识别oneOf配置(大部分主流OpenAPI生成库如ogen、go-swagger都支持)。 - 不要直接用泛型类型作为
usecase.NewInteractor的参数,因为Interactor通常是一个非泛型接口,需要明确的类型来推导Schema。
内容的提问来源于stack exchange,提问作者Alexandr Kamenev
相关产品推荐
相关产品推荐

