Encore Go 请求验证(Validation)实战指南:通过 `Validate() error` 自动校验 API 入参

Encore Go 请求验证(Validation)实战指南:通过 `Validate() error` 自动校验 API 入参 Encore Go 请求验证Validation实战指南通过Validate() error自动校验 API 入参【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore接收外部请求时校验请求负载、确保字段完整与格式正确是后端 API 的最佳实践。Encore 在 Go 运行时内置了一套零配置的请求验证机制只要请求类型实现了Validate() error方法Encore 框架便会在反序列化完成后自动调用它并在校验通过后才进入你的 API handler 与其他中间件。本文基于官方文档 docs/go/develop/validation.md并结合仓库源码深入讲解这一内置中间件的工作机制、错误映射规则以及如何在你的 Encore Go 服务中落地一套可扩展的验证方案。验证机制的核心设计接口驱动的验证约定Encore 采用了一个非常轻量的约定请求类型只要实现了Validate() error方法框架就自动启用请求校验。在运行时层这个约定被定义为一个名为Validator的 Go 接口// runtimes/go/appruntime/apisdk/api/middleware.go type Validator interface { Validate() error }也就是说你并不需要注册任何东西也不需要继承某个基类。你的请求 struct 天然满足该接口即可。例如type CreateUserRequest struct { Email string Username string Password string } func (req *CreateUserRequest) Validate() error { if req.Email { return errors.New(email is required) } if len(req.Password) 8 { return errors.New(password must be at least 8 characters) } return nil } //encore:api public func CreateUser(ctx context.Context, req *CreateUserRequest) error { // 只有校验通过后请求才会到达这里 return nil }注意Validate() error的接收者既可以是值类型也可以是指针类型只要该方法集合能被请求类型或其指针覆盖即可。仓库的 e2e 测试用例见下文使用指针接收者func (req *Request) Validate() error这也是最常见写法。校验发生的时机Encore 的验证发生在请求处理管线的反序列化之后、业务 handler 与中间件执行之前。具体调用链位于 runtimes/go/appruntime/apisdk/api/handler.goDesc.Handle入口→ 请求鉴权检查 →Desc.begin反序列化请求体并开始追踪记录Desc.handleIncoming中第一步调用d.validate(reqData)// runtimes/go/appruntime/apisdk/api/handler.go#L304-L307 func (d *Desc[Req, Resp]) handleIncoming(c IncomingContext, reqData Req) (resp *model.Response, respData Resp) { if err : d.validate(reqData); err ! nil { return newErrResp(err, 0), respData }校验通过后才进入executeEndpoint依次执行全局中间件、服务中间件最后到达你的 handler。这一点与官方文档的描述完全一致only call your API handler (and other middleware) if the validation function returnsnil。换言之校验失败时业务 handler 和所有用户自定义中间件都不会被调用请求在管线最前端就被拦截。底层校验实现同样位于 handler.go会先取出用户负载再判断其是否实现了Validator接口// runValidate validates the request, and returns a validation error on failure. // If the user payload does not implement Validator, it returns nil. func runValidate(userPayload any) error { if v, ok : userPayload.(Validator); ok { if err : v.Validate(); err ! nil { // If we already have an *errs.Error, return it directly. if _, ok : err.(*errs.Error); ok { return err } return errs.WrapCode(err, errs.InvalidArgument, validation failed) } } return nil }可以看到校验失败后的错误处理分两种情况下节详述这正是官方文档所述错误映射规则的源码落点。校验失败时的错误映射规则当Validate()返回错误时Encore 按如下规则向调用方报告最终体现在 HTTP 响应上Validate()返回值框架处理最终 HTTP 状态码nil放行进入 handler 与中间件正常处理如 200*errs.Errorencore.dev/beta/errs包构造的结构化错误原样透传错误码、消息、Details、Meta 均不改动由errs.Error.Code决定如InvalidArgument→ 400其他任意 error包装为*errs.Error错误码设为InvalidArgument400 Bad Request规则一返回*errs.Error原样透传如果你希望校验错误携带自定义错误码、结构化 Details 或内部 Meta 信息可以直接返回*errs.Errorfunc (req *CreateUserRequest) Validate() error { if req.Email { return errs.B().Code(errs.InvalidArgument).Msg(email is required).Err() } return nil }此时框架不会做任何转换错误被unmodified地报告给调用方。这在runValidate中体现为if _, ok : err.(*errs.Error); ok { return err }规则二普通 error 包装为InvalidArgument返回普通error如errors.New时框架使用errs.WrapCode(err, errs.InvalidArgument, validation failed)将其包装为*errs.Error错误码为InvalidArgument最终得到HTTP 400 Bad Request。InvalidArgument与 HTTP 状态码的映射在 runtimes/go/beta/errs/errs_internal.go 中明确给出case InvalidArgument: return 400而errs.WrapCode的行为在 docs/go/primitives/api-errors.md 中有完整说明它类似errs.Wrap在保留原始错误信息的同时额外设置错误码。包装后返回给外部客户端的 JSON 形如{ code: invalid_argument, message: validation failed, details: null }该响应由errs.HTTPErrorWithCode见 errs_internal.go序列化外部客户端收到的错误码、消息等与 gRPC 标准错误码保持一致便于前端与生成的客户端直接处理。更多错误码与 HTTP 状态码的完整映射表可参考 docs/go/primitives/api-errors.md。仓库中的真实用例e2e 验证测试仓库的端到端测试工程e2e-tests/testdata/echo中包含一个专门用于验证该机制的示例服务 validation.gopackage validation import ( context errors ) type Request struct { Msg string } func (req *Request) Validate() error { if req.Msg fail { return errors.New(bad message) } return nil } //encore:api public func TestOne(ctx context.Context, msg *Request) error { return nil }这个用例非常清晰地演示了三个要点请求类型Request通过func (req *Request) Validate() error实现Validator接口当Msg fail时返回一个普通错误errors.New因此按规则二会被包装为InvalidArgument最终返回 HTTP 400其他情况下返回nil请求正常进入TestOnehandler 执行业务逻辑。该服务的 API 定义//encore:api public说明它是对外公开端点e2e 测试见 e2e-tests/echo_app_test.go会实际发起 HTTP 请求来断言上述校验行为可作为你编写自己校验逻辑与测试的参照模板。验证库自由选择的背后逻辑官方文档强调This design means that its easy to use your validation library of choice.这一设计使得你可以自由选择任意校验库。由于 Encore 的约定只是实现Validate() error你完全可以在方法体内使用你熟悉的任何校验工具例如import ( validator github.com/go-playground/validator/v10 ) var validate validator.New() type SignupRequest struct { Email string validate:required,email Password string validate:required,min8 } func (req *SignupRequest) Validate() error { return validate.Struct(req) // 返回的错误会被包装为 InvalidArgument → 400 }也可以为仅当某字段非空时才校验另一字段这类交叉字段约束编写自定义逻辑或者把多个校验库组合使用。Encore 只负责在正确时机调用你的Validate()并把错误映射为 HTTP 响应校验策略完全由你的代码掌控。这既避免了框架锁定也保证了与既有 Go 生态校验库的兼容性。注意事项与使用边界只对非 Raw端点生效请求验证作用于 Encore 常规结构化 JSON端点。对于原始端点raw endpoints使用http.ResponseWriter/*http.Request的 handler请求体不被 Encore 反序列化因此Validate() error机制不适用需要你在 handler 内自行校验。校验失败会短路整条管线handleIncoming中校验失败直接返回错误响应newErrResp用户的中间件与 handler 均不会执行。如果你的中间件依赖请求负载做前置处理需要注意这一点例如日志中间件记录的仍是请求原文而非校验后的 payload。Validate()可能被多次调用从源码看validate既在入站处理handleIncoming时被调用也在服务间内部调用路径runCall内的d.validate(req)中被调用。因此Validate()应保持幂等、无副作用避免在其中修改请求字段或依赖调用次数。错误信息会被序列化给外部调用方除*errs.Error外普通错误会以InvalidArgument码返回原始错误信息会出现在 JSON 响应中。若不想泄露内部细节建议在Validate()中直接构造用户友好的*errs.Error消息。小结Encore 的请求验证机制把反序列化 → 校验 → 执行业务这段通用流水线固化在运行时中你只需为请求类型实现一个Validate() error方法框架就会在调用 handler 与中间件之前自动校验并把失败结果按*errs.Error原样透传 / 普通错误包装为InvalidArgumentHTTP 400的规则返回给调用方。它开箱即用、对校验库零锁定既适合快速原型也足以支撑生产级 API 的输入防护。对于更复杂的业务错误如结构化 Details、自定义错误码可进一步参考 docs/go/primitives/api-errors.md 中关于errs包错误码与错误构建器的说明。【免费下载链接】encoreThe infrastructure platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/encor/encore创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考