TypeSpec Rest 资源操作模板接口全解:@typespec/rest 中 22 个 Resource Interface 的完整参考指南
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
@typespec/rest的TypeSpec.Rest.Resource命名空间提供了一组资源操作模板接口(Resource Operation Template Interfaces),把 REST 资源最常见的 CRUD 生命周期操作抽象为可复用的泛型接口。本指南以官方参考文档 interfaces.md 为骨架,逐一拆解全部 22 个接口的模板参数、操作签名与适用场景,并结合 resource.tsp 的源码实现与 resource.test.ts 的测试用例,说明这些模板如何被解析为真实的 HTTP 路由。读完本文,你将能熟练选用合适的模板接口(标准资源、单例资源、扩展资源),理解其背后的键参数复制与路由生成机制,并直接在自己的 TypeSpec 规范中落地使用。
一、资源操作模板解决了什么问题
在 REST API 设计中,"资源"的读写删建等操作高度模式化:几乎每个资源都需要get(读取单个实例)、update(更新)、delete(删除)、create(创建)、list(分页列出)这五个标准操作。如果每个资源都手工书写这些op,规范会迅速变得冗长且难以保持一致。
@typespec/rest通过接口模板(interface 配合模板参数)解决这一问题:你只需要定义一个资源模型(如Thing),再写一句interface Things extends ResourceOperations<Thing, Error> {},编译器就会自动生成完整的标准操作集合,包括正确的路由、HTTP 动词、路径参数与响应类型。
这些模板接口定义在TypeSpec.Rest.Resource命名空间中,按适用对象可分为三类:
| 类别 | 接口 |
|---|---|
| 标准资源模板 | ResourceRead、ResourceUpdate、ResourceDelete、ResourceCreate、ResourceList、ResourceCreateOrReplace、ResourceCreateOrUpdate、ResourceInstanceOperations、ResourceCollectionOperations、ResourceOperations |
| 单例资源模板 | SingletonResourceRead、SingletonResourceUpdate、SingletonResourceOperations |
| 扩展资源模板 | ExtensionResourceRead、ExtensionResourceUpdate、ExtensionResourceDelete、ExtensionResourceCreate、ExtensionResourceList、ExtensionResourceCreateOrUpdate、ExtensionResourceInstanceOperations、ExtensionResourceCollectionOperations、ExtensionResourceOperations |
接口的权威定义位于 packages/rest/lib/resource.tsp,TypeSpec 编译器会在编译时对模板进行实例化并生成路由。
二、理解模板之前必须掌握的基础构件
所有资源操作模板都建立在一组底层模型与装饰器之上,理解它们才能真正读懂接口签名。
2.1 装饰器:@key、@segment、@resource、@parentResource
资源模板要求资源模型上至少有一个键属性(key property),即用@key标记的属性,它会被复制为操作中的路径参数。@resource("collectionName")将模型标记为资源类型,并自动在其@key属性上应用@segment;@segment则为该路径参数定义前置路径段。@parentResource(parent)用于声明父子资源关系,使子资源的操作自动带上父资源的键参数。
关于这些装饰器的完整签名与参数说明,可参考 README.md 或 decorators.md。
2.2 键参数收集模型:KeysOf与ParentKeysOf
模板签名中大量出现的...ResourceParameters<Resource>与...ResourceCollectionParameters<Resource>是路由参数展开的入口,它们最终依赖两个"动态收集"模型:
// packages/rest/lib/resource.tsp 中的定义 @copyResourceKeyParameters @friendlyName("{name}Key", Resource) model KeysOf<Resource> {} @copyResourceKeyParameters("parent") @friendlyName("{name}ParentKey", Resource) model ParentKeysOf<Resource> {}KeysOf<Resource>:动态收集Resource模型(含其父链)上的全部@key属性;ParentKeysOf<Resource>:只收集父资源的键属性(当@copyResourceKeyParameters的过滤参数为"parent"时),用于集合级操作定位父级作用域。
在此基础上:
model ResourceParameters<Resource extends {}> { ...KeysOf<Resource>; } model ResourceCollectionParameters<Resource extends {}> { ...ParentKeysOf<Resource>; }ResourceParameters用于实例级操作(如get、update、delete),展开后包含资源自身及其所有父级的键;ResourceCollectionParameters用于集合级操作(如create、list),只包含父级键。这一机制的底层实现在 src/resource.ts 的cloneKeyProperties与$copyResourceKeyParameters函数中——源码会递归遍历父资源链,将每个键属性克隆到目标模型,强制optional: false(防止可选的键属性变成可选路径参数),并自动附加@path装饰器。
2.3 请求与响应模型
模板签名中还引用了若干辅助模型,它们同样定义在TypeSpec.Rest.Resource命名空间:
ResourceCreateModel<Resource>:创建操作请求体,等价于DefaultKeyVisibility<Resource, Lifecycle.Read>并施加Lifecycle.Create可见性,即创建时通常不可提供服务端生成的键;ResourceCreateOrUpdateModel<Resource>:创建/更新操作请求体,是Resource的可更新属性集合(OptionalProperties<UpdateableProperties<...>>),属性均可选以支持部分更新;ResourceCreatedResponse<Resource>:创建成功响应,HTTP 状态码固定为201,@bodyRoot body承载被创建的资源;ResourceDeletedResponse:删除成功响应,HTTP 状态码固定为200;CollectionWithNextLink<Resource>:分页响应结构,包含value: Resource[](当前页数据,@pageItems)与可选的nextLink?: ResourceLocation<Resource>(下一页链接,@nextLink);ResourceError:默认错误响应模型,含code: int32与message: string。
上述模型的属性明细可在>interface TypeSpec.Rest.Resource.ResourceRead<Resource, Error> op get(...ResourceParameters<Resource>): Resource | Error;
签名中的...ResourceParameters<Resource>展开后即为@path @segment("things") thingId: string之类的参数。在 resource.test.ts 中,interface Things extends ResourceRead<Thing, Error> {}被解析为GET /things/{thingId}。
ResourceUpdate<Resource, Error>—— 更新单个资源:
op update(...ResourceParameters<Resource>, properties: ResourceCreateOrUpdateModel<Resource>): Resource | Error;更新操作使用@patch并开启implicitOptionality(为兼容旧行为),因此生成的路由动词为patch。源码在 resource.tsp 中还标注了@updatesResource(Resource)装饰器。
ResourceDelete<Resource, Error>—— 删除单个资源:
op delete(...ResourceParameters<Resource>): ResourceDeletedResponse | Error;删除成功后返回ResourceDeletedResponse(200 状态码),源码实现见 resource.tsp。
ResourceInstanceOperations<Resource, Error>—— 实例级三件套的聚合模板:
interface ResourceInstanceOperations<Resource extends {}, Error> extends ResourceRead<Resource, Error>, ResourceUpdate<Resource, Error>, ResourceDelete<Resource, Error> {}它通过 interface 继承组合了get、update、delete三个操作,定义见 resource.tsp。
3.2 集合级操作模板
集合级操作作用于资源集合,路由中只包含父级键参数(通常没有键参数)。
ResourceCreate<Resource, Error>—— 创建资源:
op create(...ResourceCollectionParameters<Resource>, resource: ResourceCreateModel<Resource>): Resource | ResourceCreatedResponse<Resource> | Error;创建操作使用...ResourceCollectionParameters(只带父键),请求体为ResourceCreateModel,成功时返回Resource或ResourceCreatedResponse(201 + 创建后的资源体)。测试验证其路由为POST /things。
ResourceList<Resource, Error>—— 列出资源集合:
op list(...ResourceCollectionParameters<Resource>): CollectionWithNextLink<Resource> | Error;返回分页结构CollectionWithNextLink<Resource>,路由为GET /things。
ResourceCollectionOperations<Resource, Error>—— 集合级两件套的聚合模板:
interface ResourceCollectionOperations<Resource extends {}, Error> extends ResourceCreate<Resource, Error>, ResourceList<Resource, Error> {}组合了create与list,见 resource.tsp。
3.3 完整生命周期模板:ResourceOperations
interface ResourceOperations<Resource extends {}, Error> extends ResourceInstanceOperations<Resource, Error>, ResourceCollectionOperations<Resource, Error> {}ResourceOperations聚合了全部五个标准操作:get、update、delete、create、list。这是日常使用频率最高的模板——一行interface Things extends ResourceOperations<Thing, Error> {}即可获得完整的 REST 资源端点。测试 resource.test.ts 验证其生成的路由为:
GET /things/{thingId} PATCH /things/{thingId} DELETE /things/{thingId} POST /things GET /things3.4 两种特殊写操作模板
ResourceCreateOrReplace<Resource, Error>—— 全量创建或替换:
op createOrReplace(...ResourceParameters<Resource>, resource: ResourceCreateModel<Resource>): Resource | ResourceCreatedResponse<Resource> | Error;与create不同,createOrReplace使用ResourceParameters(包含资源自身的键)并生成PUT动词,语义为"按指定键全量替换",路由为PUT /things/{thingId},见测试 resource.test.ts。
ResourceCreateOrUpdate<Resource, Error>—— 创建或部分更新(upsert 语义):
op createOrUpdate(...ResourceParameters<Resource>, resource: ResourceCreateOrUpdateModel<Resource>): Resource | ResourceCreatedResponse<Resource> | Error;同样携带资源自身键参数,请求体为ResourceCreateOrUpdateModel(属性可选),使用@patch生成PATCH动词。源码见 resource.tsp。
四、单例资源模板:SingletonResource*
单例资源(singleton resource)表示在父资源作用域下唯一存在的一个实例,例如GET /things/{thingId}/settings中的settings。它的特殊之处在于:路径中只有父资源的键,而没有单例自身的键。
单例模板使用三个类型参数:Singleton(单例资源模型)、Resource(父资源模型)、Error。
SingletonResourceRead<Singleton, Resource, Error>:
op get(...ResourceParameters<Resource>): Singleton | Error;操作只展开父资源的键参数,并通过@segmentOf(Singleton)使用单例模型上的@segment生成其 URL 段。测试 resource.test.ts 中,@segment("singleton") model Singleton与SingletonResourceOperations<Singleton, Thing, Error>配合,生成了GET /things/{thingId}/singleton。
SingletonResourceUpdate<Singleton, Resource, Error>:
op update(...ResourceParameters<Resource>, properties: ResourceCreateOrUpdateModel<Singleton>): Singleton | Error;请求体为基于Singleton模型的可选属性集合,生成PATCH /things/{thingId}/singleton。
SingletonResourceOperations<Singleton, Resource, Error>—— 聚合以上两者:
interface SingletonResourceOperations<Singleton extends {}, Resource extends {}, Error> extends SingletonResourceRead<Singleton, Resource, Error>, SingletonResourceUpdate<Singleton, Resource, Error> {}单例资源只有读取与更新两个标准操作,因为创建/删除/列表在语义上对单例不适用。
五、扩展资源模板:ExtensionResource*
扩展资源(extension resource)是一种附着在其他资源(父资源或子资源)之上的资源类型,用于在不修改原资源模型的前提下为它"扩展"额外数据与操作。扩展资源模板同样使用三个类型参数:Extension(扩展资源模型)、Resource(被扩展的资源模型)、Error。
关键差异在于签名中同时展开两组键参数,例如ExtensionResourceRead:
op get(...ResourceParameters<Resource>, ...ResourceParameters<Extension>): Extension | Error;这表示路由同时包含被扩展资源的键与扩展资源自身的键。以测试 resource.test.ts 中的ExtensionResourceOperations<Exthing, Thing, Error>为例,生成的路由为:
GET /things/{thingId}/extension/{exthingId} PATCH /things/{thingId}/extension/{exthingId} DELETE /things/{thingId}/extension/{exthingId} POST /things/{thingId}/extension GET /things/{thingId}/extension而当扩展挂在子资源(Subthing)上时,路由变为/things/{thingId}/subthings/{subthingId}/extension/{exthingId},可见父键会被完整继承。
全部 9 个扩展资源模板及其操作如下(均定义于 resource.tsp):
| 接口 | 操作 | 签名要点 |
|---|---|---|
ExtensionResourceRead | get | ...ResourceParameters<Resource>, ...ResourceParameters<Extension> |
ExtensionResourceUpdate | update | 两组键参数 +properties: ResourceCreateOrUpdateModel<Extension> |
ExtensionResourceDelete | delete | 两组键参数,返回ResourceDeletedResponse |
ExtensionResourceCreate | create | ...ResourceParameters<Resource>+resource: ResourceCreateModel<Extension> |
ExtensionResourceList | list | ...ResourceParameters<Resource>+...ResourceCollectionParameters<Extension>,返回CollectionWithNextLink<Extension> |
ExtensionResourceCreateOrUpdate | createOrUpdate | 两组键参数 +ResourceCreateOrUpdateModel<Extension> |
ExtensionResourceInstanceOperations | get/update/delete | 聚合 Read、Update、Delete |
ExtensionResourceCollectionOperations | create/list | 聚合 Create、List |
ExtensionResourceOperations | get/update/delete/create/list | 聚合以上两个 |
六、模板参数的约定与编译期校验
所有模板的类型参数遵循统一约定:
| 参数 | 含义 | 要求 |
|---|---|---|
Resource | 标准资源模型 | 必须含@key属性(否则诊断resource-missing-key) |
Extension | 扩展资源模型 | 必须含@key属性 |
Singleton | 单例资源模型 | 无需键,但需@segment定位 |
Error | 错误响应模型 | 必须用@error装饰(否则诊断resource-missing-error) |
在 resource.tsp 源码中,多数模板接口上都标注了@Private.validateHasKey(Resource)与@Private.validateIsError(Error),对应 resource.test.ts 中验证的两条诊断:
@typespec/rest/resource-missing-key:"Type 'Dog' is used as a resource and therefore must have a key. Use @key to designate a property as the key."@typespec/rest/resource-missing-error:"Type 'Error' is used as an error and therefore must have the @error decorator applied."
此外还有针对资源建模本身的校验:资源模型上存在多个@key时报告duplicate-key;子资源键名与父资源键名冲突时报告duplicate-parent-key;@parentResource形成循环时报告circular-parent-resource(源码实现见 src/resource.ts 中的checkCircularParentResource,测试见 resource.test.ts)。
七、实战:如何选用并组合这些模板
7.1 标准资源:一行接口获得完整 CRUD
import "@typespec/rest"; import "@typespec/http"; using TypeSpec.Rest; using TypeSpec.Rest.Resource; @resource("things") model Thing { @key @segment("things") thingId: string; name: string; description?: string; } @error model Error { code: int32; message: string; } interface Things extends ResourceOperations<Thing, Error> {}编译后即得到GET/PATCH/DELETE /things/{thingId}与POST/GET /things五个端点。
7.2 分层资源:借助@parentResource组合嵌套路由
@parentResource(Thing) @resource("subthings") model Subthing { @key @segment("subthings") subthingId: string; data: string; } interface Subthings extends ResourceOperations<Subthing, Error> {}依据 resource.test.ts,这会生成/things/{thingId}/subthings/{subthingId}(get/patch/delete)与/things/{thingId}/subthings(post/get),父级键thingId自动出现在所有子操作的路由中。
7.3 单例资源
@segment("settings") model ThingSettings { theme: string; } interface ThingSettingsSingleton extends SingletonResourceOperations<ThingSettings, Thing, Error> {}生成GET /things/{thingId}/settings与PATCH /things/{thingId}/settings。
7.4 扩展资源
@resource("tags") model Tag { @key @segment("tags") tagId: string; label: string; } interface ThingTags extends ExtensionResourceOperations<Tag, Thing, Error> {}生成GET/PATCH/DELETE /things/{thingId}/tags/{tagId}与POST/GET /things/{thingId}/tags,为Thing资源附加标签扩展能力。
7.5 组合使用
接口模板可以同时继承多个模板,例如测试中的interface Things extends ResourceOperations<Thing, Error>, ResourceCreateOrReplace<Thing, Error> {},它会在五个标准操作之外再增加一个PUT /things/{thingId}的替换操作,见 resource.test.ts。
八、进一步阅读
- 接口参考原文(本文档)
- 数据模型参考
- 装饰器参考
- @typespec/rest 总览
- 资源路由与自动路由生成
- 源码实现 与 装饰器运行时实现
- 路由生成与诊断的测试用例
- 包级 README 与装饰器清单
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考