組屬性到 TypeScript 客戶端模型:@typespec/http-client-js 的類型映射與序列化原理)
TypeSpec 數(shù)組屬性到 TypeScript 客戶端模型typespec/http-client-js 的類型映射與序列化原理【免費(fèi)下載鏈接】typespec項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ty/typespec本文以typespec/http-client-js包中的數(shù)組屬性場(chǎng)景測(cè)試文檔 array-properties.md 為主線講解 TypeSpec 模型中數(shù)組屬性含字符串?dāng)?shù)組、整數(shù)數(shù)組、聯(lián)合類型數(shù)組、Record 數(shù)組如何被發(fā)射emit為 TypeScript 客戶端模型并結(jié)合該包的源碼說明標(biāo)量映射、數(shù)組/Record 表達(dá)式、序列化轉(zhuǎn)換函數(shù)等底層實(shí)現(xiàn)。讀完本文你將掌握 TypeSpec 數(shù)組類型到 TypeScript 類型的完整映射規(guī)則理解ArrayT與Recordstring, T的生成機(jī)制并能獨(dú)立運(yùn)行、復(fù)現(xiàn)該場(chǎng)景測(cè)試。一、場(chǎng)景文檔定位它驗(yàn)證什么packages/http-client-js/test/scenarios/models/array-properties.md是typespec/http-client-js倉(cāng)庫中的場(chǎng)景測(cè)試scenario test規(guī)格文件。它不屬于最終用戶文檔而是以TypeSpec 輸入 期望的 TypeScript 輸出成對(duì)出現(xiàn)的方式為發(fā)射器定義可回歸驗(yàn)證的行為契約。該文檔包含兩個(gè)核心場(chǎng)景模型包含數(shù)組屬性string[]、int32[]、聯(lián)合類型數(shù)組(red | blue)[]應(yīng)分別生成Arraystring、Arraynumber、Arrayred | blue模型包含 Record 數(shù)組屬性Recordint32[]應(yīng)生成ArrayRecordstring, number。場(chǎng)景測(cè)試由 scenarios.test.ts 驅(qū)動(dòng)執(zhí)行它通過executeScenarios遍歷test/scenarios目錄下的全部.md文件用 test-host.ts 中配置的Tester加載typespec/http、typespec/rest、typespec/http-client-js三個(gè)庫并執(zhí)行typespec/http-client-js發(fā)射器編譯每個(gè)場(chǎng)景中的 TypeSpec 代碼再把實(shí)際生成結(jié)果與文檔中標(biāo)注的期望片段比對(duì)。因此這份文檔既是一份可讀的類型映射說明也是一份可執(zhí)行的測(cè)試斷言。二、核心場(chǎng)景一模型中的數(shù)組屬性原文檔給出的第一個(gè) TypeSpec 模型如下namespace Test; model Widget { id: string[]; weight: int32[]; color: (red | blue)[]; } op foo(): Widget;期望生成的 TypeScript 模型輸出文件src/models/models.ts接口名Widgetexport interface Widget { id: Arraystring; weight: Arraynumber; color: Arrayred | blue; }2.1 三條映射規(guī)則從這一組輸入輸出可以提煉出三條具有通用性的規(guī)則TypeSpec 屬性類型生成的 TypeScript 類型說明string[]Arraystring字符串標(biāo)量直通映射int32[]Arraynumber32 位整數(shù)標(biāo)量映射為number(red \| blue)[]Arrayred \| blue字符串字面量聯(lián)合映射為等價(jià)的字符串字面量聯(lián)合類型可以看到http-client-js發(fā)射器統(tǒng)一采用ArrayElementType作為數(shù)組的類型表達(dá)形式而不是string[]、number[]這樣的速記語法。這一風(fēng)格在同目錄的 basic.md單值標(biāo)量與聯(lián)合和 dictionary-properties.mdRecord 屬性場(chǎng)景中保持一致。2.2 數(shù)組表達(dá)式與標(biāo)量映射的實(shí)現(xiàn)位置ArrayT這種寫法并非發(fā)射器手寫拼接的字符串而是由 emitter-framework 提供的組件統(tǒng)一生成array-expression.tsx 中的ArrayExpression組件負(fù)責(zé)輸出Array...內(nèi)部遞歸調(diào)用TypeExpression渲染元素類型元素類型如string、int32的具體映射定義在 type-expression.tsx例如int32→number32-bit integer fits in JavaScripts numberint64→bigint64 位整數(shù)超出number安全范圍映射為大整數(shù)uint64→bigintsafeint→numberfloat/decimal/float32/float64等全部映射為number這意味著int32[]之所以變成Arraynumber根本原因在于int32標(biāo)量本身的映射是number數(shù)組只是在外層疊加了Array...容器。理解了這一層就能推導(dǎo)出int64[]會(huì)生成Arraybigint等衍生結(jié)論依據(jù)同一張映射表。2.3 聯(lián)合類型數(shù)組元素類型的遞歸渲染(red | blue)[]生成Arrayred | blue說明數(shù)組元素的類型渲染是遞歸的ArrayExpression只負(fù)責(zé)外層容器元素類型(red | blue)作為一個(gè)聯(lián)合類型Union繼續(xù)由TypeExpression處理最終渲染為red | blue的字面量聯(lián)合。這與場(chǎng)景文檔 basic.md 中模型屬性color: red | blue生成color: red | blue的規(guī)則完全一致——數(shù)組化只改變?nèi)萜鞑桓淖冊(cè)仡愋偷匿秩具壿嫛H?、核心?chǎng)景二Record 數(shù)組屬性原文檔的第二個(gè)場(chǎng)景演示了數(shù)組與 Record字典的組合namespace Test; model Widget { id: Recordint32[]; } op foo(): Widget;期望生成的 TypeScript 類型export interface Widget { id: ArrayRecordstring, number; }3.1 Record 表達(dá)式的生成Recordint32之所以成為Recordstring, number是因?yàn)?emitter-framework 的 record-expression.tsx 中RecordExpression固定以Recordstring, ElementType形式輸出——鍵類型恒為stringTypeSpec 的RecordT語義即鍵為字符串的映射值類型遞歸渲染元素類型這里int32映射為number。因此Recordint32[]的生成過程可以拆解為兩層內(nèi)層Recordint32→Recordstring, number外層(...)[]→ArrayRecordstring, number。3.2 與純 Record 屬性的對(duì)照同目錄的 dictionary-properties.md 展示了不套數(shù)組的對(duì)照?qǐng)鼍癛ecordint32屬性生成prop: Recordstring, numberRecordint32[]值類型為數(shù)組的字典生成prop: Recordstring, Arraynumber。把它與本文場(chǎng)景二對(duì)比可以看出數(shù)組與 Record 是可自由嵌套的兩個(gè)正交容器Array和Recordstring, ...在生成時(shí)可以任意組合TypeSpecTypeScriptRecordint32Recordstring, numberRecordint32[]Recordstring, ArraynumberRecordint32[]ArrayRecordstring, number四、模型文件生成models.tsx 與頂層類型過濾場(chǎng)景輸出統(tǒng)一落在src/models/models.ts文件中。這個(gè)文件由 models.tsx 組件生成它從useClientLibrary()獲取客戶端庫的數(shù)據(jù)類型集合dataTypes對(duì)每個(gè)數(shù)據(jù)類型通過 emitter-framework 的TypeDeclaration渲染出export interface ...聲明。值得注意的一個(gè)實(shí)現(xiàn)細(xì)節(jié)在 models.tsx 中頂層類型如果是$.array.is(type)數(shù)組或$.record.is(type)Record會(huì)被跳過不生成獨(dú)立聲明return $.array.is(type) || $.record.is(type) ? null : ( ef.TypeDeclaration export type{type} refkey{refkey(type)} / );這是因?yàn)閿?shù)組和 Record 都是內(nèi)聯(lián)容器類型它們只作為模型屬性的類型注解出現(xiàn)如id: Arraystring本身不需要也不應(yīng)該生成獨(dú)立的 TypeScript 接口。而像Widget這樣的具名模型則會(huì)生成export interface Widget。這一過濾邏輯解釋了為什么場(chǎng)景輸出中只有Widget一個(gè)接口而沒有額外的Array或Record聲明。五、數(shù)組的序列化與反序列化JsonTransform 分發(fā)與數(shù)組轉(zhuǎn)換函數(shù)類型聲明只是客戶端生成的一部分。typespec/http-client-js還會(huì)為模型生成 JSON 傳輸層的序列化/反序列化函數(shù)數(shù)組在這些函數(shù)中同樣有專門的轉(zhuǎn)換邏輯。5.1 JsonTransform 的分發(fā)邏輯json-transform.tsx 是轉(zhuǎn)換的入口它根據(jù) TypeSpec 類型的kind分發(fā)Model且是數(shù)組 →JsonArrayTransformModel且是 Record →JsonRecordTransform其他Model→JsonModelTransform遍歷模型屬性Union→JsonUnionTransformModelProperty→JsonModelPropertyTransformScalar→ScalarDataTransform。其中 json-model-property-transform.tsx 會(huì)先通過unpackProperty解包屬性類型unpack-model-property.ts 會(huì)遞歸解開ModelProperty引用、HttpPart以及僅含 null 的可空聯(lián)合再遞歸調(diào)用JsonTransform處理屬性值——于是數(shù)組屬性自然落入JsonArrayTransform。5.2 數(shù)組轉(zhuǎn)換函數(shù)的生成形態(tài)json-array-transform.tsx 展示了數(shù)組轉(zhuǎn)換的核心邏輯對(duì)每個(gè)元素遞歸執(zhí)行JsonTransform產(chǎn)出類似下面的函數(shù)該形態(tài)由場(chǎng)景文檔 serializers/arrays.md 給出了完整快照export function jsonArrayInt32ToTransportTransform(items_?: Arraynumber | null): any { if (!items_) { return items_ as any; } const _transformedArray []; for (const item of items_ ?? []) { const transformedItem item as any; _transformedArray.push(transformedItem); } return _transformedArray as any; }JsonArrayTransformDeclaration按json_Array_${elementName}_to_${target}_transform的命名規(guī)范生成函數(shù)名其中target為transport應(yīng)用模型 → 線上 JSON或application線上 JSON → 應(yīng)用模型。對(duì)于int32這類無需轉(zhuǎn)換的標(biāo)量元素轉(zhuǎn)換退化為直通見下節(jié)對(duì)于模型元素如Bar[]元素轉(zhuǎn)換會(huì)遞歸調(diào)用jsonBarToTransportTransform之類的模型轉(zhuǎn)換函數(shù)——這正是 serializers/arrays.md 中基礎(chǔ)類型數(shù)組的轉(zhuǎn)換冗余、復(fù)雜類型數(shù)組必須轉(zhuǎn)換兩種行為的根源。5.3 標(biāo)量轉(zhuǎn)換表為何 int32 是直通scalar-transform.tsx 定義了一張scalarTransformerMap為每種標(biāo)量登記toTransport/toApplication一對(duì)轉(zhuǎn)換函數(shù)。其中int32以及全部數(shù)值標(biāo)量、string、boolean、url等都注冊(cè)為passthroughTransformer——即原樣透?jìng)鞑划a(chǎn)生任何轉(zhuǎn)換調(diào)用。真正有轉(zhuǎn)換邏輯的標(biāo)量是bytes按base64/base64url編碼調(diào)用encodeUint8Array/decodeUint8ArrayutcDateTime按rfc3339/rfc7231/unixTimestamp選擇序列化與反序列化函數(shù)unixTimestamp32固定使用 Unix 時(shí)間戳序列化器。這解釋了為何int32[]的數(shù)組轉(zhuǎn)換函數(shù)體只是逐元素透?jìng)鳌獢?shù)組轉(zhuǎn)換的骨架判空、建新數(shù)組、遍歷依然生成但元素層無需任何編解碼。六、命名策略transport 名與 application 名在序列化函數(shù)中屬性名會(huì)按方向切換。json-model-property-transform.tsx 通過useTransformNamePolicy()獲取 transport 名與 application 名向transport方向時(shí)屬性名取線上編碼名如my_values向application方向時(shí)取應(yīng)用模型名如myValues。命名策略的默認(rèn)實(shí)現(xiàn)在 transform-name-policy.tsdefaultApplicationNameGetter用 TS 命名策略渲染屬性名camelCasedefaultTransportNameGetter通過$.type.getEncodedName(type, application/json)取編碼名若屬性是 HTTP 頭isHttpHeader則進(jìn)一步kebabCase。這也解釋了 serializers/arrays.md 中jsonFooToTransportTransform輸出my_values鍵、而jsonFooToApplicationTransform輸出myValues鍵的現(xiàn)象——數(shù)組元素本身不變但屬性名隨方向切換。七、如何運(yùn)行與驗(yàn)證7.1 安裝與發(fā)射typespec/http-client-js的安裝與用法見其 README.mdnpm install typespec/http-client-js命令行方式tsp compile . --emittypespec/http-client-js或在tspconfig.yaml中配置emit: - typespec/http-client-js options: typespec/http-client-js: option: value常用選項(xiàng)包括emitter-output-dir輸出目錄默認(rèn){output-dir}/typespec/http-client-js和package-name生成的 package.json 中的包名默認(rèn)test-package。7.2 運(yùn)行場(chǎng)景測(cè)試在倉(cāng)庫的packages/http-client-js目錄下該包 package.json 見 package.json可運(yùn)行pnpm test # vitest run包含 scenarios 場(chǎng)景測(cè)試 pnpm test:regen # RECORDtrue重新生成/更新場(chǎng)景快照?qǐng)鼍皽y(cè)試由 scenarios.test.ts 調(diào)用executeScenarios執(zhí)行它解析test/scenarios下所有.md文檔中標(biāo)注了src/...路徑的代碼塊作為期望輸出與實(shí)際發(fā)射結(jié)果比對(duì)。array-properties.md 正是被該測(cè)試框架消費(fèi)的規(guī)格文件之一——修改 TypeSpec 語法或發(fā)射器實(shí)現(xiàn)后回歸測(cè)試會(huì)自動(dòng)校驗(yàn)本文所述的兩條映射規(guī)則是否仍然成立。7.3 端到端驗(yàn)證除了快照式場(chǎng)景測(cè)試倉(cāng)庫還提供真實(shí) HTTP 往返的端到端測(cè)試。例如 array.test.ts 會(huì)啟動(dòng)測(cè)試服務(wù)端驗(yàn)證 int32 數(shù)組值[1, 2]經(jīng)生成的客戶端發(fā)送與接收后保持一致覆蓋了類型聲明正確之外的運(yùn)行時(shí)序列化正確這一層。八、小結(jié)數(shù)組屬性生成的行為契約結(jié)合 array-properties.md 與其配套源碼可以總結(jié)出typespec/http-client-js對(duì)數(shù)組屬性的完整行為契約類型層面數(shù)組屬性統(tǒng)一生成ArrayElementType元素類型遞歸映射int32→number、int64→bigint、字符串字面量聯(lián)合原樣保留RecordT生成Recordstring, T數(shù)組與 Record 可任意嵌套組合。聲明層面數(shù)組與 Record 作為內(nèi)聯(lián)容器類型不會(huì)生成獨(dú)立的 TypeScript 接口見 models.tsx只作為模型屬性的類型注解。序列化層面數(shù)組屬性在模型序列化函數(shù)中由jsonArray...Transform處理逐元素遞歸轉(zhuǎn)換基礎(chǔ)標(biāo)量元素直通透?jìng)鲝?fù)雜模型元素遞歸調(diào)用其模型轉(zhuǎn)換函數(shù)見 json-array-transform.tsx 與 serializers/arrays.md??沈?yàn)證性以上規(guī)則均被場(chǎng)景測(cè)試與 e2e 測(cè)試雙重覆蓋任何一處改動(dòng)都會(huì)在 CI 中被快照比對(duì)捕獲。這套以場(chǎng)景文檔為規(guī)格、以源碼實(shí)現(xiàn)為支撐、以測(cè)試為校驗(yàn)的組合正是閱讀 TypeSpec 生態(tài)倉(cāng)庫時(shí)理解發(fā)射器行為的高效路徑先看test/scenarios下的輸入輸出對(duì)再回到src/components中查找對(duì)應(yīng)實(shí)現(xiàn)最后用pnpm test驗(yàn)證你的理解?!久赓M(fèi)下載鏈接】typespec項(xiàng)目地址: https://gitcode.com/GitHub_Trending/ty/typespec創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考