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

Http4s中自定义Refined类型校验错误响应的实现方法

Http4s自定义Refined校验错误返回方案

问题背景

  • 项目中使用refined库对Http4s接口的请求参数做约束校验,保证请求体符合规范
  • 示例请求样例类定义:
case class NewGenreRequest(
  name: GenreNameParam,
  parent: Option[GenreParentParam]
)
  • 字段对应的Refined类型定义:
type GenreNameString = String Refined MinSize[3]
  • 当传入不符合规则的参数(比如长度为2的字符串作为name值)时,接口默认返回的错误信息为Predicate taking size(me) = 2 failed: Predicate (2 < 3) did not fail.: DownField(name),这类信息包含内部实现细节,不适合直接返回给客户端。
  • 最初尝试通过middleware层修改BadRequest响应实现自定义返回,代码框架如下:
def handleErr(resp: Response[F]): Response[F] =
  resp match {
    case Status.BadRequest(resp) =>
      logger.info("HERE IS MY ERR")
      resp.???? 
    case _ =>
      resp
  }
  • 实现卡点:Http4s响应体为fs2 Stream类型,直接通过map操作处理响应体无法正常提取错误字段信息,也难以高效改写错误内容。

最优实现方案

不要在middleware层解析已经序列化完成的响应流,这类操作需要把整个响应流拉取到内存做字符串匹配,性能差且容易因为依赖版本升级导致错误格式变化、匹配逻辑失效。直接在请求解码阶段拦截Refined校验错误、替换为自定义友好提示即可,有两种常用实现路径:

1. 自定义Refined的JSON解码器(最简便,推荐)

如果项目使用circe做JSON序列化,直接隐式替换默认的refined编解码器,在校验失败时直接生成结构化的可读错误:

import io.circe._
import eu.timepit.refined.api._
import eu.timepit.refined.collection.MinSize

implicit def refinedDecoder[T, P](implicit
  underlying: Decoder[T],
  validate: Validate[T, P]
): Decoder[T Refined P] = (c: HCursor) => {
  underlying(c).flatMap { value =>
    RefType.applyRef[T Refined P](value) match {
      case Right(refinedValue) => Right(refinedValue)
      case Left(_) =>
        // 按校验规则匹配生成用户可读提示
        val friendlyMsg = validate.showExpr(value) match {
          case s if s.contains("MinSize") => 
            val minSize = s.split("<").last.trim.toInt
            s"字段长度不能少于${minSize}位"
          // 其余校验规则的提示按需补充
          case _ => "参数格式不符合要求"
        }
        Left(DecodingFailure(friendlyMsg, c.history))
    }
  }
}

替换后解码阶段抛出的错误会自动携带自定义提示,只需要在全局错误处理层统一把解码错误组装成接口约定的响应格式即可,不需要额外处理响应流。

2. 自定义请求EntityDecoder

如果不想修改全局的circe隐式实例,可以直接自定义请求体解码器,在解码层拦截Refined相关错误替换提示:

import org.http4s._
import org.http4s.circe._

def customJsonDecoder[A: Decoder]: EntityDecoder[F, A] =
  accumulatingJsonDecoder[F, A].adaptError { err =>
    err.getCause match {
      case e: IllegalArgumentException if e.getMessage.contains("Predicate taking") =>
        // 按需提取字段、规则信息,组装自定义错误
        new IllegalArgumentException("请求参数校验不通过,请检查输入后重试", e)
      case _ => err
    }
  }

路由中使用该解码器替代默认的jsonOf即可生效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 01:25:05