ARTICLE DETAIL

资讯详情

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

TypeSpec HTTP Client JS 操作参数处理详解:required 参数如何在生成的 TypeScript 客户端中落地

TypeSpec HTTP Client JS 操作参数处理详解:required 参数如何在生成的 TypeScript 客户端中落地 TypeSpec HTTP Client JS 操作参数处理详解required 参数如何在生成的 TypeScript 客户端中落地【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec导读本文围绕typespec/http-client-js的测试场景文档 only_required.md完整讲解当 TypeSpec 接口中定义的操作带有required必填参数时代码生成器如何将其映射为 TypeScript 客户端函数的显式位置参数并把可选参数与运行时配置收敛进options参数包options bag。你将掌握 required 参数的判定逻辑、GetWithParamsOptions选项接口的生成规则、TestClient类方法如何转发调用以及底层源码 operation-parameters.tsx 与 operation-options.tsx 的具体实现依据。场景概述一个带必填参数的 GET 操作only_required.md描述的是一个只含必填参数的最小化测试场景。其 TypeSpec 定义如下service namespace Test; get op getWithParams(query name: string, query age: int32): int32;service将Test命名空间标记为一个服务供typespec/http-client发现客户端根类型get声明这是一个 HTTP GET 操作两个query查询参数name: string与age: int32都没有?可选标记也没有默认值因此属于 required 参数返回值是int32最终会映射为Promisenumber。从源码结构看该场景属于packages/http-client-js/test/scenarios/operation-parameters/目录下 13 个场景之一其余包括only_optional.md、no_parameters.md、default_value_as_optional.md、spread_body.md等它们共同覆盖了参数形态对生成代码签名的影响这一主题。场景文件由 scenarios.test.ts 驱动executeScenarios会基于typespec/http、typespec/rest两个库编译每个场景并利用createSnippetExtractor从生成结果中提取代码片段与 Markdown 中预期的代码块比对从而保证本文展示的生成代码与当前仓库实现一致。核心规则必填参数映射为位置参数可选参数收敛进 options场景文档在 Operation 一节给出了最终的生成函数签名这是理解整套规则的关键入口export async function getWithParams( client: TestClientContext, name: string, age: number, options?: GetWithParamsOptions, ): Promisenumber { const path parse(/{?name,age}).expand({ name: name, age: age, }); const httpRequestOptions { headers: {}, }; const response await client.pathUnchecked(path).get(httpRequestOptions); if (typeof options?.operationOptions?.onResponse function) { options?.operationOptions?.onResponse(response); } if (response.status 200 response.headers[content-type]?.includes(application/json)) { return response.body!; } throw createRestError(response); }可以归纳出以下可复用的设计规则client永远是第一个参数每个生成的操作函数都以TestClientContext作为第一个参数它是后续所有请求的载体。required 参数按声明顺序平铺为后续位置参数name: string、age: number依次展开int32被映射为 TS 的number。options始终以可选参数收尾即使存在必填参数也保留一个可选的 options 参数包用于承载 OperationOptions回调、重试、追踪等运行时配置以及可选/带默认值的参数。请求构造使用 URI 模板parse(/{?name,age})来自uri-template运行时依赖uri-template.ts{?name,age}表示两个 query 变量必填参数的值直接以命名属性传入expand。响应处理固定套路先触发onResponse回调若配置再检查状态码与content-type匹配200 application/json时直接返回response.body!否则抛出createRestError(response)。状态码比较使用response.status将其强制转为 number兼容字符串形式的状态字段。Options 参数包为什么必填参数不会出现在里面场景文档 Options 一节展示了生成的选项接口export interface GetWithParamsOptions extends OperationOptions {}这里的关键结论是当操作只有必填参数时Options 接口是空的仅继承OperationOptions。这与only_optional.md场景形成鲜明对比——后者的 Options 接口为export interface GetWithParamsOptions extends OperationOptions { name?: string; age?: number; }该差异的源码依据在 operation-options.tsxconst optionalParameters props.operation.parameters.properties .filter((p) !excludes.includes(p.property.name)) .filter((p) p.property.optional || hasDefaultValue(p));即只有满足property.optional为真或具有默认值hasDefaultValue的 HTTP 参数才会被纳入 Options 接口并且统一以可选成员optional为 true的形式声明。判定默认值的逻辑位于 parameters.tsx只有content-type的默认值会被特殊对待见hasDefaultValue的注释 Only honors default values for content-type其余类型参数若带默认值也会按default_value_as_optional.md场景的规则进入 options。由此可以得出完整的行为矩阵参数形态生成签名Options 接口内容必填参数无?、无默认值平铺为位置参数不出现可选参数带?不进入位置参数作为可选成员出现带默认值的参数不进入位置参数作为可选成员出现单值字面量 contentType不进入位置参数以可选字面量成员出现如contentType?: application/json参考constant_as_optional.md场景Client 类必填参数如何从类方法转发到函数场景文档 Client 一节展示了生成的服务端封装类export class TestClient { #context: TestClientContext; constructor(endpoint: string, options?: TestClientOptions) { this.#context createTestClientContext(endpoint, options); } async getWithParams(name: string, age: number, options?: GetWithParamsOptions) { return getWithParams(this.#context, name, age, options); } }要点如下TestClient用#contextECMAScript 私有字段持有TestClientContext构造函数接收endpoint与可选的TestClientOptions通过createTestClientContext(endpoint, options)创建上下文类方法与顶层函数共用同一套参数签名name, age, options?方法体只是把#context连同参数原样转发给顶层函数getWithParams返回值透传。该转发结构由 client.tsx 生成每个操作通过getOperationParameters(op.httpOperation, refkey())取得参数描述再以[contextMemberRef, ...args]构造对顶层函数的调用表达式。换句话说类方法只是薄封装真正的请求逻辑URI 展开、请求选项组装、响应处理全部沉淀在src/api/testClientOperations.ts中的顶层函数里。底层原理getOperationParameters 的必填过滤逻辑顶层函数签名的生成逻辑集中在 operation-parameters.tsxexport function getOperationParameters( operation: HttpOperation, optionsRefkey: Refkey, ): ts.ParameterDescriptor[] { const transformNamer useTransformNamePolicy(); const requiredParameters operation.parameters.properties .filter((p) !p.property.optional !hasDefaultValue(p)) .filter((p) p.path.length 1); const parameters: ts.ParameterDescriptor[] []; for (const parameter of requiredParameters) { const name transformNamer.getApplicationName(parameter.property); const parameterDescriptor: ts.ParameterDescriptor { name, refkey: refkey(), type: ef.TypeExpression type{parameter.property.type} /, }; parameters.push(parameterDescriptor); } parameters.push({ name: options, refkey: optionsRefkey, type: getOperationOptionsTypeRefkey(operation), optional: true, }); return parameters; }requiredParameters的筛选条件有三层值得逐一说明!p.property.optional排除在 TypeSpec 中带?的可选参数!hasDefaultValue(p)排除带默认值的参数即便未标记可选也会被当作可选处理落入 options参考default_value_as_optional.md场景p.path.length 1从源码结构可以推断这是为了排除通过模型展开/嵌套携带的深层参数确保只有直接属于操作自身的参数路径深度为 1才会平铺为位置参数。body类参数及展开型参数如spread_body.md、union_body.md场景不在此列。随后每个必填参数通过useTransformNamePolicy().getApplicationName(...)应用命名策略如命名空间/前缀整理以ef.TypeExpression映射类型int32→number最后固定追加一个可选参数options其类型引用由getOperationOptionsTypeRefkey(operation)解析出的GetWithParamsOptions接口。结合源码的运行路径从 TypeSpec 到可调用函数将以上线索串起来一次get op getWithParams(query name: string, query age: int32): int32;的完整生成与运行路径为typespec/http-client在编译期把服务中的操作收集为HttpOperation含parameters、uriTemplate、verb等元数据operation-parameters.tsx 计算位置参数列表此处为name、ageoperation-options.tsx 生成GetWithParamsOptions此处为空接口仅继承OperationOptionshttp-request.tsx 依据uriTemplate/{?name,age}生成parse(...).expand(...)的 URL 展开代码并从parameters.properties中筛选kind path || kind query的参数注入展开对象顶层函数通过client.pathUnchecked(path).get(httpRequestOptions)发起请求httpRequestOptions由 http-request-options.tsx 根据 header/body 等配置组装本场景无 body因此只有headers: {}client.tsx 生成TestClient类方法把#context与参数转发给顶层函数场景测试 scenarios.test.ts 用executeScenarios编译该场景并抽取生成片段与 only_required.md 中的期望代码逐段比对保证上述行为被持续回归验证。与相邻场景的对比required 与 optional 的分水岭operation-parameters场景家族从正反两面印证了同一条规则only_required.md全部为必填参数签名是(client, name, age, options?)Options 为空接口only_optional.md全部为可选参数name?: string, age?: int32签名退化为(client, options?)必填参数一个都没有URL 模板退化为parse(/)expand({})为空对象参数值改从options?.name、options?.age读取constant_as_optional.md即使规范里没有任何参数仍会生成空 Options 参数包且字面量类型的content-type会以options?.contentType ?? application/json的形式兜底。三者的共同点是options参数包永远不会缺席——它既是 OperationOptions 的载体也是所有非必填参数的落点。这保证了客户端 API 在参数形态变化时签名稳定必填参数越多位置参数越多必填参数越少签名越接近(client, options?)。小结only_required.md虽然只是一个十余行的场景文档但它完整定义了typespec/http-client-js代码生成器对必填参数的核心行为必填参数非optional、无默认值、路径深度为 1作为位置参数平铺在client之后所有可选参数与带默认值的参数收敛进OperationOptions派生的 options 接口客户端类方法转发顶层函数请求逻辑集中在src/api/testClientOperations.ts上述规则由 operation-parameters.tsx、operation-options.tsx 和 client.tsx 三处源码共同实现并由 scenarios.test.ts 场景测试持续锁定。在编写或阅读 TypeSpec 服务定义时只需记住一条心智模型TypeSpec 中的?与默认值决定参数从签名移到 options其余部分生成器会为你保持一致、可预测的 TypeScript 客户端 API。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表