拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TypeSpec REST 实战:用嵌套命名空间定义 CRUD 操作与多状态码响应

TypeSpec REST 实战用嵌套命名空间定义 CRUD 操作与多状态码响应【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec本文是 TypeSpec REST 入门系列的第二篇聚焦于如何在 TypeSpec 中为 REST API 定义完整的 CRUD增删改查操作借助嵌套命名空间组织资源路由、使用get/post/put/delete等动词装饰器声明 HTTP 方法并通过statusCode|联合类型为每个操作声明多种成功/失败响应。读完本文你将能够独立用 TypeSpec 写出结构清晰、可生成 OpenAPI 规范的 CRUD 接口并理解其背后的源码级实现原理路由拼接、状态码校验与operationId生成。本文对应的原始教程位于仓库 website/src/content/docs/docs/getting-started/getting-started-rest/02-operations-responses.md你可以对照阅读所有代码示例均来自该文档可放入main.tsp后使用typespec/openapi3发射器直接生成 OpenAPI 3.0 规范。准备工作服务骨架与数据模型在定义操作之前需要先声明服务的基础信息。下面的代码在前一篇教程的基础上引入了typespec/http库并定义了 Pet Store 的Pet模型与petType枚举import typespec/http; using Http; service(#{ title: Pet Store }) server(https://example.com, Single server endpoint) namespace PetStore; model Pet { id: int32; minLength(1) name: string; minValue(0) maxValue(100) age: int32; kind: petType; } enum petType { dog: dog, cat: cat, fish: fish, bird: bird, reptile: reptile, }几个关键点service装饰器把PetStore命名空间标记为服务根其title会进入 OpenAPI 规范中的info.titleserver装饰器声明服务的基准地址与描述最终会映射为 OpenAPI 的servers字段Pet模型通过minLength、minValue、maxValue等校验装饰器描述字段约束这些约束会体现在 OpenAPI 的schema中kind字段引用petType枚举枚举的每个成员同时给出标签值与字面量值如dog: dog。定义 CRUD 操作嵌套命名空间Pets接下来为Pet模型定义增删改查操作。关键做法是在PetStore命名空间内部再嵌套一个Pets命名空间并用route(/pets)声明其基准路径import typespec/http; using Http; service(#{ title: Pet Store }) server(https://example.com, Single server endpoint) namespace PetStore; model Pet { id: int32; minLength(1) name: string; minValue(0) maxValue(100) age: int32; kind: petType; } enum petType { dog: dog, cat: cat, fish: fish, bird: bird, reptile: reptile, } route(/pets) namespace Pets { get op listPets(): { statusCode statusCode: 200; body pets: Pet[]; }; get op getPet(path petId: int32): { statusCode statusCode: 200; body pet: Pet; }; post op createPet(body pet: Pet): { statusCode statusCode: 201; body newPet: Pet; }; put op updatePet(path petId: int32, body pet: Pet): { statusCode statusCode: 200; body updatedPet: Pet; }; delete op deletePet(path petId: int32): { statusCode statusCode: 204; }; }这个示例中每个元素承担的角色如下route(/pets)为Pets命名空间定义基准路径作用于其内部所有操作listPets列出所有宠物返回 200 状态码和Pet[]数组getPet按petId获取单个宠物path标记该参数来自 URL 路径createPet创建宠物body标记请求体为Pet模型成功返回 201 与新建的宠物对象updatePet按petId更新宠物请求体携带新的Pet数据返回 200 与更新后的对象deletePet按petId删除宠物返回 204无内容。动词装饰器背后的源码实现get、post、put、delete在 packages/http/src/decorators.ts 中其实是通过同一个工厂函数createVerbDecorator生成的它们统一把 HTTP 动词记录到操作上供后续路由与 OpenAPI 发射器消费function createVerbDecorator(verb: HttpVerb) { return (context: DecoratorContext, entity: Operation) { setOperationVerb(context, entity, verb); }; } export const $get: GetDecorator createVerbDecorator(get); export const $put: PutDecorator createVerbDecorator(put); export const $post: PostDecorator createVerbDecorator(post); export const $delete: DeleteDecorator createVerbDecorator(delete); export const $head: HeadDecorator createVerbDecorator(head);而statusCode、body的实现则非常简单——它们只是把对应的模型属性登记到编译器的状态集合中供后续分析阶段识别export const $body: BodyDecorator (context: DecoratorContext, entity: ModelProperty) { context.program.stateSet(HttpStateKeys.body).add(entity); }; export const $statusCode: StatusCodeDecorator ( context: DecoratorContext, entity: ModelProperty, ) { context.program.stateSet(HttpStateKeys.statusCode).add(entity); };route的拼接规则route装饰器在 packages/http/src/decorators/route.ts 中实现其文档注释明确说明route定义目标操作的相对路由 URI第一个参数可以是包含一个或多个路径参数占位符的 URI 片段如果包含该操作的命名空间或接口也标记了route它将作为操作路由的前缀。在 packages/http/src/route.ts 中collectSegmentsAndOptions会从命名空间逐层向上收集父级路由段再与操作自身的route拼接成完整的 URI 模板。这正是PetStore无路由→Pets/pets→ 操作无路由最终生成/pets、/pets/{petId}的原因。嵌套命名空间的三大收益教程明确列出了在 API 定义中使用嵌套命名空间的三个好处组织结构清晰Organization把相关操作按资源聚合到同一个命名空间下API 更易于管理与理解。Pets命名空间把所有宠物相关的操作集中在一起后续添加Owners、Orders等资源时互不干扰。可读的操作 IDOperation IDsTypeSpec 编译器会把命名空间名拼接到 OpenAPI 规范的operationId中使人一眼看出每个操作作用于哪个资源。避免命名冲突Clarity不同命名空间下可以存在同名操作而不会冲突为 API 提供清晰的结构边界。示例OpenAPI 中的操作 ID对于定义在Pets命名空间中的listPets操作生成的 OpenAPI 规范中operationId形如Pets_listPets明确表明该操作与Pets资源相关同理还有Pets_getPet、Pets_createPet、Pets_updatePet、Pets_deletePet。从源码看这一行为由 packages/openapi3/src/operation-id-resolver/operation-id-resolver.ts 中的OperationIdResolver负责它会先检查是否显式使用了operationId装饰器若没有则按照命名空间路径 操作名解析出操作 ID并对重名 ID 自动追加_2、_3后缀去重。生成的路由 URL 一览将Pets命名空间下的 CRUD 操作与基准地址https://example.com组合得到如下完整路由List PetsGET https://example.com/pets—— 获取所有宠物列表Get Pet by IDGET https://example.com/pets/{petId}—— 按petId获取单个宠物Create PetPOST https://example.com/pets—— 创建一个新宠物Update Pet by IDPUT https://example.com/pets/{petId}—— 按petId更新已有宠物Delete Pet by IDDELETE https://example.com/pets/{petId}—— 按petId删除已有宠物。注意path petId: int32声明的petId会作为路径参数出现在 URI 模板{petId}中并在 OpenAPI 中生成in: path、required: true、schema: { type: integer, format: int32 }的参数定义。操作流程图为便于理解客户端请求在整个 API 中的流转教程给出了如下数据流示意[Client] -- [API Gateway] -- [listPets Operation] -- [Database] -- [Response: List of Pets] [Client] -- [API Gateway] -- [getPet Operation] -- [Database] -- [Response: Pet Details] [Client] -- [API Gateway] -- [createPet Operation] -- [Database] -- [Response: Created Pet] [Client] -- [API Gateway] -- [updatePet Operation] -- [Database] -- [Response: Updated Pet] [Client] -- [API Gateway] -- [deletePet Operation] -- [Database] -- [Response: Deletion Confirmation]即客户端请求先经 API Gateway 路由到对应操作操作访问数据库后返回对应类型的响应列表、详情、新建对象、更新对象或删除确认。处理不同类型的响应多状态码与|联合类型真实世界中的 API 往往需要根据后端处理结果返回不同的状态码。TypeSpec 使用|运算符联合类型在一个操作上声明多种可能的响应。下面更新宠物操作使其按结果返回不同状态码import typespec/http; using Http; service(#{ title: Pet Store }) server(https://example.com, Single server endpoint) namespace PetStore; model Pet { id: int32; minLength(1) name: string; minValue(0) maxValue(100) age: int32; kind: petType; } enum petType { dog: dog, cat: cat, fish: fish, bird: bird, reptile: reptile, } route(/pets) namespace Pets { get op listPets(): { statusCode statusCode: 200; body pets: Pet[]; }; get op getPet(path petId: int32): | { statusCode statusCode: 200; body pet: Pet; } | { statusCode statusCode: 404; }; post op createPet(body pet: Pet): | { statusCode statusCode: 201; body newPet: Pet; } | { statusCode statusCode: 202; body acceptedPet: Pet; }; put op updatePet(path petId: int32, body pet: Pet): | { statusCode statusCode: 200; body updatedPet: Pet; } | { statusCode statusCode: 404; }; delete op deletePet(path petId: int32): { statusCode statusCode: 204; }; }在本例中各操作根据后端服务上报的结果返回不同状态码getPet找到宠物返回 200 Pet未找到返回 404createPet创建成功返回 201 newPet若请求被接受但尚未完成处理则返回 202 acceptedPetupdatePet更新成功返回 200 updatedPet目标不存在返回 404deletePet删除成功返回 204无响应体。|运算符的语义|用于为一个操作定义多个可能的响应每个响应块指定不同的状态码与响应体以createPet为例|使该操作既能返回 201 状态码 newPet对象也能返回 202 状态码 acceptedPet对象响应块之间是或的关系编译器会为每个块独立生成 OpenAPIresponses下的一个状态码条目。状态码的合法性校验源码级statusCode中的数值并非随意填写。在 packages/http/src/status-codes.ts 中validateStatusCode会对状态码做严格校验export function validateStatusCode( code: number | string, diagnosticTarget: DiagnosticTarget, ): [HttpStatusCodes, readonly Diagnostic[]] { const codeAsNumber typeof code string ? parseInt(code, 10) : code; if (isNaN(codeAsNumber)) { return error(diagnosticTarget); } if (!Number.isInteger(codeAsNumber)) { return error(diagnosticTarget); } if (codeAsNumber 100 || codeAsNumber 599) { return error(diagnosticTarget); } return [[codeAsNumber], []]; }即状态码必须是100到599之间的整数否则会触发status-code-invalid诊断。此外getStatusCodesFromType还支持从联合类型、标量Scalar及属性上解析出状态码集合甚至可以通过minValue/maxValue在标量上表达状态码范围如 2xx、4xx 区间满足更复杂的场景。OpenAPI 规范映射对照下表展示了 TypeSpec 操作定义与最终 OpenAPI 规范的映射关系左侧为 TypeSpec 源码右侧为等价 OpenAPI YAMLTypeSpec DefinitionOpenAPI Specroute(/pets) namespace Pets { get op listPets(): { statusCode statusCode: 200; body pets: Pet[]; }; get op getPet(path petId: int32): { statusCode statusCode: 200; body pet: Pet; } | { statusCode statusCode: 404; }; post op createPet(body pet: Pet): { statusCode statusCode: 201; body newPet: Pet; } | { statusCode statusCode: 202; body acceptedPet: Pet; }; put op updatePet(path petId: int32, body pet: Pet):{ statusCode statusCode: 200; body updatedPet: Pet; } | { statusCode statusCode: 404; } | { statusCode statusCode: 500; }; delete op deletePet(path petId: int32): { statusCode statusCode: 204; body NoContentResponse; } | { statusCode statusCode: 404; }; }paths: /pets: get: operationId: Pets_listPets parameters: [] responses: 200: description: The request has succeeded. content: application/json: schema: type: array items: $ref: #/components/schemas/Pet post: operationId: Pets_createPet parameters: [] responses: 201: description: The request has succeeded and a new resource has been created as a result. content: application/json: schema: $ref: #/components/schemas/Pet 202: description: The request has been accepted for processing, but processing has not yet completed. content: application/json: schema: $ref: #/components/schemas/Pet requestBody: required: true content: application/json: schema: $ref: #/components/schemas/Pet /pets/{petId}: get: operationId: Pets_getPet parameters: - name: petId in: path required: true schema: type: integer format: int32 responses: 200: description: The request has succeeded. content: application/json: schema: $ref: #/components/schemas/Pet 404: description: The server cannot find the requested resource. put: operationId: Pets_updatePet parameters: - name: petId in: path required: true schema: type: integer format: int32 responses: 200: description: The request has succeeded. content: application/json: schema: $ref: #/components/schemas/Pet 404: description: The server cannot find the requested resource. requestBody: required: true content: application/json: schema: $ref: #/components/schemas/Pet delete: operationId: Pets_deletePet parameters: - name: petId in: path required: true schema: type: integer format: int32 responses: 204: description: There is no content to send for this request, but the headers may be useful. 对照这张表可以清楚看到几个值得注意的映射细节operationId每个操作都生成了Pets_*形式的前缀化操作 ID与嵌套命名空间直接对应path petId映射为parameters中in: path、required: true、type: integer / format: int32的路径参数bodycreatePet、updatePet的请求体生成requestBodyrequired: true且 schema 引用#/components/schemas/Pet多状态码每个|分支都展开为responses下的一个状态码条目并自动附带标准的description文案204 无内容deletePet的 204 响应没有content字段只有描述文案。注意可以明显看出与等价的 OpenAPI 规范相比TypeSpec 源码更加紧凑、可读性更高——同一套接口定义用 TypeSpec 只需数十行而生成的 OpenAPI YAML 则要长得多。小结本节演示了如何用 TypeSpec 为 REST API 定义完整的 CRUD 操作包括用嵌套命名空间Petsroute(/pets)组织资源路由用get/post/put/delete声明 HTTP 动词用statusCode、body、path描述响应状态码、请求/响应体与路径参数用|联合类型为一个操作声明多种状态码响应200/201/202/204/404/500理解operationId如Pets_listPets的生成规则、状态码 100–599 的合法性校验以及route沿命名空间逐层拼接的底层原理。下一篇教程将进一步深入 REST API 的错误处理包括为错误处理定义自定义响应模型。如果你想在本地复现上述示例可以在 packages/http 与 packages/openapi3 包中查看typespec/http与typespec/openapi3的实现与测试用例。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门