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

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!"))

三、原理说明

  1. 无鉴别器解码:利用Decoder.or方法,先尝试解码ThingOneSpec,失败后自动切换到ThingTwoSpec。由于两个子类字段完全不重叠,不会出现解码歧义。
  2. Schema生成:Schema.oneOf直接定义所有子Schema,生成的OpenAPI规范会包含完整的oneOf数组,Redoc会自动渲染所有分支的请求体结构。
  3. 无额外字段暴露:移除kind字段后,终端用户的请求和响应中不会出现该鉴别字段,实现完全透明的多负载支持。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 19:31:23