t3code 依赖的 Effect 4.0:内置 HttpApiError 错误类实现 HttpServerRespondable,可在普通 HTTP 服务端直接返回
2026/9/13 18:41:43 网站建设 项目流程

t3code 依赖的 Effect 4.0:内置 HttpApiError 错误类实现 HttpServerRespondable,可在普通 HTTP 服务端直接返回

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

本文基于 t3code 仓库内置参考仓库.repos/effect-smol中的 changeset 变更记录eff-701-httpapierror-respondable展开:Effect 4.0 让全部内置HttpApiError错误类实现HttpServerRespondable协议,使这些错误值不再只能服务于HttpApi声明式框架,而是可以直接从普通 HTTP 服务端处理函数中返回,由运行时自动渲染为对应的状态码响应。读完本文,你能掌握该变更的具体内容、内置错误类与状态码的完整映射、Respondable协议的转换机制,以及它在普通 handler 错误中间件中的实际调用链。

变更记录本身:一段 changeset 描述了什么

关联文档是一份标准的 changesets 变更记录,位于 .repos/effect-smol/.changeset/pre/eff-701-httpapierror-respondable.md,全文只有 frontmatter 与一句变更描述:

--- "effect": patch --- Make all built-in `HttpApiError` classes implement `HttpServerRespondable`, so they can be returned directly from plain HTTP server handlers outside of `HttpApi`.

三个要点值得注意:

  1. 变更级别是patch:这是补丁级变更,说明它被视为行为增强而非破坏性修改——之前这些错误类在HttpApi体系内照常工作,之后它们"额外获得"了被普通服务端识别的能力;
  2. 位置在.changeset/pre/目录:changesets 工具在预发布(pre-release)模式下会把变更记录移入pre/子目录。结合 .repos/effect-smol/packages/effect/package.json 中version4.0.0-rc.112可以确认,该变更属于effect4.0.0预发布周期内的迭代。t3code 主仓库通过 patches/effect@4.0.0-rc.112.patch 锁定的正是同一版本线,说明本仓库消费的 Effect 运行时与这份变更记录来自同一发布序列;
  3. 变更目标是HttpApiError内置错误类:即400 Bad Request404 Not Found这类由框架统一提供的状态码错误,而不是用户自定义错误。

内置错误类全景:13 个错误类与状态码映射

变更后落地的实现位于 .repos/effect-smol/packages/effect/src/unstable/httpapi/HttpApiError.ts。该模块导出的内置错误类与 HTTP 状态码的完整对应关系如下,均可在源文件中直接验证:

错误类状态码说明ErrorReporter.ignore
BadRequest400请求错误
Unauthorized401未认证
Forbidden403无权限
NotFound404资源不存在
MethodNotAllowed405方法不允许
NotAcceptable406不可接受
RequestTimeout408请求超时
Conflict409冲突
Gone410资源已移除
UnprocessableEntity422语义错误
InternalServerError500服务器内部错误
NotImplemented501未实现
ServiceUnavailable503服务不可用

NotFound为例,源码结构(见 HttpApiError.ts)是:

export class NotFound extends Schema.Error<NotFound>("effect/HttpApiError/NotFound")({ _tag: Schema.tag("NotFound") }, { description: "NotFound", httpApiStatus: 404 }) { override readonly [ErrorReporter.ignore] = true [HttpServerRespondable.symbol]() { return Effect.succeed(notFoundResponse) } }

