tapir端点密封层次结构Redoc渲染异常及无鉴别器实现问询
解决方案
一、修复Redoc请求体显示不全问题
你的Redoc只展示ThingOneSpec的核心原因是当前Schema.oneOfUsingField生成的OpenAPI结构,Redoc默认仅渲染第一个分支详情。要让所有负载类型都显示,需调整ThingSpec的Schema定义,改用无鉴别器的oneOf结构,同时移除暴露给用户的kind字段。
二、无显式鉴别器的完整实现
以下是修改后的代码,既移除了kind字段,又确保Redoc能展示所有请求体类型:
1. 核心ADT定义(移除kind)
import io.circe.{Decoder, Encoder, derivation} import io.circe.derivation.{deriveDecoder, deriveEncoder} import sttp.tapir.Schema import sttp.tapir.generic.Configuration import io.circe.syntax.EncoderOps // 移除kind方法,不暴露给终端用户 sealed trait ThingSpec object ThingSpec { implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames // 无鉴别器解码:先尝试ThingOneSpec,失败则尝试ThingTwoSpec(字段不重叠无歧义) implicit val decoder: Decoder[ThingSpec] = Decoder[ThingOneSpec].widen or Decoder[ThingTwoSpec].widen // 编码时直接序列化子类,不带额外鉴别字段 implicit val encoder: Encoder[ThingSpec] = Encoder.instance { case one: ThingOneSpec => one.asJson case two: ThingTwoSpec => two.asJson } // 定义oneOf Schema,包含所有子类的Schema,让OpenAPI生成完整分支结构 implicit val schema: Schema[ThingSpec] = Schema.oneOf( "ThingOneSpec" -> ThingOneSpec.schema, "ThingTwoSpec" -> ThingTwoSpec.schema ) }
2. 子类实现(保持原有字段,移除kind)
case class ThingOneSpec( name: String, age: Long ) extends ThingSpec object ThingOneSpec { implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames implicit val encoder: Encoder[ThingOneSpec] = deriveEncoder(derivation.renaming.snakeCase) implicit val decoder: Decoder[ThingOneSpec] = deriveDecoder(derivation.renaming.snakeCase) implicit val schema: Schema[ThingOneSpec] = Schema.derived } case class ThingTwoSpec( height: Long, weight: Long ) extends ThingSpec object ThingTwoSpec { implicit val config: Configuration = Configuration.default.withSnakeCaseMemberNames implicit val encoder: Encoder[ThingTwoSpec] = deriveEncoder(derivation.renaming.snakeCase) implicit val decoder: Decoder[ThingTwoSpec] = deriveDecoder(derivation.renaming.snakeCase) implicit val schema: Schema[ThingTwoSpec] = Schema.derived }
3. 端点定义保持不变
endpoint .post .in(jsonBody[ThingSpec].description("Specification of the thing")) .out(jsonBody[Thing].description("Thing!"))
三、原理说明
- 无鉴别器解码:利用
Decoder.or方法,先尝试解码ThingOneSpec,失败后自动切换到ThingTwoSpec。由于两个子类字段完全不重叠,不会出现解码歧义。 - Schema生成:
Schema.oneOf直接定义所有子Schema,生成的OpenAPI规范会包含完整的oneOf数组,Redoc会自动渲染所有分支的请求体结构。 - 无额外字段暴露:移除
kind字段后,终端用户的请求和响应中不会出现该鉴别字段,实现完全透明的多负载支持。
内容的提问来源于stack exchange,提问作者user3468054
相关产品推荐
相关产品推荐

