ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Fastify 流畅模式:用 fluent-json-schema 编写可复用的请求校验与响应序列化 Schema

Fastify 流畅模式:用 fluent-json-schema 编写可复用的请求校验与响应序列化 Schema Fastify 流畅模式用 fluent-json-schema 编写可复用的请求校验与响应序列化 Schema【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastifyFastify 支持通过 JSON Schema 对请求的 body、params、query、headers 做校验并对响应做高性能序列化。本指南讲解如何结合官方推荐的fluent-json-schema以链式 API 方式声明这些 Schema无需手写 JSON、无需手动调用.valueOf()并通过addSchema()将共享 Schema 注册到 Fastify 实例中以实现复用。读完本文你将掌握 fluent schema 的完整声明语法、两种共享引用方式$ref-way与replace-way及其在 Fastify 内部的识别与转换机制。fluent-json-schema 是什么为什么用它按照 Validation and Serialization 参考文档Fastify 的路由schema选项接受body、querystring或query、params、headers、response等键其值就是标准 JSON Schema。手写大段 JSON Schema 冗长且难以维护而fluent-json-schema中fluent-json-schema: ^6.0.0提供了链式声明式写法同时允许复用常量如枚举值、公共子结构这正是本指南的主题。基础用法一个路由的完整 fluent schema 声明以下示例覆盖了 body、query、params、headers 四个维度的常用 fluent 方法const S require(fluent-json-schema) // You can have an object like this, or query a DB to get the values const MY_KEYS { KEY1: ONE, KEY2: TWO } const bodyJsonSchema S.object() .prop(someKey, S.string()) .prop(someOtherKey, S.number()) .prop(requiredKey, S.array().maxItems(3).items(S.integer()).required()) .prop(nullableKey, S.mixed([S.TYPES.NUMBER, S.TYPES.NULL])) .prop(multipleTypesKey, S.mixed([S.TYPES.BOOLEAN, S.TYPES.NUMBER])) .prop(multipleRestrictedTypesKey, S.oneOf([S.string().maxLength(5), S.number().minimum(10)])) .prop(enumKey, S.enum(Object.values(MY_KEYS))) .prop(notTypeKey, S.not(S.array())) const queryStringJsonSchema S.object() .prop(name, S.string()) .prop(excitement, S.integer()) const paramsJsonSchema S.object() .prop(par1, S.string()) .prop(par2, S.integer()) const headersJsonSchema S.object() .prop(x-foo, S.string().required()) // Note that there is no need to call .valueOf()! const schema { body: bodyJsonSchema, querystring: queryStringJsonSchema, // (or) query: queryStringJsonSchema params: paramsJsonSchema, headers: headersJsonSchema } fastify.post(/the/url, { schema }, handler)几个关键要点无需调用.valueOf()fluent schema 对象本身可以像普通 JSON Schema 一样直接放进schema选项。这一点并非偶然Fastify 内部会检测并转换原理见下节因此测试文件 test/fluent-schema.test.js 中有专门的用例Should call valueOf internally覆盖query、querystring、body、params、headers以及response.200/201全部位置。querystring与query等价两者只能出现其一同时出现会抛出FST_ERR_SCH_DUPLICATE(querystring)错误。常量复用S.enum(Object.values(MY_KEYS))演示了把业务常量甚至来自数据库的值直接映射为枚举约束避免魔法字符串散落在 schema 中。多类型表达S.mixed([...])声明任意类型之一如可空字段nullableKeyS.oneOf([...])声明各分支带不同约束如string 且 ≤5或number 且 ≥10S.not(S.array())做类型排除。同样的写法也可以用于response实现响应序列化。官方测试给出了一个完整形态fastify.post(/:id, { handler: (req, reply) { reply.send({ name: a, surname: b, dateOfBirth: 01-01-2020 }) }, schema: { params: S.object().prop(id, S.integer().minimum(42)), headers: S.object().prop(x-custom, S.string().format(email)), query: S.object().prop(surname, S.string().required()), body: S.object().prop(name, S.string().required()), response: { 200: S.object() .prop(name, S.string()) .prop(surname, S.string()) } } })从该测试test/fluent-schema.test.js可以看到两点实战价值一是校验失败时 Fastify 返回结构化错误如params/id must be 42、headers/x-custom must match format email二是response序列化会裁剪掉未在 schema 中声明的字段——handler 返回了dateOfBirth但 200 响应体中只有name和surname。源码机制Fastify 如何识别并转换 fluent schema从源码结构看fastify 对 fluent 对象的透明支持由 lib/schemas.js 实现// lib/schemas.js const kFluentSchema Symbol.for(fluent-schema-object) function generateFluentSchema (schema) { for (const key of SCHEMAS_SOURCE) { if (schema[key] (schema[key].isFluentSchema || schema[key][kFluentSchema])) { schema[key] schema[key].valueOf() } } if (schema.response) { const httpCodes Object.keys(schema.response) for (const code of httpCodes) { if (schema.response[code].isFluentSchema || schema.response[code][kFluentSchema]) { schema.response[code] schema.response[code].valueOf() } } } }其工作机制是双版本兼容识别通过isFluentSchemafluent-json-schemav6 的标记属性或全局符号Symbol.for(fluent-schema-object)旧版fluent-schema的标记判断对象是否为 fluent schema。从源码结构看这使 fastify 同时兼容新旧两代 fluent schema 库。按需valueOf()展开只有被识别为 fluent 对象时才调用.valueOf()得到纯 JSON Schema 对象SCHEMAS_SOURCE数组params、body、querystring、query、headers与response的各状态码均被遍历处理。这就是文档中无需手动.valueOf()承诺的实现来源。别名归一化normalizeSchema()会把query键改写为querystring键所以两种写法最终等价。转换之后的纯 JSON Schema 会交给 Fastify 的 SchemaController 编译默认由fastify/ajv-compiler构建校验器、fastify/fast-json-stringify-compiler构建序列化器见 lib/schema-controller.js。也就是说fluent 写法最终走的仍是 Fastify 标准的校验/序列化管线不改变任何性能特征。复用共享 schemaaddSchema 的两种引用方式当某个子结构如地址需要在多个路由间复用时fluent-json-schema 允许更轻松地以编程方式操纵 schema再通过fastify.addSchema()注册复用。Validation and Serialization 参考文档 详细定义了引用的解析规则fluent 场景下有典型两种用法。方式一$ref-way外部 schema 引用用一个带$id的容器对象把若干命名 schema 以definitions形式登记然后路由中用S.ref()指向其中某个定义const addressSchema S.object() .id(#address) .prop(line1).required() .prop(line2) .prop(country).required() .prop(city).required() .prop(zipcode).required() const commonSchemas S.object() .id(https://fastify/demo) .definition(addressSchema, addressSchema) .definition(otherSchema, otherSchema) // You can add any schemas you need fastify.addSchema(commonSchemas) const bodyJsonSchema S.object() .prop(residence, S.ref(https://fastify/demo#address)).required() .prop(office, S.ref(https://fastify/demo#/definitions/addressSchema)).required() const schema { body: bodyJsonSchema } fastify.post(/the/url, { schema }, handler)两个$ref的写法对应两种解析路径https://fastify/demo#address在共享 schema 内部查找$id: #address的子 schemahttps://fastify/demo#/definitions/addressSchema则直接取definitions.addressSchema。注意S.object().id(#address)生成的就是文档中myField: { $ref: http://url.com/sh.json#foo }规则所要求的内部$id。方式二replace-way验证前整段替换也可以不走$ref语义而是把一个整体共享 schema以$id注册路由 schema 中用字符串形式引用其根在校验前被 Fastify 替换为完整 schema 内容const sharedAddressSchema { $id: sharedAddress, type: object, required: [line1, country, city, zipcode], properties: { line1: { type: string }, line2: { type: string }, country: { type: string }, city: { type: string }, zipcode: { type: string } } } fastify.addSchema(sharedAddressSchema) const bodyJsonSchema { type: object, properties: { vacation: sharedAddress# } } const schema { body: bodyJsonSchema } fastify.post(/the/url, { schema }, handler)此方式与 fluent 无关——共享 schema 可以直接手写为普通 JSON Schema 对象addSchema()对两种形态一视同仁。replace-way也完全可以在 fluent 之外与原生写法混用test/shared-schema 示例即演示了共享 schema 的基本用法。两种方式的混合与底层存储机制ℹ️ 说明使用fastify.addSchema时$ref-way和replace-way可以任意混合。这一混合能力有专门的回归测试 test/fluent-schema.test.js 用例use fluent schema and plain JSON schema佐证同一个 Fastify 实例上先注册 fluent 风格的commonSchemas再注册普通对象风格的sharedAddressSchema两条路由均能成功通过fastify.ready()的编译。从源码结构看addSchema()的落地存储在 lib/schemas.jsSchemas.prototype.add function (inputSchema) { const schema fastClone((inputSchema.isFluentSchema || inputSchema.isFluentJSONSchema || inputSchema[kFluentSchema]) ? inputSchema.valueOf() : inputSchema ) // developers can add schemas without $id, but with $def instead const id schema.$id if (!id) { throw new FST_ERR_SCH_MISSING_ID() } if (this.store[id]) { throw new FST_ERR_SCH_ALREADY_PRESENT(id) } this.store[id] schema }要点fluent 容器同样免.valueOf()如果addSchema()直接传入 fluent 对象内部会先valueOf()展开再深拷贝rfdc入 store强制$id共享 schema 缺$id会抛FST_ERR_SCH_MISSING_ID同一$id重复注册抛FST_ERR_SCH_ALREADY_PRESENT封装性addSchemaAPI 是 encapsulated 的只在当前 Fastify 上下文及其子上下文内可见配合插件体系可以做出模块内私有的共享 schema。此外如果自定义了校验器/序列化器.addSchema不再由 Fastify 代管需要用.getSchemas()/.getSchema(schemaId)读取已注册的 schema详见 Validation and Serialization 参考文档。小结与实践建议路由级 schema 统一使用S.object().prop(...)链式写法枚举、格式约束如S.string().format(email)全部声明在 fluent 层常量枚举值等从外部变量注入便于从数据库或配置中心取值body、params、query或querystring、headers、response.状态码五个位置都可以直接放 fluent 对象Fastify 会在 lib/schemas.js 中自动valueOf()不要手写转换逻辑复用优先选择$ref-wayS.ref()引用粒度细、语义即标准 JSON Schema$ref方便被 Swagger 等工具理解把整个对象原样塞给某个字段时才考虑replace-way校验/序列化失败会走 Fastify 标准错误通道FST_ERR_VALIDATION等行为可参考 test/fluent-schema.test.js 中对 400 响应体的逐项断言便于编写端到端测试。【免费下载链接】fastifyFast and low overhead web framework, for Node.js项目地址: https://gitcode.com/GitHub_Trending/fa/fastify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表