其中notFoundResponse是模块顶部预创建的HttpServerResponse.empty({ status: 404 })(HttpApiError.ts#L21-L33 为全部 13 个状态码各缓存了一个空响应实例,避免每次渲染都新建对象)。

从源码结构看,每个错误类承担了三重身份:

  • Schema.Error派生类:携带httpApiStatus元数据,可被 OpenAPI 生成、HttpApi端点/中间件的错误声明、反射与生成的客户端所理解——这是变更之前就存在的角色;
  • Respondable协议实现者:通过实现[HttpServerRespondable.symbol]()方法,声明自己应当渲染为哪个响应。这是本次 changeset 新增的能力;
  • 错误报告策略:所有 4xx 类设置了[ErrorReporter.ignore] = true(表示客户端错误不应触发全局错误上报),而InternalServerErrorNotImplementedServiceUnavailable三个 5xx 类没有该标记,意味着直接返回 5xx 错误类时仍会被错误报告器记录。这一区分与运维直觉一致:客户端错误是常态,服务器错误才需要告警。

*NoContent模式变体

每个状态码错误类还配套导出了一个NoContent模式变体,例如NotFoundNoContent(HttpApiError.ts#L152-L154):

export const NotFoundNoContent = NotFound.pipe(HttpApiSchema.asNoContent({ decode: () => new NotFound({}) }))

asNoContent变体把"空响应体"固化为契约的一部分:解码一个空响应会直接还原为对应错误值。这与Respondable协议方向正好互补——协议解决"服务端错误 → HTTP 响应"的编码方向,NoContent模式解决"HTTP 响应 → 错误值"的解码方向,两端都用同一个类型。

HttpApiSchemaError:请求/响应解包失败的专用错误

同一模块还定义了 HttpApiSchemaError,它不是状态码错误,而是由 HTTP API 运行时在请求组件解码失败时抛出的错误:

export class HttpApiSchemaError extends Data.TaggedClass("HttpApiSchemaError")<{ readonly kind: "Params" | "Headers" | "Query" | "Body" | "Payload" | "ResponseHeaders" readonly cause: Schema.SchemaError }> { ... [HttpServerRespondable.symbol]() { return Effect.succeed(badRequestResponse) } }

kind字段记录失败发生的组件位置(路径参数、请求头、查询串、请求体、负载或响应头),cause保存原始SchemaError;它提供static is()类型守卫与static wrap()帮助方法用于把SchemaError提升为带位置的包装错误。渲染上它固定为空的400 Bad Request。该错误类同样实现了Respondable协议,与 changeset 中"all built-in HttpApiError classes"的表述一致。

协议机制:HttpServerRespondable如何被运行时识别

协议本体定义在 .repos/effect-smol/packages/effect/src/unstable/http/HttpServerRespondable.ts,核心是一个字符串符号:

export const symbol = "~effect/http/HttpServerRespondable" export interface Respondable { [symbol](): Effect.Effect<HttpServerResponse, unknown> }

任何对象只要在自身携带该符号属性(即实现[symbol]()方法),就被视为"可响应"值。模块提供三个转换函数,构成完整的识别与降级逻辑:

toResponse:严格转换

toResponse(self)先检查值是否已经是HttpServerResponse(是则直接成功返回),否则调用self[symbol]()Effect.orDie包裹——即协议方法内部抛错会转成 defect。适合在类型系统已经保证值可响应时使用。

toResponseOrElse:带兜底的宽松转换

export const toResponseOrElse = (u: unknown, orElse: HttpServerResponse): Effect.Effect<HttpServerResponse> => { if (Response.isHttpServerResponse(u)) { return Effect.succeed(u) } else if (isRespondable(u)) { return Effect.catchCause(u[symbol](), () => Effect.succeed(orElse)) } else if (Schema.isSchemaError(u)) { return Effect.succeed(badRequest) } else if (Cause.isNoSuchElementError(u)) { return Effect.succeed(notFound) } return Effect.succeed(orElse) }

识别顺序是:已构造好的响应 >Respondable协议值(含变更后加入的全部HttpApiError类)>SchemaError(渲染为 400)>NoSuchElementError(渲染为 404)> 用户传入的兜底响应。协议值渲染失败时通过Effect.catchCause回退到orElse,保证任何情况下都有响应可发。

toResponseOrElseDefect:defect 通道的保守转换

处理 defect(非预期崩溃)时只识别响应值与Respondable值两类,其余一律回退到orElse——不猜测 defect 应该映射到什么状态码,符合"未预期错误保守处理"的原则。

实际调用链:普通 handler 的错误为何能被自动渲染

这条能力在普通 HTTP 服务端的落点是错误中间件。.repos/effect-smol/packages/effect/src/unstable/http/HttpServerError.ts 中的处理逻辑展示了运行时如何消费该协议:

// 类型化错误通道 effect = Respondable.toResponseOrElse(reason.error, internalServerError) // defect 通道 effect = Respondable.toResponseOrElseDefect(reason.defect, internalServerError)

两处都把InternalServerError空响应作为兜底参数传入。结合toResponseOrElse的分支逻辑,可以还原出普通 handler 中抛错的完整决策表:

handler 中产生的值最终响应
HttpServerResponse实例原样发送
NotFound等任一内置HttpApiError该错误的空状态码响应(如 404)
HttpApiSchemaError空 400
SchemaError(裸的模式错误)空 400
NoSuchElementError空 404
其他类型化错误 / defect兜底 500

这正是 changeset 描述"returned directly from plain HTTP server handlers outside ofHttpApi"的运行时含义:在HttpApp/HttpServer这类普通路由服务中,业务代码把new NotFound({})作为错误抛出或返回,错误中间件就会经由toResponseOrElse将其渲染为 400/404 等对应响应,无需先手工构造HttpServerResponse,也无需HttpApi的端点声明机制参与。

变更的实际影响与适用前提

从本次变更记录与配套源码可以归纳出三点实际影响:

  1. 普通服务端的错误表达成本下降。此前在HttpApi之外的普通 handler 中,要让某个状态码返回给客户端,通常需要显式构造HttpServerResponse.empty({ status: 404 })之类的响应对象或自定义携带渲染逻辑的错误类型。变更后,13 个内置状态码错误类加HttpApiSchemaError均可直接作为错误值使用,由中间件统一渲染;
  2. 错误语义与上报语义保持一致。4xx 错误类携带ErrorReporter.ignore标记,直接抛Unauthorized不会触发全局错误报告,而抛InternalServerError会——即使你是在普通 handler 里"正常地"返回 5xx;
  3. 协议面向扩展开放Respondable是一个开放接口,任何自定义服务端错误都可以通过实现[HttpServerRespondable.symbol]()获得同样的"直接返回即渲染"能力,HttpApiError内置类只是该协议的首批内置实现者。

适用前提需要说明:这些模块位于effect包的unstable/httpunstable/httpapi子模块下(源文件路径为packages/effect/src/unstable/http...),对应effect@4.0.0-rc.112预发布版本;@since标注均为4.0.0,即该能力以 4.0 稳定版为基线。t3code 主仓库通过 patches/effect@4.0.0-rc.112.patch 与 patches/@effect__vitest@4.0.0-rc.112.patch 锁定同一版本线,引用本仓库时以.repos/effect-smol内实际源码为准。

关键路径索引

文件内容
.repos/effect-smol/.changeset/pre/eff-701-httpapierror-respondable.md本文所依据的 changeset 变更记录
.repos/effect-smol/packages/effect/src/unstable/httpapi/HttpApiError.ts13 个内置状态码错误类、*NoContent变体与HttpApiSchemaError的实现
.repos/effect-smol/packages/effect/src/unstable/http/HttpServerRespondable.tsRespondable协议符号、isRespondable守卫与三个转换函数
.repos/effect-smol/packages/effect/src/unstable/http/HttpServerError.ts错误中间件消费协议的调用点(toResponseOrElse/toResponseOrElseDefect
.repos/effect-smol/packages/effect/package.jsoneffect包版本4.0.0-rc.112,确认变更所属版本线

【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询