From 467cd32105583c8cfde3972fd940182be8a1803d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:10:03 +0000 Subject: [PATCH 1/6] Initial plan From 1aa084074b9018043f76d63df1bce4ca600dbb7a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:23:30 +0000 Subject: [PATCH 2/6] Add CodeTransparency test spec to reproduce the issue Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com> --- .../Microsoft.CodeTransparency/spec/main.tsp | 480 ++++++++++++++++++ .../Microsoft.CodeTransparency/tspconfig.yaml | 11 + 2 files changed, 491 insertions(+) create mode 100644 packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp create mode 100644 packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp new file mode 100644 index 0000000000..6f4f114602 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp @@ -0,0 +1,480 @@ +import "@typespec/http"; +import "@typespec/rest"; +import "@typespec/versioning"; +import "@azure-tools/typespec-azure-core"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; +using Azure.Core; +using Azure.Core.Foundations; +using Azure.Core.Traits; + +#suppress "@azure-tools/typespec-autorest/unsupported-http-auth-scheme" +@service(#{ title: "Microsoft Code Transparency Service" }) +@useAuth(BearerAuth) +@server( + "{endpoint}", + "Code Transparency Service", + { + endpoint: url, + } +) +@versioned(Microsoft.CodeTransparency.Versions) +namespace Microsoft.CodeTransparency; + +@doc("The Microsoft.CodeTransparency service versions.") +enum Versions { + @useDependency(Azure.Core.Versions.v1_0_Preview_1) + // aligns with Feb 4 2025 SCRAPI spec https://github.com/ietf-wg-scitt/draft-ietf-scitt-scrapi/blob/4fafe531c21a3d1241934330bb771f82c64e0827/draft-ietf-scitt-scrapi.md + @doc("The 2025-01-31-preview version of the Microsoft.CodeTransparency service.") + `2025-01-31-preview`, +} + +// Additional supported traits by the API. +alias ServiceTraits = SupportsClientRequestId; + +#suppress "@azure-tools/typespec-azure-core/use-standard-operations" +@added(Versions.`2025-01-31-preview`) +@doc("Get the transparency service configuration, mandatory in IETF SCITT draft") +@route("/.well-known/transparency-configuration") +@get +op getTransparencyConfigCbor is Foundations.Operation< + AcceptCborHeader, + TransparencyConfigurationCbor, + ServiceTraits, + CborServerErrors +>; + +#suppress "@azure-tools/typespec-azure-core/response-schema-problem" "cannot assign error to directive to cbor binary responses" +#suppress "@azure-tools/typespec-azure-core/use-standard-operations" +@added(Versions.`2025-01-31-preview`) +@doc("Get the public keys used by the service to sign receipts, mentioned in IETF SCITT draft as part of jwks_uri implementation") +@route("/jwks") +op getPublicKeys is Foundations.Operation< + {}, + JwksDocument, + ServiceTraits, + AnyCborError +>; + +#suppress "@azure-tools/typespec-azure-core/use-standard-operations" +@added(Versions.`2025-01-31-preview`) +@doc("Post an entry to be registered on the CodeTransparency instance, mandatory in IETF SCITT draft") +@post +@route("/entries") +op createEntry is Foundations.Operation< + SignedStatement & AcceptCoseOrCborHeader, + CreateEntryCreated | CreateEntryPending, + ServiceTraits, + AnyCborError +>; + +#suppress "@azure-tools/typespec-azure-core/use-standard-operations" +@added(Versions.`2025-01-31-preview`) +@doc("Get status of the long running registration operation, mandatory in IETF SCITT draft") +@route("/operations/{operationId}") +op getOperation is Foundations.Operation< + OperationIdParameter & AcceptCborHeader, + OperationStatusComplete | OperationStatusPending, + ServiceTraits, + AnyCborError +>; + +#suppress "@azure-tools/typespec-azure-core/use-standard-operations" +@added(Versions.`2025-01-31-preview`) +@doc("Get receipt") +@route("/entries/{entryId}") +op getEntry is Foundations.Operation< + EntryIdParameter & AcceptCoseHeader, + ReceiptEntry, + ServiceTraits, + AnyCborError +>; + +#suppress "@azure-tools/typespec-azure-core/use-standard-operations" +@added(Versions.`2025-01-31-preview`) +@doc("Get the transparent statement consisting of the signed statement and the receipt embedded in the header") +@route("/entries/{entryId}/statement") +op getEntryStatement is Foundations.Operation< + EntryIdParameter & AcceptCoseHeader, + TransparentStatement, + ServiceTraits, + AnyCborError +>; + +@added(Versions.`2025-01-31-preview`) +@doc("Response of entry submission if the response can be served immediately, mandatory in IETF SCITT draft") +model CreateEntryCreated { + @doc("Status code") + @statusCode + statusCode: 201; + + @doc("The MIME content type a Cose body is application/cose, containing a CoseSign1 signature.") + @header("Content-Type") + contentType: "application/cose"; + + @doc("Location of the receipt") + @header("Location") + location?: string; + + @doc("Receipt body in COSE format") + @bodyRoot + body: bytes; +} + +@added(Versions.`2025-01-31-preview`) +@doc("Response of entry submission containing the operation id, mandatory in IETF SCITT draft") +model CreateEntryPending { + @doc("Status code") + @statusCode + statusCode: 202; + + @doc("Response content is in CBOR format") + @header("Content-Type") + contentType: "application/cbor"; + + @doc("Location of the operation") + @header("Location") + location?: string; + + @doc("Retry-After seconds value to help with polling") + @header("Retry-After") + retryAfter?: int32; + + // Example CBOR: + // { + // / locator / "OperationID": "67f89d5f0042e3ad42...35a1f190", + // / status / "Status": "running", + // } + // Status can be "running" or "succeeded" + @doc("Operation status response") + @bodyRoot + body: bytes; +} + +@doc("Provides status details for long running operation, mandatory in IETF SCITT draft") +model OperationStatusComplete { + @doc("Response content is in CBOR format") + @header("Content-Type") + contentType: "application/cbor"; + + @doc("Location of the entry") + @header("Location") + location?: string; + + // A success CBOR structure: + // { + // / locator / "EntryID": "67f89d5f0042e3ad42...35a1f190", + // / status / "Status": "succeeded", + // } + // Or an error CBOR structure: + // { + // / status / "Status": "failed", + // / error / "Error": { + // / title / -1: \ + // "Bad Signature Algorithm", + // / detail / -2: \ + // "Signed Statement contained a non supported algorithm" + // } + // } + @doc("Operation status response") + @bodyRoot + body: bytes; +} + +@doc("Pending response if the operation was not complete, mandatory in IETF SCITT draft") +model OperationStatusPending { + @doc("Status code") + @statusCode + statusCode: 202; + + @doc("Location of the operation") + @header("Location") + location?: string; + + @doc("Retry-After seconds value to help with polling") + @header("Retry-After") + retryAfter?: int32; +} + +@doc("Signed statement") +model SignedStatement { + @doc("The MIME content type a Cose body is application/cose, containing a CoseSign1 signature, see IETF SCITT draft") + @header("Content-Type") + contentType: "application/cose"; + + // Example CBOR: + // 18([ / COSE Sign1 / + // h'a101382...', / Protected Header / + // {}, / Unprotected Header / + // h'8f4211d...', / Payload / + // h'269cd68...' / Signature / + // ]) + @doc("CoseSign1 signature envelope") + @bodyRoot + body: bytes; +} + +@doc("Transparent statement") +model TransparentStatement { + @doc("The MIME content type a Cose body is application/cose, containing a CoseSign1 signature, see IETF SCITT draft") + @header("Content-Type") + contentType: "application/cose"; + + @doc("CoseSign1 signature envelope with the receipt embedded in the unprotected header") + @bodyRoot + body: bytes; +} + +@doc("A ledger receipt, see IETF SCITT draft") +model ReceiptEntry { + @doc("The MIME content type for receipt is application/cose.") + @added(Versions.`2025-01-31-preview`) + @header("Content-Type") + contentType: "application/cose"; + + @doc("Location of the receipt") + @header("Location") + location?: string; + + // Example CBOR: + // 18([ / COSE Sign1 / + // h'a101382...', / Protected Header / + // {}, / Unprotected Header / + // null, / Payload / + // h'269cd68...' / Signature / + // ]) + @doc("A receipt structure in COSE format") + @bodyRoot + body: bytes; +} + +@doc("The OperationId parameter.") +model OperationIdParameter { + @doc("ID of the operation to retrieve.") + @path + @maxLength(100) + operationId: string; +} + +@doc("The EntryId parameter.") +model EntryIdParameter { + @doc("ID of the entry to retrieve.") + @path + @maxLength(100) + entryId: string; +} + +@doc("Accept application/cose header") +model AcceptCoseOrCborHeader { + @doc("Accept header") + @header + accept: "application/cose; application/cbor"; +} + +@doc("Accept application/cose header") +model AcceptCoseHeader { + @doc("Accept header") + @header + accept: "application/cose"; +} + +@doc("Accept application/cbor header") +model AcceptCborHeader { + @doc("Accept header") + @header + accept: "application/cbor"; +} + +@added(Versions.`2025-01-31-preview`) +@doc("Transparency configuration, see IETF SCITT draft.") +model TransparencyConfigurationCbor { + @doc("Default content type is application/cbor.") + @header + contentType: "application/cbor"; + + // Example CBOR: + // { + // "issuer": "https://transparency.example", + // "jwks_uri": "https://transparency.example/jwks" + // } + @doc("CBOR content of the configuration object") + @body + body: bytes; +} + +@added(Versions.`2025-01-31-preview`) +@doc("A JWKS like document") +model JwksDocument { + @doc("Content type header") + @header + contentType: "application/json"; + + @doc("List of public keys used for receipt verification.") + keys: JsonWebKey[]; +} + +#suppress "@azure-tools/typespec-azure-core/casing-style" +@doc("rfc7517 JSON Web Key representation adapted from a shared swagger definition in the common types") +model JsonWebKey { + @doc("The \"alg\" (algorithm) parameter identifies the algorithm intended for\nuse with the key. The values used should either be registered in the\nIANA \"JSON Web Signature and Encryption Algorithms\" registry\nestablished by [JWA] or be a value that contains a Collision-\nResistant Name.") + alg?: string; + + @doc("The \"crv\" (curve) parameter identifies the curve type") + crv?: string; + + @doc("RSA private exponent or ECC private key") + d?: string; + + @doc("RSA Private Key Parameter") + dp?: string; + + @doc("RSA Private Key Parameter") + dq?: string; + + @doc("RSA public exponent, in Base64") + e?: string; + + @doc("Symmetric key") + k?: string; + + @doc("The \"kid\" (key ID) parameter is used to match a specific key. This\nis used, for instance, to choose among a set of keys within a JWK Set\nduring key rollover. The structure of the \"kid\" value is\nunspecified. When \"kid\" values are used within a JWK Set, different\nkeys within the JWK Set SHOULD use distinct \"kid\" values. (One\nexample in which different keys might use the same \"kid\" value is if\nthey have different \"kty\" (key type) values but are considered to be\nequivalent alternatives by the application using them.) The \"kid\"\nvalue is a case-sensitive string.") + kid?: string; + + @doc("The \"kty\" (key type) parameter identifies the cryptographic algorithm\nfamily used with the key, such as \"RSA\" or \"EC\". \"kty\" values should\neither be registered in the IANA \"JSON Web Key Types\" registry\nestablished by [JWA] or be a value that contains a Collision-\nResistant Name. The \"kty\" value is a case-sensitive string.") + kty: string; + + @doc("RSA modulus, in Base64") + n?: string; + + @doc("RSA secret prime") + p?: string; + + @doc("RSA secret prime, with p < q") + q?: string; + + @doc("RSA Private Key Parameter") + qi?: string; + + @doc("Use (\"public key use\") identifies the intended use of\nthe public key. The \"use\" parameter is employed to indicate whether\na public key is used for encrypting data or verifying the signature\non data. Values are commonly \"sig\" (signature) or \"enc\" (encryption).") + use?: string; + + @doc("X coordinate for the Elliptic Curve point") + x?: string; + + @doc("The \"x5c\" (X.509 certificate chain) parameter contains a chain of one\nor more PKIX certificates [RFC5280]. The certificate chain is\nrepresented as a JSON array of certificate value strings. Each\nstring in the array is a base64-encoded (Section 4 of [RFC4648] --\nnot base64url-encoded) DER [ITU.X690.1994] PKIX certificate value.\nThe PKIX certificate containing the key value MUST be the first\ncertificate.") + x5c?: Array; + + @doc("Y coordinate for the Elliptic Curve point") + y?: string; +} + +// ----------------------------------------------------------------- +// Error responses as per IETF draft +// ----------------------------------------------------------------- + +// Example CBOR: +// { +// / title / -1: \ +// "Operation Not Found", +// / detail / -2: \ +// "No running operation was found matching the requested ID" +// } +alias ConciseProblemDetailsCbor = bytes; + +@added(Versions.`2025-01-31-preview`) +@doc("Validation error response") +model Response400Cbor { + @doc("Status code") + @statusCode + statusCode: 400; + + @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") + @header + contentType: "application/concise-problem-details+cbor"; + + @doc("CBOR content of the error object") + @body + error: ConciseProblemDetailsCbor; +} + +@added(Versions.`2025-01-31-preview`) +@doc("Not found error response") +model Response404Cbor { + @doc("Status code") + @statusCode + statusCode: 404; + + @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") + @header + contentType: "application/concise-problem-details+cbor"; + + @doc("CBOR content of the error object") + @body + error: ConciseProblemDetailsCbor; +} + +@added(Versions.`2025-01-31-preview`) +@doc("Not found error response") +model Response429Cbor { + @doc("Status code") + @statusCode + statusCode: 429; + + @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") + @header + contentType: "application/concise-problem-details+cbor"; + + @doc("Retry-After seconds value to help with polling") + @header("Retry-After") + retryAfter?: int32; + + @doc("CBOR content of the error object") + @body + error: ConciseProblemDetailsCbor; +} + +@added(Versions.`2025-01-31-preview`) +@doc("Server error response") +model Response500Cbor { + @doc("Status code") + @statusCode + statusCode: 500; + + @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") + @header + contentType: "application/concise-problem-details+cbor"; + + @doc("CBOR content of the error object") + @body + error: ConciseProblemDetailsCbor; +} + +@added(Versions.`2025-01-31-preview`) +@doc("Service unavailable error response") +model Response503Cbor { + @doc("Retry the same request after a suggested number of seconds") + @header("Retry-After") + retryAfter?: int32; + + @doc("Status code") + @statusCode + statusCode: 503; + + @doc("The MIME content type for error, see RFC9290") + @header + contentType: "application/concise-problem-details+cbor"; + + @doc("CBOR content of the error object") + @body + error: ConciseProblemDetailsCbor; +} + +alias AnyCborError = + | Response400Cbor + | Response404Cbor + | Response429Cbor + | Response500Cbor + | Response503Cbor; +alias CborServerErrors = Response500Cbor | Response503Cbor; \ No newline at end of file diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml b/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml new file mode 100644 index 0000000000..a8ffee16ea --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml @@ -0,0 +1,11 @@ +emit: + - "@azure-tools/typespec-ts" +options: + "@azure-tools/typespec-ts": + title: Code Transparency Service + azure-sdk-for-js: false + is-modular-library: true + emitter-output-dir: "{project-root}/generated/typespec-ts" + package-details: + name: "@azure-rest/codetransparency" + description: "Code Transparency Service" \ No newline at end of file From 364dc03f1715e8b776bec01e61b4b1ccc1b3d215 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:30:04 +0000 Subject: [PATCH 3/6] Fix null/undefined handling for binary payload detection Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com> --- .../src/modular/helpers/operationHelpers.ts | 14 +++++++++++--- packages/typespec-ts/src/utils/modelUtils.ts | 8 ++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts index b751a0e349..c47a4359d3 100644 --- a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts @@ -228,14 +228,22 @@ export function getDeserializePrivateFunction( } else if (isAzureCoreErrorType(context.program, deserializedType.__raw)) { statements.push(`return ${deserializedRoot}`); } else { + // Determine if the response contains binary payload + // Use __raw if it exists and is a Type, otherwise fall back to the type itself + const rawType = (response.type as any)?.__raw; + const responseType = rawType && typeof rawType === 'object' && 'kind' in rawType + ? rawType + : response.type; + const isBinary = responseType && contentTypes + ? isBinaryPayload(context, responseType as any, contentTypes) + : false; + statements.push( `return ${deserializeResponseValue( context, deserializedType, deserializedRoot, - isBinaryPayload(context, response.type!.__raw!, contentTypes!) - ? "binary" - : getEncodeForType(deserializedType) + isBinary ? "binary" : getEncodeForType(deserializedType) )}` ); } diff --git a/packages/typespec-ts/src/utils/modelUtils.ts b/packages/typespec-ts/src/utils/modelUtils.ts index 8df827621a..f5761acdb0 100644 --- a/packages/typespec-ts/src/utils/modelUtils.ts +++ b/packages/typespec-ts/src/utils/modelUtils.ts @@ -99,19 +99,23 @@ export function getBinaryType(usage: SchemaContext[]) { } export function isByteOrByteUnion(dpgContext: SdkContext, type: Type) { + if (!type) { + return false; + } const schema = getSchemaForType(dpgContext, type); return isBytesType(schema) || isBytesUnion(schema); } function isBytesType(schema: any) { return ( + schema && schema.type === "string" && (schema.format === "bytes" || schema.format === "binary") ); } function isBytesUnion(schema: any) { - if (!Array.isArray(schema.enum)) { + if (!schema || !Array.isArray(schema.enum)) { return false; } for (const ele of schema.enum) { @@ -289,7 +293,7 @@ export function getEffectiveModelFromType( * If type is an anonymous model, tries to find a named model that has the same * set of properties when non-schema properties are excluded. */ - if (type.kind === "Model" && type.name === "") { + if (type && type.kind === "Model" && type.name === "") { const effective = getEffectiveModelType(context.program, type, (property) => isSchemaProperty(context.program, property) ); From 3b04a10f53a704b5dc6aeb5fa4c2f064d774db5e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:38:59 +0000 Subject: [PATCH 4/6] Add unit test for binary payload with application/cose content type Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com> --- .../test/modularUnit/operations.spec.ts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/packages/typespec-ts/test/modularUnit/operations.spec.ts b/packages/typespec-ts/test/modularUnit/operations.spec.ts index 14899f8fdf..a765d89636 100644 --- a/packages/typespec-ts/test/modularUnit/operations.spec.ts +++ b/packages/typespec-ts/test/modularUnit/operations.spec.ts @@ -47,4 +47,45 @@ describe("operations", () => { } }); }); + + describe("binary payload with application/cose", () => { + it("should handle binary response with application/cose content type without crashing", async () => { + const tspContent = ` + @doc("Signed statement") + model SignedStatement { + @doc("The MIME content type a Cose body is application/cose") + @header("Content-Type") + contentType: "application/cose"; + + @doc("CoseSign1 signature envelope") + @bodyRoot + body: bytes; + } + + @doc("Response with COSE binary content") + model CoseResponse { + @doc("Status code") + @statusCode + statusCode: 201; + + @doc("The MIME content type a Cose body is application/cose") + @header("Content-Type") + contentType: "application/cose"; + + @doc("Receipt body in COSE format") + @bodyRoot + body: bytes; + } + + @post + op createEntry(...SignedStatement): CoseResponse; + `; + + // This should not throw an error - previously would crash with "Cannot read properties of undefined (reading 'kind')" + const result = await emitModularOperationsFromTypeSpec(tspContent); + assert.ok(result); + // Verify that operations were generated successfully + assert.ok(result!.length > 0); + }); + }); }); From 6442d40549bc97452acc17d71ce801af32c81ebe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 03:40:31 +0000 Subject: [PATCH 5/6] Format code with prettier Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com> --- .../src/modular/helpers/operationHelpers.ts | 16 +++++++++------- .../test/modularUnit/operations.spec.ts | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts index c47a4359d3..606036ebd1 100644 --- a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts @@ -231,13 +231,15 @@ export function getDeserializePrivateFunction( // Determine if the response contains binary payload // Use __raw if it exists and is a Type, otherwise fall back to the type itself const rawType = (response.type as any)?.__raw; - const responseType = rawType && typeof rawType === 'object' && 'kind' in rawType - ? rawType - : response.type; - const isBinary = responseType && contentTypes - ? isBinaryPayload(context, responseType as any, contentTypes) - : false; - + const responseType = + rawType && typeof rawType === "object" && "kind" in rawType + ? rawType + : response.type; + const isBinary = + responseType && contentTypes + ? isBinaryPayload(context, responseType as any, contentTypes) + : false; + statements.push( `return ${deserializeResponseValue( context, diff --git a/packages/typespec-ts/test/modularUnit/operations.spec.ts b/packages/typespec-ts/test/modularUnit/operations.spec.ts index a765d89636..9b21c9da9d 100644 --- a/packages/typespec-ts/test/modularUnit/operations.spec.ts +++ b/packages/typespec-ts/test/modularUnit/operations.spec.ts @@ -47,7 +47,7 @@ describe("operations", () => { } }); }); - + describe("binary payload with application/cose", () => { it("should handle binary response with application/cose content type without crashing", async () => { const tspContent = ` From 7a0da3bb12b09060f2ce42e5a7d3300ca6018e38 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 Aug 2025 04:21:07 +0000 Subject: [PATCH 6/6] Add comprehensive test cases for binary COSE response handling Co-authored-by: MaryGao <9943211+MaryGao@users.noreply.github.com> --- .../Microsoft.CodeTransparency/spec/main.tsp | 480 ------------------ .../Microsoft.CodeTransparency/tspconfig.yaml | 11 - .../generated/typespec-ts/package.json | 128 +---- .../typespec-ts/review/widget_dpg.api.md | 178 ------- .../generated/typespec-ts/src/package.json | 3 + .../test/modularUnit/operations.spec.ts | 56 ++ .../scenarios/operations/operations.md | 130 +++++ 7 files changed, 196 insertions(+), 790 deletions(-) delete mode 100644 packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp delete mode 100644 packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml delete mode 100644 packages/typespec-test/test/widget_dpg/generated/typespec-ts/review/widget_dpg.api.md create mode 100644 packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/package.json diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp deleted file mode 100644 index 6f4f114602..0000000000 --- a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp +++ /dev/null @@ -1,480 +0,0 @@ -import "@typespec/http"; -import "@typespec/rest"; -import "@typespec/versioning"; -import "@azure-tools/typespec-azure-core"; - -using TypeSpec.Http; -using TypeSpec.Rest; -using TypeSpec.Versioning; -using Azure.Core; -using Azure.Core.Foundations; -using Azure.Core.Traits; - -#suppress "@azure-tools/typespec-autorest/unsupported-http-auth-scheme" -@service(#{ title: "Microsoft Code Transparency Service" }) -@useAuth(BearerAuth) -@server( - "{endpoint}", - "Code Transparency Service", - { - endpoint: url, - } -) -@versioned(Microsoft.CodeTransparency.Versions) -namespace Microsoft.CodeTransparency; - -@doc("The Microsoft.CodeTransparency service versions.") -enum Versions { - @useDependency(Azure.Core.Versions.v1_0_Preview_1) - // aligns with Feb 4 2025 SCRAPI spec https://github.com/ietf-wg-scitt/draft-ietf-scitt-scrapi/blob/4fafe531c21a3d1241934330bb771f82c64e0827/draft-ietf-scitt-scrapi.md - @doc("The 2025-01-31-preview version of the Microsoft.CodeTransparency service.") - `2025-01-31-preview`, -} - -// Additional supported traits by the API. -alias ServiceTraits = SupportsClientRequestId; - -#suppress "@azure-tools/typespec-azure-core/use-standard-operations" -@added(Versions.`2025-01-31-preview`) -@doc("Get the transparency service configuration, mandatory in IETF SCITT draft") -@route("/.well-known/transparency-configuration") -@get -op getTransparencyConfigCbor is Foundations.Operation< - AcceptCborHeader, - TransparencyConfigurationCbor, - ServiceTraits, - CborServerErrors ->; - -#suppress "@azure-tools/typespec-azure-core/response-schema-problem" "cannot assign error to directive to cbor binary responses" -#suppress "@azure-tools/typespec-azure-core/use-standard-operations" -@added(Versions.`2025-01-31-preview`) -@doc("Get the public keys used by the service to sign receipts, mentioned in IETF SCITT draft as part of jwks_uri implementation") -@route("/jwks") -op getPublicKeys is Foundations.Operation< - {}, - JwksDocument, - ServiceTraits, - AnyCborError ->; - -#suppress "@azure-tools/typespec-azure-core/use-standard-operations" -@added(Versions.`2025-01-31-preview`) -@doc("Post an entry to be registered on the CodeTransparency instance, mandatory in IETF SCITT draft") -@post -@route("/entries") -op createEntry is Foundations.Operation< - SignedStatement & AcceptCoseOrCborHeader, - CreateEntryCreated | CreateEntryPending, - ServiceTraits, - AnyCborError ->; - -#suppress "@azure-tools/typespec-azure-core/use-standard-operations" -@added(Versions.`2025-01-31-preview`) -@doc("Get status of the long running registration operation, mandatory in IETF SCITT draft") -@route("/operations/{operationId}") -op getOperation is Foundations.Operation< - OperationIdParameter & AcceptCborHeader, - OperationStatusComplete | OperationStatusPending, - ServiceTraits, - AnyCborError ->; - -#suppress "@azure-tools/typespec-azure-core/use-standard-operations" -@added(Versions.`2025-01-31-preview`) -@doc("Get receipt") -@route("/entries/{entryId}") -op getEntry is Foundations.Operation< - EntryIdParameter & AcceptCoseHeader, - ReceiptEntry, - ServiceTraits, - AnyCborError ->; - -#suppress "@azure-tools/typespec-azure-core/use-standard-operations" -@added(Versions.`2025-01-31-preview`) -@doc("Get the transparent statement consisting of the signed statement and the receipt embedded in the header") -@route("/entries/{entryId}/statement") -op getEntryStatement is Foundations.Operation< - EntryIdParameter & AcceptCoseHeader, - TransparentStatement, - ServiceTraits, - AnyCborError ->; - -@added(Versions.`2025-01-31-preview`) -@doc("Response of entry submission if the response can be served immediately, mandatory in IETF SCITT draft") -model CreateEntryCreated { - @doc("Status code") - @statusCode - statusCode: 201; - - @doc("The MIME content type a Cose body is application/cose, containing a CoseSign1 signature.") - @header("Content-Type") - contentType: "application/cose"; - - @doc("Location of the receipt") - @header("Location") - location?: string; - - @doc("Receipt body in COSE format") - @bodyRoot - body: bytes; -} - -@added(Versions.`2025-01-31-preview`) -@doc("Response of entry submission containing the operation id, mandatory in IETF SCITT draft") -model CreateEntryPending { - @doc("Status code") - @statusCode - statusCode: 202; - - @doc("Response content is in CBOR format") - @header("Content-Type") - contentType: "application/cbor"; - - @doc("Location of the operation") - @header("Location") - location?: string; - - @doc("Retry-After seconds value to help with polling") - @header("Retry-After") - retryAfter?: int32; - - // Example CBOR: - // { - // / locator / "OperationID": "67f89d5f0042e3ad42...35a1f190", - // / status / "Status": "running", - // } - // Status can be "running" or "succeeded" - @doc("Operation status response") - @bodyRoot - body: bytes; -} - -@doc("Provides status details for long running operation, mandatory in IETF SCITT draft") -model OperationStatusComplete { - @doc("Response content is in CBOR format") - @header("Content-Type") - contentType: "application/cbor"; - - @doc("Location of the entry") - @header("Location") - location?: string; - - // A success CBOR structure: - // { - // / locator / "EntryID": "67f89d5f0042e3ad42...35a1f190", - // / status / "Status": "succeeded", - // } - // Or an error CBOR structure: - // { - // / status / "Status": "failed", - // / error / "Error": { - // / title / -1: \ - // "Bad Signature Algorithm", - // / detail / -2: \ - // "Signed Statement contained a non supported algorithm" - // } - // } - @doc("Operation status response") - @bodyRoot - body: bytes; -} - -@doc("Pending response if the operation was not complete, mandatory in IETF SCITT draft") -model OperationStatusPending { - @doc("Status code") - @statusCode - statusCode: 202; - - @doc("Location of the operation") - @header("Location") - location?: string; - - @doc("Retry-After seconds value to help with polling") - @header("Retry-After") - retryAfter?: int32; -} - -@doc("Signed statement") -model SignedStatement { - @doc("The MIME content type a Cose body is application/cose, containing a CoseSign1 signature, see IETF SCITT draft") - @header("Content-Type") - contentType: "application/cose"; - - // Example CBOR: - // 18([ / COSE Sign1 / - // h'a101382...', / Protected Header / - // {}, / Unprotected Header / - // h'8f4211d...', / Payload / - // h'269cd68...' / Signature / - // ]) - @doc("CoseSign1 signature envelope") - @bodyRoot - body: bytes; -} - -@doc("Transparent statement") -model TransparentStatement { - @doc("The MIME content type a Cose body is application/cose, containing a CoseSign1 signature, see IETF SCITT draft") - @header("Content-Type") - contentType: "application/cose"; - - @doc("CoseSign1 signature envelope with the receipt embedded in the unprotected header") - @bodyRoot - body: bytes; -} - -@doc("A ledger receipt, see IETF SCITT draft") -model ReceiptEntry { - @doc("The MIME content type for receipt is application/cose.") - @added(Versions.`2025-01-31-preview`) - @header("Content-Type") - contentType: "application/cose"; - - @doc("Location of the receipt") - @header("Location") - location?: string; - - // Example CBOR: - // 18([ / COSE Sign1 / - // h'a101382...', / Protected Header / - // {}, / Unprotected Header / - // null, / Payload / - // h'269cd68...' / Signature / - // ]) - @doc("A receipt structure in COSE format") - @bodyRoot - body: bytes; -} - -@doc("The OperationId parameter.") -model OperationIdParameter { - @doc("ID of the operation to retrieve.") - @path - @maxLength(100) - operationId: string; -} - -@doc("The EntryId parameter.") -model EntryIdParameter { - @doc("ID of the entry to retrieve.") - @path - @maxLength(100) - entryId: string; -} - -@doc("Accept application/cose header") -model AcceptCoseOrCborHeader { - @doc("Accept header") - @header - accept: "application/cose; application/cbor"; -} - -@doc("Accept application/cose header") -model AcceptCoseHeader { - @doc("Accept header") - @header - accept: "application/cose"; -} - -@doc("Accept application/cbor header") -model AcceptCborHeader { - @doc("Accept header") - @header - accept: "application/cbor"; -} - -@added(Versions.`2025-01-31-preview`) -@doc("Transparency configuration, see IETF SCITT draft.") -model TransparencyConfigurationCbor { - @doc("Default content type is application/cbor.") - @header - contentType: "application/cbor"; - - // Example CBOR: - // { - // "issuer": "https://transparency.example", - // "jwks_uri": "https://transparency.example/jwks" - // } - @doc("CBOR content of the configuration object") - @body - body: bytes; -} - -@added(Versions.`2025-01-31-preview`) -@doc("A JWKS like document") -model JwksDocument { - @doc("Content type header") - @header - contentType: "application/json"; - - @doc("List of public keys used for receipt verification.") - keys: JsonWebKey[]; -} - -#suppress "@azure-tools/typespec-azure-core/casing-style" -@doc("rfc7517 JSON Web Key representation adapted from a shared swagger definition in the common types") -model JsonWebKey { - @doc("The \"alg\" (algorithm) parameter identifies the algorithm intended for\nuse with the key. The values used should either be registered in the\nIANA \"JSON Web Signature and Encryption Algorithms\" registry\nestablished by [JWA] or be a value that contains a Collision-\nResistant Name.") - alg?: string; - - @doc("The \"crv\" (curve) parameter identifies the curve type") - crv?: string; - - @doc("RSA private exponent or ECC private key") - d?: string; - - @doc("RSA Private Key Parameter") - dp?: string; - - @doc("RSA Private Key Parameter") - dq?: string; - - @doc("RSA public exponent, in Base64") - e?: string; - - @doc("Symmetric key") - k?: string; - - @doc("The \"kid\" (key ID) parameter is used to match a specific key. This\nis used, for instance, to choose among a set of keys within a JWK Set\nduring key rollover. The structure of the \"kid\" value is\nunspecified. When \"kid\" values are used within a JWK Set, different\nkeys within the JWK Set SHOULD use distinct \"kid\" values. (One\nexample in which different keys might use the same \"kid\" value is if\nthey have different \"kty\" (key type) values but are considered to be\nequivalent alternatives by the application using them.) The \"kid\"\nvalue is a case-sensitive string.") - kid?: string; - - @doc("The \"kty\" (key type) parameter identifies the cryptographic algorithm\nfamily used with the key, such as \"RSA\" or \"EC\". \"kty\" values should\neither be registered in the IANA \"JSON Web Key Types\" registry\nestablished by [JWA] or be a value that contains a Collision-\nResistant Name. The \"kty\" value is a case-sensitive string.") - kty: string; - - @doc("RSA modulus, in Base64") - n?: string; - - @doc("RSA secret prime") - p?: string; - - @doc("RSA secret prime, with p < q") - q?: string; - - @doc("RSA Private Key Parameter") - qi?: string; - - @doc("Use (\"public key use\") identifies the intended use of\nthe public key. The \"use\" parameter is employed to indicate whether\na public key is used for encrypting data or verifying the signature\non data. Values are commonly \"sig\" (signature) or \"enc\" (encryption).") - use?: string; - - @doc("X coordinate for the Elliptic Curve point") - x?: string; - - @doc("The \"x5c\" (X.509 certificate chain) parameter contains a chain of one\nor more PKIX certificates [RFC5280]. The certificate chain is\nrepresented as a JSON array of certificate value strings. Each\nstring in the array is a base64-encoded (Section 4 of [RFC4648] --\nnot base64url-encoded) DER [ITU.X690.1994] PKIX certificate value.\nThe PKIX certificate containing the key value MUST be the first\ncertificate.") - x5c?: Array; - - @doc("Y coordinate for the Elliptic Curve point") - y?: string; -} - -// ----------------------------------------------------------------- -// Error responses as per IETF draft -// ----------------------------------------------------------------- - -// Example CBOR: -// { -// / title / -1: \ -// "Operation Not Found", -// / detail / -2: \ -// "No running operation was found matching the requested ID" -// } -alias ConciseProblemDetailsCbor = bytes; - -@added(Versions.`2025-01-31-preview`) -@doc("Validation error response") -model Response400Cbor { - @doc("Status code") - @statusCode - statusCode: 400; - - @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") - @header - contentType: "application/concise-problem-details+cbor"; - - @doc("CBOR content of the error object") - @body - error: ConciseProblemDetailsCbor; -} - -@added(Versions.`2025-01-31-preview`) -@doc("Not found error response") -model Response404Cbor { - @doc("Status code") - @statusCode - statusCode: 404; - - @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") - @header - contentType: "application/concise-problem-details+cbor"; - - @doc("CBOR content of the error object") - @body - error: ConciseProblemDetailsCbor; -} - -@added(Versions.`2025-01-31-preview`) -@doc("Not found error response") -model Response429Cbor { - @doc("Status code") - @statusCode - statusCode: 429; - - @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") - @header - contentType: "application/concise-problem-details+cbor"; - - @doc("Retry-After seconds value to help with polling") - @header("Retry-After") - retryAfter?: int32; - - @doc("CBOR content of the error object") - @body - error: ConciseProblemDetailsCbor; -} - -@added(Versions.`2025-01-31-preview`) -@doc("Server error response") -model Response500Cbor { - @doc("Status code") - @statusCode - statusCode: 500; - - @doc("The MIME content type for error is application/concise-problem-details+cbor, see RFC9290") - @header - contentType: "application/concise-problem-details+cbor"; - - @doc("CBOR content of the error object") - @body - error: ConciseProblemDetailsCbor; -} - -@added(Versions.`2025-01-31-preview`) -@doc("Service unavailable error response") -model Response503Cbor { - @doc("Retry the same request after a suggested number of seconds") - @header("Retry-After") - retryAfter?: int32; - - @doc("Status code") - @statusCode - statusCode: 503; - - @doc("The MIME content type for error, see RFC9290") - @header - contentType: "application/concise-problem-details+cbor"; - - @doc("CBOR content of the error object") - @body - error: ConciseProblemDetailsCbor; -} - -alias AnyCborError = - | Response400Cbor - | Response404Cbor - | Response429Cbor - | Response500Cbor - | Response503Cbor; -alias CborServerErrors = Response500Cbor | Response503Cbor; \ No newline at end of file diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml b/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml deleted file mode 100644 index a8ffee16ea..0000000000 --- a/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml +++ /dev/null @@ -1,11 +0,0 @@ -emit: - - "@azure-tools/typespec-ts" -options: - "@azure-tools/typespec-ts": - title: Code Transparency Service - azure-sdk-for-js: false - is-modular-library: true - emitter-output-dir: "{project-root}/generated/typespec-ts" - package-details: - name: "@azure-rest/codetransparency" - description: "Code Transparency Service" \ No newline at end of file diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/package.json b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/package.json index 4447688190..07caff2d01 100644 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/package.json +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/package.json @@ -12,39 +12,21 @@ "./package.json": "./package.json", ".": "./src/index.ts", "./api": "./src/api/index.ts", - "./api/budgets": "./src/api/budgets/index.ts", - "./api/sapWidgets": "./src/api/sapWidgets/index.ts", + "./api/budgets": "src/api/budgets/index.ts", + "./api/sapWidgets": "src/api/sapWidgets/index.ts", "./models": "./src/models/index.ts" }, - "dialects": [ - "esm", - "commonjs" - ], - "esmDialects": [ - "browser", - "react-native" - ], + "dialects": ["esm", "commonjs"], + "esmDialects": ["browser", "react-native"], "selfLink": false }, "type": "module", "browser": "./dist/browser/index.js", "react-native": "./dist/react-native/index.js", - "keywords": [ - "node", - "azure", - "cloud", - "typescript", - "browser", - "isomorphic" - ], + "keywords": ["node", "azure", "cloud", "typescript", "browser", "isomorphic"], "author": "Microsoft Corporation", "license": "MIT", - "files": [ - "dist/", - "!dist/**/*.d.*ts.map", - "README.md", - "LICENSE" - ], + "files": ["dist/", "!dist/**/*.d.*ts.map", "README.md", "LICENSE"], "dependencies": { "@azure/core-util": "^1.9.2", "@azure-rest/core-client": "^2.3.1", @@ -72,101 +54,5 @@ "lint": "eslint package.json api-extractor.json src", "lint:fix": "eslint package.json api-extractor.json src --fix --fix-type [problem,suggestion]", "build": "npm run clean && tshy && npm run extract-api" - }, - "exports": { - "./package.json": "./package.json", - ".": { - "browser": { - "types": "./dist/browser/index.d.ts", - "default": "./dist/browser/index.js" - }, - "react-native": { - "types": "./dist/react-native/index.d.ts", - "default": "./dist/react-native/index.js" - }, - "import": { - "types": "./dist/esm/index.d.ts", - "default": "./dist/esm/index.js" - }, - "require": { - "types": "./dist/commonjs/index.d.ts", - "default": "./dist/commonjs/index.js" - } - }, - "./api": { - "browser": { - "types": "./dist/browser/api/index.d.ts", - "default": "./dist/browser/api/index.js" - }, - "react-native": { - "types": "./dist/react-native/api/index.d.ts", - "default": "./dist/react-native/api/index.js" - }, - "import": { - "types": "./dist/esm/api/index.d.ts", - "default": "./dist/esm/api/index.js" - }, - "require": { - "types": "./dist/commonjs/api/index.d.ts", - "default": "./dist/commonjs/api/index.js" - } - }, - "./api/budgets": { - "browser": { - "types": "./dist/browser/api/budgets/index.d.ts", - "default": "./dist/browser/api/budgets/index.js" - }, - "react-native": { - "types": "./dist/react-native/api/budgets/index.d.ts", - "default": "./dist/react-native/api/budgets/index.js" - }, - "import": { - "types": "./dist/esm/api/budgets/index.d.ts", - "default": "./dist/esm/api/budgets/index.js" - }, - "require": { - "types": "./dist/commonjs/api/budgets/index.d.ts", - "default": "./dist/commonjs/api/budgets/index.js" - } - }, - "./api/sapWidgets": { - "browser": { - "types": "./dist/browser/api/sapWidgets/index.d.ts", - "default": "./dist/browser/api/sapWidgets/index.js" - }, - "react-native": { - "types": "./dist/react-native/api/sapWidgets/index.d.ts", - "default": "./dist/react-native/api/sapWidgets/index.js" - }, - "import": { - "types": "./dist/esm/api/sapWidgets/index.d.ts", - "default": "./dist/esm/api/sapWidgets/index.js" - }, - "require": { - "types": "./dist/commonjs/api/sapWidgets/index.d.ts", - "default": "./dist/commonjs/api/sapWidgets/index.js" - } - }, - "./models": { - "browser": { - "types": "./dist/browser/models/index.d.ts", - "default": "./dist/browser/models/index.js" - }, - "react-native": { - "types": "./dist/react-native/models/index.d.ts", - "default": "./dist/react-native/models/index.js" - }, - "import": { - "types": "./dist/esm/models/index.d.ts", - "default": "./dist/esm/models/index.js" - }, - "require": { - "types": "./dist/commonjs/models/index.d.ts", - "default": "./dist/commonjs/models/index.js" - } - } - }, - "main": "./dist/commonjs/index.js", - "types": "./dist/commonjs/index.d.ts", - "module": "./dist/esm/index.js" + } } diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/review/widget_dpg.api.md b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/review/widget_dpg.api.md deleted file mode 100644 index 6a15531a5a..0000000000 --- a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/review/widget_dpg.api.md +++ /dev/null @@ -1,178 +0,0 @@ -## API Report File for "@msinternal/widget_dpg" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { AbortSignalLike } from '@azure/abort-controller'; -import { ClientOptions } from '@azure-rest/core-client'; -import { KeyCredential } from '@azure/core-auth'; -import { OperationOptions } from '@azure-rest/core-client'; -import { OperationState } from '@azure/core-lro'; -import { PathUncheckedResponse } from '@azure-rest/core-client'; -import { Pipeline } from '@azure/core-rest-pipeline'; -import { PollerLike } from '@azure/core-lro'; - -// @public -export interface AnalyzeResult { - // (undocumented) - summary: string; -} - -// @public -export interface BudgetsContinueOptionalParams extends OperationOptions { -} - -// @public -export interface BudgetsCreateOrReplaceOptionalParams extends OperationOptions { - updateIntervalInMs?: number; -} - -// @public -export interface BudgetsGetBudgetsOptionalParams extends OperationOptions { -} - -// @public -export interface BudgetsOperations { - continue: (options?: BudgetsContinueOptionalParams) => Promise; - createOrReplace: (name: string, resource: SAPUser, options?: BudgetsCreateOrReplaceOptionalParams) => PollerLike, SAPUser>; - // (undocumented) - getBudgets: (name: string, options?: BudgetsGetBudgetsOptionalParams) => Promise; -} - -// @public -export type ContinuablePage = TPage & { - continuationToken?: string; -}; - -// @public -export enum KnownVersions { - _100 = "1.0.0" -} - -// @public -export interface NonReferencedModel { - prop1: number; - prop2: string; -} - -// @public -export interface PagedAsyncIterableIterator { - [Symbol.asyncIterator](): PagedAsyncIterableIterator; - byPage: (settings?: TPageSettings) => AsyncIterableIterator>; - next(): Promise>; -} - -// @public -export interface PageSettings { - continuationToken?: string; -} - -// @public -export function restorePoller(client: SAPWidgetServiceClient, serializedState: string, sourceOperation: (...args: any[]) => PollerLike, TResult>, options?: RestorePollerOptions): PollerLike, TResult>; - -// @public (undocumented) -export interface RestorePollerOptions extends OperationOptions { - abortSignal?: AbortSignalLike; - processResponseBody?: (result: TResponse) => Promise; - updateIntervalInMs?: number; -} - -// @public -export interface SAPUser { - id: string; - readonly name: string; - role: string; -} - -// @public -export interface SAPWidgetsAnalyzeWidgetOptionalParams extends OperationOptions { -} - -// @public -export interface SAPWidgetsCreateOrReplaceOptionalParams extends OperationOptions { - updateIntervalInMs?: number; -} - -// @public -export interface SAPWidgetsCreateWidgetOptionalParams extends OperationOptions { -} - -// @public -export interface SAPWidgetsDeleteWidgetOptionalParams extends OperationOptions { -} - -// @public (undocumented) -export class SAPWidgetServiceClient { - constructor(endpointParam: string, credential: KeyCredential, options?: SAPWidgetServiceClientOptionalParams); - readonly budgets: BudgetsOperations; - readonly pipeline: Pipeline; - readonly sapWidgets: SAPWidgetsOperations; -} - -// @public -export interface SAPWidgetServiceClientOptionalParams extends ClientOptions { - apiVersion?: string; -} - -// @public -export interface SAPWidgetsGetWidgetOptionalParams extends OperationOptions { -} - -// @public -export interface SAPWidgetsListWidgetsPagesOptionalParams extends OperationOptions { -} - -// @public -export interface SAPWidgetsOperations { - analyzeWidget: (id: string, options?: SAPWidgetsAnalyzeWidgetOptionalParams) => Promise; - createOrReplace: (name: string, resource: SAPUser, options?: SAPWidgetsCreateOrReplaceOptionalParams) => PollerLike, SAPUser>; - createWidget: (weight: number, color: "red" | "blue", options?: SAPWidgetsCreateWidgetOptionalParams) => Promise; - deleteWidget: (id: string, options?: SAPWidgetsDeleteWidgetOptionalParams) => Promise; - getWidget: (id: string, options?: SAPWidgetsGetWidgetOptionalParams) => Promise; - // (undocumented) - listWidgetsPages: (page: number, pageSize: number, options?: SAPWidgetsListWidgetsPagesOptionalParams) => PagedAsyncIterableIterator; - // (undocumented) - queryWidgetsPages: (page: number, pageSize: number, options?: SAPWidgetsQueryWidgetsPagesOptionalParams) => PagedAsyncIterableIterator; - sapListWidgets: (requiredHeader: string, bytesHeader: Uint8Array, value: Uint8Array, csvArrayHeader: Uint8Array[], utcDateHeader: Date, options?: SAPWidgetsSAPListWidgetsOptionalParams) => Promise; - updateWidget: (id: string, options?: SAPWidgetsUpdateWidgetOptionalParams) => Promise; -} - -// @public -export interface SAPWidgetsQueryWidgetsPagesOptionalParams extends OperationOptions { -} - -// @public -export interface SAPWidgetsSAPListWidgetsOptionalParams extends OperationOptions { - // (undocumented) - nullableDateHeader?: Date | null; - // (undocumented) - nullableOptionalHeader?: string | null; - // (undocumented) - optionalDateHeader?: Date; - // (undocumented) - optionalHeader?: string; -} - -// @public -export interface SAPWidgetsUpdateWidgetOptionalParams extends OperationOptions { - color?: "red" | "blue"; - weight?: number; -} - -// @public -export interface Widget { - color: "red" | "blue"; - id: string; - weight: number; -} - -// @public -export interface WidgetError { - code: number; - message: string; -} - -// (No @packageDocumentation comment for this package) - -``` diff --git a/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/package.json b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/package.json new file mode 100644 index 0000000000..5bbefffbab --- /dev/null +++ b/packages/typespec-test/test/widget_dpg/generated/typespec-ts/src/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/packages/typespec-ts/test/modularUnit/operations.spec.ts b/packages/typespec-ts/test/modularUnit/operations.spec.ts index 9b21c9da9d..24efb559b7 100644 --- a/packages/typespec-ts/test/modularUnit/operations.spec.ts +++ b/packages/typespec-ts/test/modularUnit/operations.spec.ts @@ -88,4 +88,60 @@ describe("operations", () => { assert.ok(result!.length > 0); }); }); + + describe("binary COSE response handling", () => { + it("should generate correct operations for binary COSE responses", async () => { + const tspContent = ` + @doc("Signed statement with binary COSE content") + model SignedStatement { + @doc("The MIME content type for COSE is application/cose") + @header("Content-Type") + contentType: "application/cose"; + + @doc("Binary COSE payload") + @bodyRoot + body: bytes; + } + + @doc("Response with binary COSE content") + model CoseResponse { + @doc("Status code") + @statusCode + statusCode: 201; + + @doc("The MIME content type for COSE is application/cose") + @header("Content-Type") + contentType: "application/cose"; + + @doc("Binary COSE response payload") + @bodyRoot + body: bytes; + } + + @post + @route("/submit") + op submitCoseData(...SignedStatement): CoseResponse; + + @get + @route("/cose") + op getCoseData(): CoseResponse; + `; + + const result = await emitModularOperationsFromTypeSpec(tspContent); + assert.ok(result); + assert.ok(result!.length > 0); + + // Check that binary response operations are generated correctly + const operationsFile = result!.find(file => file.getFilePath().endsWith("operations.ts")); + assert.ok(operationsFile, "operations.ts should be generated"); + + const operationsContent = operationsFile!.getFullText(); + + // Verify binary response handling + assert.ok(operationsContent.includes("Promise"), "Should return Uint8Array for binary responses"); + assert.ok(operationsContent.includes('"accept": "application/cose"'), "Should set correct accept header"); + assert.ok(operationsContent.includes('contentType: "application/cose"'), "Should set correct content type"); + assert.ok(operationsContent.includes("return result.body"), "Should return binary body directly"); + }); + }); }); diff --git a/packages/typespec-ts/test/modularUnit/scenarios/operations/operations.md b/packages/typespec-ts/test/modularUnit/scenarios/operations/operations.md index c1cdba95d0..f1c5fa18c7 100644 --- a/packages/typespec-ts/test/modularUnit/scenarios/operations/operations.md +++ b/packages/typespec-ts/test/modularUnit/scenarios/operations/operations.md @@ -1172,3 +1172,133 @@ export async function createOrUpdateEndpoint( return _createOrUpdateEndpointDeserialize(result); } ``` + +# Binary COSE response handling + +Binary responses with application/cose content type should be properly handled without crashing. + +## TypeSpec + +```tsp +@doc("Signed statement with binary COSE content") +model SignedStatement { + @doc("The MIME content type for COSE is application/cose") + @header("Content-Type") + contentType: "application/cose"; + + @doc("Binary COSE payload") + @bodyRoot + body: bytes; +} + +@doc("Response with binary COSE content") +model CoseResponse { + @doc("Status code") + @statusCode + statusCode: 201; + + @doc("The MIME content type for COSE is application/cose") + @header("Content-Type") + contentType: "application/cose"; + + @doc("Binary COSE response payload") + @bodyRoot + body: bytes; +} + +@post +@route("/submit") +op submitCoseData(...SignedStatement): CoseResponse; + +@get +@route("/cose") +op getCoseData(): CoseResponse; +``` + +## Operations + +```ts operations +import { BinaryCoseTestContext as Client } from "./index.js"; +import { + GetCoseDataOptionalParams, + SubmitCoseDataOptionalParams, +} from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@typespec/ts-http-runtime"; + +export function _getCoseDataSend( + context: Client, + options: GetCoseDataOptionalParams = { requestOptions: {} }, +): StreamableMethod { + return context + .path("/cose") + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cose", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getCoseDataDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["201"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +export async function getCoseData( + context: Client, + options: GetCoseDataOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _getCoseDataSend(context, options); + return _getCoseDataDeserialize(result); +} + +export function _submitCoseDataSend( + context: Client, + body: Uint8Array, + options: SubmitCoseDataOptionalParams = { requestOptions: {} }, +): StreamableMethod { + return context + .path("/submit") + .post({ + ...operationOptionsToRequestParameters(options), + contentType: "application/cose", + headers: { + accept: "application/cose", + ...options.requestOptions?.headers, + }, + body: body, + }); +} + +export async function _submitCoseDataDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["201"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +export async function submitCoseData( + context: Client, + body: Uint8Array, + options: SubmitCoseDataOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _submitCoseDataSend(context, body, options); + return _submitCoseDataDeserialize(result); +} +```