You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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
}

之前的尝试及错误

  1. 尝试定义带类型约束的接口:
type ResponsePhone interface {
  ResponsePhoneExist | ResponsePhoneNoExist | ResponsePhoneInArchive
}

报错:

cannot use type ResponsePhone outside a type constraint: interface contains type constraints

  1. 尝试泛型结构体:
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标签,让文档生成工具识别这是多类型响应。

  1. 定义统一的响应类型:
// 使用空接口,并通过schema标签指定oneOf的引用
type PhoneResponse interface{} `schema:"oneOf=ResponsePhoneExist,ResponsePhoneNoExist,ResponsePhoneInArchive"`
  1. 修改处理函数:
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)使用,你可以先定义一个基础结构体,然后让所有响应结构体嵌入它,再通过判别字段区分类型。

  1. 定义基础结构体和判别字段:
// 基础响应结构体,包含公共字段和判别字段
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"`
  1. 处理函数修改同方案一,返回对应的结构体实例即可。这种方式更符合OpenAPI的最佳实践,自动生成的文档会包含判别器信息,更易读。

关键注意事项

  • 确保你的usecase库支持通过结构体标签识别oneOf配置(大部分主流OpenAPI生成库如ogen、go-swagger都支持)。
  • 不要直接用泛型类型作为usecase.NewInteractor的参数,因为Interactor通常是一个非泛型接口,需要明确的类型来推导Schema。

内容的提问来源于stack exchange,提问作者Alexandr Kamenev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 04:15:50