diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/LICENSE b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/LICENSE new file mode 100644 index 0000000000..63447fd8bb --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) Microsoft Corporation. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/README.md b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/README.md new file mode 100644 index 0000000000..98f9477429 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/README.md @@ -0,0 +1,108 @@ +# Azure CodeTransparency client library for JavaScript + +This package contains an isomorphic SDK (runs both in Node.js and in browsers) for Azure CodeTransparency client. + + + +Key links: + +- [Package (NPM)](https://www.npmjs.com/package/@azure/codetransparency) +- [API reference documentation](https://learn.microsoft.com/javascript/api/@azure/codetransparency?view=azure-node-preview) + +## Getting started + +### Currently supported environments + +- [LTS versions of Node.js](https://github.com/nodejs/release#release-schedule) +- Latest versions of Safari, Chrome, Edge and Firefox. + +See our [support policy](https://github.com/Azure/azure-sdk-for-js/blob/main/SUPPORT.md) for more details. + +### Prerequisites + +- An [Azure subscription][azure_sub]. + +### Install the `@azure/codetransparency` package + +Install the Azure CodeTransparency client library for JavaScript with `npm`: + +```bash +npm install @azure/codetransparency +``` + +### Create and authenticate a `CodeTransparencyClient` + +To create a client object to access the Azure CodeTransparency API, you will need the `endpoint` of your Azure CodeTransparency resource and a `credential`. The Azure CodeTransparency client can use Azure Active Directory credentials to authenticate. +You can find the endpoint for your Azure CodeTransparency resource in the [Azure Portal][azure_portal]. + +You can authenticate with Azure Active Directory using a credential from the [@azure/identity][azure_identity] library or [an existing AAD Token](https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/identity/identity/samples/AzureIdentityExamples.md#authenticating-with-a-pre-fetched-access-token). + +To use the [DefaultAzureCredential][defaultazurecredential] provider shown below, or other credential providers provided with the Azure SDK, please install the `@azure/identity` package: + +```bash +npm install @azure/identity +``` + +You will also need to **register a new AAD application and grant access to Azure CodeTransparency** by assigning the suitable role to your service principal (note: roles such as `"Owner"` will not grant the necessary permissions). + +For more information about how to create an Azure AD Application check out [this guide](https://learn.microsoft.com/azure/active-directory/develop/howto-create-service-principal-portal). + +Using Node.js and Node-like environments, you can use the `DefaultAzureCredential` class to authenticate the client. + +```ts +import { CodeTransparencyClient } from "@azure/codetransparency"; +import { DefaultAzureCredential } from "@azure/identity"; + +const client = new CodeTransparencyClient("", new DefaultAzureCredential()); +``` + +For browser environments, use the `InteractiveBrowserCredential` from the `@azure/identity` package to authenticate. + +```ts +import { InteractiveBrowserCredential } from "@azure/identity"; +import { CodeTransparencyClient } from "@azure/codetransparency"; + +const credential = new InteractiveBrowserCredential({ + tenantId: "", + clientId: "" + }); +const client = new CodeTransparencyClient("", credential); +``` + + +### JavaScript Bundle +To use this client library in the browser, first you need to use a bundler. For details on how to do this, please refer to our [bundling documentation](https://aka.ms/AzureSDKBundling). + +## Key concepts + +### CodeTransparencyClient + +`CodeTransparencyClient` is the primary interface for developers using the Azure CodeTransparency client library. Explore the methods on this client object to understand the different features of the Azure CodeTransparency service that you can access. + +## Troubleshooting + +### Logging + +Enabling logging may help uncover useful information about failures. In order to see a log of HTTP requests and responses, set the `AZURE_LOG_LEVEL` environment variable to `info`. Alternatively, logging can be enabled at runtime by calling `setLogLevel` in the `@azure/logger`: + +```ts +import { setLogLevel } from "@azure/logger"; + +setLogLevel("info"); +``` + +For more detailed instructions on how to enable logs, you can look at the [@azure/logger package docs](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/logger). + + +## Contributing + +If you'd like to contribute to this library, please read the [contributing guide](https://github.com/Azure/azure-sdk-for-js/blob/main/CONTRIBUTING.md) to learn more about how to build and test the code. + +## Related projects + +- [Microsoft Azure SDK for JavaScript](https://github.com/Azure/azure-sdk-for-js) + +[azure_sub]: https://azure.microsoft.com/free/ +[azure_portal]: https://portal.azure.com +[azure_identity]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity +[defaultazurecredential]: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#defaultazurecredential diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/api-extractor.json b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/api-extractor.json new file mode 100644 index 0000000000..2f1a765a04 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/api-extractor.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "mainEntryPointFilePath": "dist/esm/index.d.ts", + "docModel": { "enabled": true }, + "apiReport": { "enabled": true, "reportFolder": "./review" }, + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "", + "publicTrimmedFilePath": "dist/codetransparency.d.ts" + }, + "messages": { + "tsdocMessageReporting": { "default": { "logLevel": "none" } }, + "extractorMessageReporting": { + "ae-missing-release-tag": { "logLevel": "none" }, + "ae-unresolved-link": { "logLevel": "none" } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/eslint.config.mjs b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/eslint.config.mjs new file mode 100644 index 0000000000..9396819633 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/eslint.config.mjs @@ -0,0 +1,14 @@ +import azsdkEslint from "@azure/eslint-plugin-azure-sdk"; + +export default azsdkEslint.config([ + { + rules: { + "@azure/azure-sdk/ts-modules-only-named": "warn", + "@azure/azure-sdk/ts-package-json-types": "warn", + "@azure/azure-sdk/ts-package-json-engine-is-present": "warn", + "@azure/azure-sdk/ts-package-json-files-required": "off", + "@azure/azure-sdk/ts-package-json-main-is-cjs": "off", + "tsdoc/syntax": "warn" + } + } +]); diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/package.json b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/package.json new file mode 100644 index 0000000000..50966e3353 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/package.json @@ -0,0 +1,132 @@ +{ + "name": "@azure/codetransparency", + "version": "1.0.0-beta.1", + "description": "A generated SDK for CodeTransparencyClient.", + "engines": { + "node": ">=20.0.0" + }, + "sideEffects": false, + "autoPublish": false, + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./api": "./src/api/index.ts", + "./models": "./src/models/index.ts" + }, + "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" + ], + "author": "Microsoft Corporation", + "license": "MIT", + "files": [ + "dist/", + "!dist/**/*.d.*ts.map", + "README.md", + "LICENSE" + ], + "dependencies": { + "@azure/core-util": "^1.9.2", + "@azure-rest/core-client": "^2.3.1", + "@azure/core-auth": "^1.6.0", + "@azure/core-rest-pipeline": "^1.5.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "devDependencies": { + "dotenv": "^16.0.0", + "@types/node": "^20.0.0", + "eslint": "^9.9.0", + "typescript": "~5.8.2", + "tshy": "^2.0.0", + "@microsoft/api-extractor": "^7.40.3", + "rimraf": "^5.0.5", + "mkdirp": "^3.0.1" + }, + "scripts": { + "clean": "rimraf --glob dist dist-browser dist-esm test-dist temp types *.tgz *.log", + "extract-api": "rimraf review && mkdirp ./review && api-extractor run --local", + "pack": "npm pack 2>&1", + "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" + } + }, + "./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/Microsoft.CodeTransparency/generated/typespec-ts/review/codetransparency.api.md b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/review/codetransparency.api.md new file mode 100644 index 0000000000..00b418cda3 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/review/codetransparency.api.md @@ -0,0 +1,86 @@ +## API Report File for "@azure/codetransparency" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { ClientOptions } from '@azure-rest/core-client'; +import { KeyCredential } from '@azure/core-auth'; +import { OperationOptions } from '@azure-rest/core-client'; +import { Pipeline } from '@azure/core-rest-pipeline'; + +// @public (undocumented) +export class CodeTransparencyClient { + constructor(endpointParam: string, credential: KeyCredential, options?: CodeTransparencyClientOptionalParams); + createEntry(body: Uint8Array, options?: CreateEntryOptionalParams): Promise; + getEntry(entryId: string, options?: GetEntryOptionalParams): Promise; + getEntryStatement(entryId: string, options?: GetEntryStatementOptionalParams): Promise; + getOperation(operationId: string, options?: GetOperationOptionalParams): Promise; + getPublicKeys(options?: GetPublicKeysOptionalParams): Promise; + getTransparencyConfigCbor(options?: GetTransparencyConfigCborOptionalParams): Promise; + readonly pipeline: Pipeline; +} + +// @public +export interface CodeTransparencyClientOptionalParams extends ClientOptions { + apiVersion?: string; +} + +// @public +export interface CreateEntryOptionalParams extends OperationOptions { +} + +// @public +export interface GetEntryOptionalParams extends OperationOptions { +} + +// @public +export interface GetEntryStatementOptionalParams extends OperationOptions { +} + +// @public +export interface GetOperationOptionalParams extends OperationOptions { +} + +// @public +export interface GetPublicKeysOptionalParams extends OperationOptions { +} + +// @public +export interface GetTransparencyConfigCborOptionalParams extends OperationOptions { +} + +// @public +export interface JsonWebKey { + alg?: string; + crv?: string; + d?: string; + dp?: string; + dq?: string; + e?: string; + k?: string; + kid?: string; + kty: string; + n?: string; + p?: string; + q?: string; + qi?: string; + use?: string; + x?: string; + x5C?: string[]; + y?: string; +} + +// @public +export interface JwksDocument { + keys: JsonWebKey[]; +} + +// @public +export enum KnownVersions { + _20250131Preview = "2025-01-31-preview" +} + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/rollup.config.js b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/rollup.config.js new file mode 100644 index 0000000000..843de501bf --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/rollup.config.js @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import nodeResolve from "@rollup/plugin-node-resolve"; +import cjs from "@rollup/plugin-commonjs"; +import sourcemaps from "rollup-plugin-sourcemaps"; +import multiEntry from "@rollup/plugin-multi-entry"; +import json from "@rollup/plugin-json"; + +import nodeBuiltins from "builtin-modules"; + +// #region Warning Handler + +/** + * A function that can determine whether a rollup warning should be ignored. If + * the function returns `true`, then the warning will not be displayed. + */ + +function ignoreNiseSinonEval(warning) { + return ( + warning.code === "EVAL" && + warning.id && + (warning.id.includes("node_modules/nise") || + warning.id.includes("node_modules/sinon")) === true + ); +} + +function ignoreChaiCircularDependency(warning) { + return ( + warning.code === "CIRCULAR_DEPENDENCY" && + warning.importer && + warning.importer.includes("node_modules/chai") === true + ); +} + +const warningInhibitors = [ignoreChaiCircularDependency, ignoreNiseSinonEval]; + +/** + * Construct a warning handler for the shared rollup configuration + * that ignores certain warnings that are not relevant to testing. + */ +function makeOnWarnForTesting() { + return (warning, warn) => { + // If every inhibitor returns false (i.e. no inhibitors), then show the warning + if (warningInhibitors.every((inhib) => !inhib(warning))) { + warn(warning); + } + }; +} + +// #endregion + +function makeBrowserTestConfig() { + const config = { + input: { + include: ["dist-esm/test/**/*.spec.js"], + exclude: ["dist-esm/test/**/node/**"], + }, + output: { + file: `dist-test/index.browser.js`, + format: "umd", + sourcemap: true, + }, + preserveSymlinks: false, + plugins: [ + multiEntry({ exports: false }), + nodeResolve({ + mainFields: ["module", "browser"], + }), + cjs(), + json(), + sourcemaps(), + //viz({ filename: "dist-test/browser-stats.html", sourcemap: true }) + ], + onwarn: makeOnWarnForTesting(), + // Disable tree-shaking of test code. In rollup-plugin-node-resolve@5.0.0, + // rollup started respecting the "sideEffects" field in package.json. Since + // our package.json sets "sideEffects=false", this also applies to test + // code, which causes all tests to be removed by tree-shaking. + treeshake: false, + }; + + return config; +} + +const defaultConfigurationOptions = { + disableBrowserBundle: false, +}; + +export function makeConfig(pkg, options) { + options = { + ...defaultConfigurationOptions, + ...(options || {}), + }; + + const baseConfig = { + // Use the package's module field if it has one + input: pkg["module"] || "dist-esm/src/index.js", + external: [ + ...nodeBuiltins, + ...Object.keys(pkg.dependencies), + ...Object.keys(pkg.devDependencies), + ], + output: { file: "dist/index.js", format: "cjs", sourcemap: true }, + preserveSymlinks: false, + plugins: [sourcemaps(), nodeResolve()], + }; + + const config = [baseConfig]; + + if (!options.disableBrowserBundle) { + config.push(makeBrowserTestConfig()); + } + + return config; +} + +export default makeConfig(require("./package.json")); diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/codeTransparencyContext.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/codeTransparencyContext.ts new file mode 100644 index 0000000000..7a81308344 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/codeTransparencyContext.ts @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { logger } from "../logger.js"; +import { KnownVersions } from "../models/models.js"; +import { Client, ClientOptions, getClient } from "@azure-rest/core-client"; +import { KeyCredential, isKeyCredential } from "@azure/core-auth"; + +export interface CodeTransparencyContext extends Client { + /** The API version to use for this operation. */ + /** Known values of {@link KnownVersions} that the service accepts. */ + apiVersion: string; +} + +/** Optional parameters for the client. */ +export interface CodeTransparencyClientOptionalParams extends ClientOptions { + /** The API version to use for this operation. */ + /** Known values of {@link KnownVersions} that the service accepts. */ + apiVersion?: string; +} + +export function createCodeTransparency( + endpointParam: string, + credential: KeyCredential, + options: CodeTransparencyClientOptionalParams = {}, +): CodeTransparencyContext { + const endpointUrl = options.endpoint ?? String(endpointParam); + const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix; + const userAgentInfo = `azsdk-js-codetransparency/1.0.0-beta.1`; + const userAgentPrefix = prefixFromOptions + ? `${prefixFromOptions} azsdk-js-api ${userAgentInfo}` + : `azsdk-js-api ${userAgentInfo}`; + const { apiVersion: _, ...updatedOptions } = { + ...options, + userAgentOptions: { userAgentPrefix }, + loggingOptions: { logger: options.loggingOptions?.logger ?? logger.info }, + }; + const clientContext = getClient(endpointUrl, undefined, updatedOptions); + + if (isKeyCredential(credential)) { + clientContext.pipeline.addPolicy({ + name: "customKeyCredentialPolicy", + sendRequest(request, next) { + request.headers.set("Authorization", "Bearer " + credential.key); + return next(request); + }, + }); + } + clientContext.pipeline.removePolicy({ name: "ApiVersionPolicy" }); + const apiVersion = options.apiVersion ?? "2025-01-31-preview"; + clientContext.pipeline.addPolicy({ + name: "ClientApiVersionPolicy", + sendRequest: (req, next) => { + // Use the apiVersion defined in request url directly + // Append one if there is no apiVersion and we have one at client options + const url = new URL(req.url); + if (!url.searchParams.get("api-version")) { + req.url = `${req.url}${ + Array.from(url.searchParams.keys()).length > 0 ? "&" : "?" + }api-version=${apiVersion}`; + } + + return next(req); + }, + }); + return { ...clientContext, apiVersion } as CodeTransparencyContext; +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/index.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/index.ts new file mode 100644 index 0000000000..03f927ca1b --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/index.ts @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { + createCodeTransparency, + CodeTransparencyContext, + CodeTransparencyClientOptionalParams, +} from "./codeTransparencyContext.js"; +export { + getEntryStatement, + getEntry, + getOperation, + createEntry, + getPublicKeys, + getTransparencyConfigCbor, +} from "./operations.js"; +export { + GetEntryStatementOptionalParams, + GetEntryOptionalParams, + GetOperationOptionalParams, + CreateEntryOptionalParams, + GetPublicKeysOptionalParams, + GetTransparencyConfigCborOptionalParams, +} from "./options.js"; diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/operations.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/operations.ts new file mode 100644 index 0000000000..21f9f96f6e --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/operations.ts @@ -0,0 +1,302 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { CodeTransparencyContext as Client } from "./index.js"; +import { JwksDocument, jwksDocumentDeserializer } from "../models/models.js"; +import { getBinaryResponse } from "../static-helpers/serialization/get-binary-response.js"; +import { expandUrlTemplate } from "../static-helpers/urlTemplate.js"; +import { + GetEntryStatementOptionalParams, + GetEntryOptionalParams, + GetOperationOptionalParams, + CreateEntryOptionalParams, + GetPublicKeysOptionalParams, + GetTransparencyConfigCborOptionalParams, +} from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; + +export function _getEntryStatementSend( + context: Client, + entryId: string, + options: GetEntryStatementOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/entries/{entryId}/statement{?api%2Dversion}", + { + entryId: entryId, + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cose", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getEntryStatementDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200", "400", "404", "429", "500", "503"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +/** Get the transparent statement consisting of the signed statement and the receipt embedded in the header */ +export async function getEntryStatement( + context: Client, + entryId: string, + options: GetEntryStatementOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _getEntryStatementSend(context, entryId, options); + const result = await getBinaryResponse(streamableMethod); + return _getEntryStatementDeserialize(result); +} + +export function _getEntrySend( + context: Client, + entryId: string, + options: GetEntryOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/entries/{entryId}{?api%2Dversion}", + { + entryId: entryId, + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cose", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getEntryDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200", "400", "404", "429", "500", "503"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +/** Get receipt */ +export async function getEntry( + context: Client, + entryId: string, + options: GetEntryOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _getEntrySend(context, entryId, options); + const result = await getBinaryResponse(streamableMethod); + return _getEntryDeserialize(result); +} + +export function _getOperationSend( + context: Client, + operationId: string, + options: GetOperationOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/operations/{operationId}{?api%2Dversion}", + { + operationId: operationId, + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cbor", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getOperationDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200", "202", "400", "404", "429", "500", "503"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +/** Get status of the long running registration operation, mandatory in IETF SCITT draft */ +export async function getOperation( + context: Client, + operationId: string, + options: GetOperationOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _getOperationSend(context, operationId, options); + return _getOperationDeserialize(result); +} + +export function _createEntrySend( + context: Client, + body: Uint8Array, + options: CreateEntryOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/entries{?api%2Dversion}", + { + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .post({ + ...operationOptionsToRequestParameters(options), + contentType: "application/cose", + headers: { + accept: "application/cose; application/cbor", + ...options.requestOptions?.headers, + }, + body: body, + }); +} + +export async function _createEntryDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["201", "202", "400", "404", "429", "500", "503"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +/** Post an entry to be registered on the CodeTransparency instance, mandatory in IETF SCITT draft */ +export async function createEntry( + context: Client, + body: Uint8Array, + options: CreateEntryOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _createEntrySend(context, body, options); + const result = await getBinaryResponse(streamableMethod); + return _createEntryDeserialize(result); +} + +export function _getPublicKeysSend( + context: Client, + options: GetPublicKeysOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/jwks{?api%2Dversion}", + { + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/json", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getPublicKeysDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200", "400", "404", "429", "500", "503"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return jwksDocumentDeserializer(result.body); +} + +/** Get the public keys used by the service to sign receipts, mentioned in IETF SCITT draft as part of jwks_uri implementation */ +export async function getPublicKeys( + context: Client, + options: GetPublicKeysOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _getPublicKeysSend(context, options); + return _getPublicKeysDeserialize(result); +} + +export function _getTransparencyConfigCborSend( + context: Client, + options: GetTransparencyConfigCborOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/.well-known/transparency-configuration{?api%2Dversion}", + { + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cbor", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getTransparencyConfigCborDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200", "500", "503"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +/** Get the transparency service configuration, mandatory in IETF SCITT draft */ +export async function getTransparencyConfigCbor( + context: Client, + options: GetTransparencyConfigCborOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _getTransparencyConfigCborSend(context, options); + const result = await getBinaryResponse(streamableMethod); + return _getTransparencyConfigCborDeserialize(result); +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/options.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/options.ts new file mode 100644 index 0000000000..a2d116999c --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/api/options.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { OperationOptions } from "@azure-rest/core-client"; + +/** Optional parameters. */ +export interface GetEntryStatementOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetEntryOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetOperationOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface CreateEntryOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetPublicKeysOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetTransparencyConfigCborOptionalParams + extends OperationOptions {} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/codeTransparencyClient.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/codeTransparencyClient.ts new file mode 100644 index 0000000000..516087db67 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/codeTransparencyClient.ts @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + createCodeTransparency, + CodeTransparencyContext, + CodeTransparencyClientOptionalParams, +} from "./api/index.js"; +import { + getEntryStatement, + getEntry, + getOperation, + createEntry, + getPublicKeys, + getTransparencyConfigCbor, +} from "./api/operations.js"; +import { + GetEntryStatementOptionalParams, + GetEntryOptionalParams, + GetOperationOptionalParams, + CreateEntryOptionalParams, + GetPublicKeysOptionalParams, + GetTransparencyConfigCborOptionalParams, +} from "./api/options.js"; +import { JwksDocument } from "./models/models.js"; +import { KeyCredential } from "@azure/core-auth"; +import { Pipeline } from "@azure/core-rest-pipeline"; + +export { CodeTransparencyClientOptionalParams } from "./api/codeTransparencyContext.js"; + +export class CodeTransparencyClient { + private _client: CodeTransparencyContext; + /** The pipeline used by this client to make requests */ + public readonly pipeline: Pipeline; + + constructor( + endpointParam: string, + credential: KeyCredential, + options: CodeTransparencyClientOptionalParams = {}, + ) { + const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix; + const userAgentPrefix = prefixFromOptions + ? `${prefixFromOptions} azsdk-js-client` + : `azsdk-js-client`; + this._client = createCodeTransparency(endpointParam, credential, { + ...options, + userAgentOptions: { userAgentPrefix }, + }); + this.pipeline = this._client.pipeline; + } + + /** Get the transparent statement consisting of the signed statement and the receipt embedded in the header */ + getEntryStatement( + entryId: string, + options: GetEntryStatementOptionalParams = { requestOptions: {} }, + ): Promise { + return getEntryStatement(this._client, entryId, options); + } + + /** Get receipt */ + getEntry( + entryId: string, + options: GetEntryOptionalParams = { requestOptions: {} }, + ): Promise { + return getEntry(this._client, entryId, options); + } + + /** Get status of the long running registration operation, mandatory in IETF SCITT draft */ + getOperation( + operationId: string, + options: GetOperationOptionalParams = { requestOptions: {} }, + ): Promise { + return getOperation(this._client, operationId, options); + } + + /** Post an entry to be registered on the CodeTransparency instance, mandatory in IETF SCITT draft */ + createEntry( + body: Uint8Array, + options: CreateEntryOptionalParams = { requestOptions: {} }, + ): Promise { + return createEntry(this._client, body, options); + } + + /** Get the public keys used by the service to sign receipts, mentioned in IETF SCITT draft as part of jwks_uri implementation */ + getPublicKeys( + options: GetPublicKeysOptionalParams = { requestOptions: {} }, + ): Promise { + return getPublicKeys(this._client, options); + } + + /** Get the transparency service configuration, mandatory in IETF SCITT draft */ + getTransparencyConfigCbor( + options: GetTransparencyConfigCborOptionalParams = { requestOptions: {} }, + ): Promise { + return getTransparencyConfigCbor(this._client, options); + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/index.ts new file mode 100644 index 0000000000..2794b19306 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/index.ts @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { CodeTransparencyClient } from "./codeTransparencyClient.js"; +export { JwksDocument, JsonWebKey, KnownVersions } from "./models/index.js"; +export { + CodeTransparencyClientOptionalParams, + GetEntryStatementOptionalParams, + GetEntryOptionalParams, + GetOperationOptionalParams, + CreateEntryOptionalParams, + GetPublicKeysOptionalParams, + GetTransparencyConfigCborOptionalParams, +} from "./api/index.js"; diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/logger.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/logger.ts new file mode 100644 index 0000000000..f10da211f9 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/logger.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { createClientLogger } from "@azure/logger"; +export const logger = createClientLogger("codetransparency"); diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/models/index.ts new file mode 100644 index 0000000000..08ffbd5b8a --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/models/index.ts @@ -0,0 +1,4 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { JwksDocument, JsonWebKey, KnownVersions } from "./models.js"; diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/models/models.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/models/models.ts new file mode 100644 index 0000000000..1b82e5c667 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/models/models.ts @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * This file contains only generated model types and (de)serializers. + * Disable this rule for deserializer functions which require 'any' for raw JSON input. + */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/** A JWKS like document */ +export interface JwksDocument { + /** List of public keys used for receipt verification. */ + keys: JsonWebKey[]; +} + +export function jwksDocumentDeserializer(item: any): JwksDocument { + return { + keys: jsonWebKeyArrayDeserializer(item["keys"]), + }; +} + +export function jsonWebKeyArrayDeserializer(result: Array): any[] { + return result.map((item) => { + return jsonWebKeyDeserializer(item); + }); +} + +/** rfc7517 JSON Web Key representation adapted from a shared swagger definition in the common types */ +export interface JsonWebKey { + /** + * The "alg" (algorithm) parameter identifies the algorithm intended for + * use with the key. The values used should either be registered in the + * IANA "JSON Web Signature and Encryption Algorithms" registry + * established by [JWA] or be a value that contains a Collision- + * Resistant Name. + */ + alg?: string; + /** The "crv" (curve) parameter identifies the curve type */ + crv?: string; + /** RSA private exponent or ECC private key */ + d?: string; + /** RSA Private Key Parameter */ + dp?: string; + /** RSA Private Key Parameter */ + dq?: string; + /** RSA public exponent, in Base64 */ + e?: string; + /** Symmetric key */ + k?: string; + /** + * The "kid" (key ID) parameter is used to match a specific key. This + * is used, for instance, to choose among a set of keys within a JWK Set + * during key rollover. The structure of the "kid" value is + * unspecified. When "kid" values are used within a JWK Set, different + * keys within the JWK Set SHOULD use distinct "kid" values. (One + * example in which different keys might use the same "kid" value is if + * they have different "kty" (key type) values but are considered to be + * equivalent alternatives by the application using them.) The "kid" + * value is a case-sensitive string. + */ + kid?: string; + /** + * The "kty" (key type) parameter identifies the cryptographic algorithm + * family used with the key, such as "RSA" or "EC". "kty" values should + * either be registered in the IANA "JSON Web Key Types" registry + * established by [JWA] or be a value that contains a Collision- + * Resistant Name. The "kty" value is a case-sensitive string. + */ + kty: string; + /** RSA modulus, in Base64 */ + n?: string; + /** RSA secret prime */ + p?: string; + /** RSA secret prime, with p < q */ + q?: string; + /** RSA Private Key Parameter */ + qi?: string; + /** + * Use ("public key use") identifies the intended use of + * the public key. The "use" parameter is employed to indicate whether + * a public key is used for encrypting data or verifying the signature + * on data. Values are commonly "sig" (signature) or "enc" (encryption). + */ + use?: string; + /** X coordinate for the Elliptic Curve point */ + x?: string; + /** + * The "x5c" (X.509 certificate chain) parameter contains a chain of one + * or more PKIX certificates [RFC5280]. The certificate chain is + * represented as a JSON array of certificate value strings. Each + * string in the array is a base64-encoded (Section 4 of [RFC4648] -- + * not base64url-encoded) DER [ITU.X690.1994] PKIX certificate value. + * The PKIX certificate containing the key value MUST be the first + * certificate. + */ + x5C?: string[]; + /** Y coordinate for the Elliptic Curve point */ + y?: string; +} + +export function jsonWebKeyDeserializer(item: any): JsonWebKey { + return { + alg: item["alg"], + crv: item["crv"], + d: item["d"], + dp: item["dp"], + dq: item["dq"], + e: item["e"], + k: item["k"], + kid: item["kid"], + kty: item["kty"], + n: item["n"], + p: item["p"], + q: item["q"], + qi: item["qi"], + use: item["use"], + x: item["x"], + x5C: !item["x5c"] + ? item["x5c"] + : item["x5c"].map((p: any) => { + return p; + }), + y: item["y"], + }; +} + +/** The Microsoft.CodeTransparency service versions. */ +export enum KnownVersions { + /** The 2025-01-31-preview version of the Microsoft.CodeTransparency service. */ + _20250131Preview = "2025-01-31-preview", +} + +/** Alias for _GetPublicKeysUnionResponse */ +export type _GetPublicKeysUnionResponse = + | JwksDocument + | Uint8Array + | Uint8Array + | Uint8Array + | Uint8Array + | Uint8Array; + +export function _getPublicKeysUnionResponseDeserializer( + item: any, +): _GetPublicKeysUnionResponse { + return item; +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/serialization/get-binary-response-browser.mts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/serialization/get-binary-response-browser.mts new file mode 100644 index 0000000000..d2036e9eed --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/serialization/get-binary-response-browser.mts @@ -0,0 +1,20 @@ +import { HttpResponse, StreamableMethod } from "@azure-rest/core-client"; + +/** + * Gets a response type representing the given streamable response, using the stream methods + * to bypass Core's default response handling. This works around an issue where binary bodies in Core + * are coerced into UTF-8, regardless of whether the body is valid UTF-8 or not. + */ +export async function getBinaryResponse(streamableMethod: StreamableMethod): Promise { + const response = await streamableMethod.asBrowserStream(); + + if (response.body === undefined) { + return response as HttpResponse & { body?: Uint8Array }; + } + + const arrayBuffer = await new Response(response.body).arrayBuffer(); + return { + ...response, + body: new Uint8Array(arrayBuffer), + }; +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/serialization/get-binary-response.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/serialization/get-binary-response.ts new file mode 100644 index 0000000000..7b86b90b5c --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/serialization/get-binary-response.ts @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { HttpResponse, StreamableMethod } from "@azure-rest/core-client"; +import { Buffer } from "node:buffer"; + +/** + * Gets a response type representing the given streamable response, using the stream methods + * to bypass Core's default response handling. This works around an issue where binary bodies in Core + * are coerced into UTF-8, regardless of whether the body is valid UTF-8 or not. + */ +export async function getBinaryResponse( + streamableMethod: StreamableMethod, +): Promise { + const response = await streamableMethod.asNodeStream(); + if (response.body === undefined) { + return response as HttpResponse & { body?: Uint8Array }; + } + const bufs: Buffer[] = []; + for await (const buf of response.body) { + bufs.push(Buffer.isBuffer(buf) ? buf : Buffer.from(buf)); + } + + return { + ...response, + body: Buffer.concat(bufs), + }; +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/urlTemplate.ts b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/urlTemplate.ts new file mode 100644 index 0000000000..0a3e0d7372 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/src/static-helpers/urlTemplate.ts @@ -0,0 +1,213 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// --------------------- +// interfaces +// --------------------- +interface ValueOptions { + isFirst: boolean; // is first value in the expression + op?: string; // operator + varValue?: any; // variable value + varName?: string; // variable name + modifier?: string; // modifier e.g * + reserved?: boolean; // if true we'll keep reserved words with not encoding +} + +export interface UrlTemplateOptions { + // if set to true, reserved characters will not be encoded + allowReserved?: boolean; +} + +// --------------------- +// helpers +// --------------------- +function encodeComponent(val: string, reserved?: boolean, op?: string): string { + return (reserved ?? op === "+") || op === "#" + ? encodeReservedComponent(val) + : encodeRFC3986URIComponent(val); +} + +function encodeReservedComponent(str: string): string { + return str + .split(/(%[0-9A-Fa-f]{2})/g) + .map((part) => (!/%[0-9A-Fa-f]/.test(part) ? encodeURI(part) : part)) + .join(""); +} + +function encodeRFC3986URIComponent(str: string): string { + return encodeURIComponent(str).replace( + /[!'()*]/g, + (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`, + ); +} + +function isDefined(val: any): boolean { + return val !== undefined && val !== null; +} + +function getNamedAndIfEmpty(op?: string): [boolean, string] { + return [ + !!op && [";", "?", "&"].includes(op), + !!op && ["?", "&"].includes(op) ? "=" : "", + ]; +} + +function getFirstOrSep(op?: string, isFirst = false): string { + if (isFirst) { + return !op || op === "+" ? "" : op; + } else if (!op || op === "+" || op === "#") { + return ","; + } else if (op === "?") { + return "&"; + } else { + return op; + } +} + +function getExpandedValue(option: ValueOptions): string { + let isFirst = option.isFirst; + const { op, varName, varValue: value, reserved } = option; + const vals: string[] = []; + const [named, ifEmpty] = getNamedAndIfEmpty(op); + + if (Array.isArray(value)) { + for (const val of value.filter(isDefined)) { + // prepare the following parts: separator, varName, value + vals.push(`${getFirstOrSep(op, isFirst)}`); + if (named && varName) { + vals.push(`${encodeURIComponent(varName)}`); + if (val === "") { + vals.push(ifEmpty); + } else { + vals.push("="); + } + } + vals.push(encodeComponent(val, reserved, op)); + isFirst = false; + } + } else if (typeof value === "object") { + for (const key of Object.keys(value)) { + const val = value[key]; + if (!isDefined(val)) { + continue; + } + // prepare the following parts: separator, key, value + vals.push(`${getFirstOrSep(op, isFirst)}`); + if (key) { + vals.push(`${encodeURIComponent(key)}`); + if (named && val === "") { + vals.push(ifEmpty); + } else { + vals.push("="); + } + } + vals.push(encodeComponent(val, reserved, op)); + isFirst = false; + } + } + return vals.join(""); +} + +function getNonExpandedValue(option: ValueOptions): string | undefined { + const { op, varName, varValue: value, isFirst, reserved } = option; + const vals: string[] = []; + const first = getFirstOrSep(op, isFirst); + const [named, ifEmpty] = getNamedAndIfEmpty(op); + if (named && varName) { + vals.push(encodeComponent(varName, reserved, op)); + if (value === "") { + if (!ifEmpty) { + vals.push(ifEmpty); + } + return !vals.join("") ? undefined : `${first}${vals.join("")}`; + } + vals.push("="); + } + + const items = []; + if (Array.isArray(value)) { + for (const val of value.filter(isDefined)) { + items.push(encodeComponent(val, reserved, op)); + } + } else if (typeof value === "object") { + for (const key of Object.keys(value)) { + if (!isDefined(value[key])) { + continue; + } + items.push(encodeRFC3986URIComponent(key)); + items.push(encodeComponent(value[key], reserved, op)); + } + } + vals.push(items.join(",")); + return !vals.join(",") ? undefined : `${first}${vals.join("")}`; +} + +function getVarValue(option: ValueOptions): string | undefined { + const { op, varName, modifier, isFirst, reserved, varValue: value } = option; + + if (!isDefined(value)) { + return undefined; + } else if (["string", "number", "boolean"].includes(typeof value)) { + let val = value.toString(); + const [named, ifEmpty] = getNamedAndIfEmpty(op); + const vals: string[] = [getFirstOrSep(op, isFirst)]; + if (named && varName) { + // No need to encode varName considering it is already encoded + vals.push(varName); + if (val === "") { + vals.push(ifEmpty); + } else { + vals.push("="); + } + } + if (modifier && modifier !== "*") { + val = val.substring(0, parseInt(modifier, 10)); + } + vals.push(encodeComponent(val, reserved, op)); + return vals.join(""); + } else if (modifier === "*") { + return getExpandedValue(option); + } else { + return getNonExpandedValue(option); + } +} + +// --------------------------------------------------------------------------------------------------- +// This is an implementation of RFC 6570 URI Template: https://datatracker.ietf.org/doc/html/rfc6570. +// --------------------------------------------------------------------------------------------------- +export function expandUrlTemplate( + template: string, + context: Record, + option?: UrlTemplateOptions, +): string { + return template.replace(/\{([^{}]+)\}|([^{}]+)/g, (_, expr, text) => { + if (!expr) { + return encodeReservedComponent(text); + } + let op; + if (["+", "#", ".", "/", ";", "?", "&"].includes(expr[0])) { + op = expr[0]; + expr = expr.slice(1); + } + const varList = expr.split(/,/g); + const result = []; + for (const varSpec of varList) { + const varMatch = /([^:*]*)(?::(\d+)|(\*))?/.exec(varSpec); + if (!varMatch || !varMatch[1]) { + continue; + } + const varValue = getVarValue({ + isFirst: result.length === 0, + op, + varValue: context[varMatch[1]], + varName: varMatch[1], + modifier: varMatch[2] || varMatch[3], + reserved: option?.allowReserved, + }); + if (varValue) { + result.push(varValue); + } + } + return result.join(""); + }); +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/tsconfig.json b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/tsconfig.json new file mode 100644 index 0000000000..031889db45 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/generated/typespec-ts/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "NodeNext", + "lib": [], + "declaration": true, + "declarationMap": true, + "inlineSources": true, + "sourceMap": true, + "importHelpers": true, + "strict": true, + "alwaysStrict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true, + "moduleResolution": "NodeNext", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/CreateEntryDeprecated.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/CreateEntryDeprecated.json new file mode 100644 index 0000000000..4c5e16fe50 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/CreateEntryDeprecated.json @@ -0,0 +1,16 @@ +{ + "title": "CreateEntryDeprecated", + "operationId": "CreateEntryDeprecated", + "parameters": { + "api-version": "2024-01-11-preview", + "body": "{binary}", + "Content-Type": "application/cose" + }, + "responses": { + "200": { + "body": { + "operationId": "2.123" + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetCodeTransparencyConfig.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetCodeTransparencyConfig.json new file mode 100644 index 0000000000..8b8a8c1bd8 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetCodeTransparencyConfig.json @@ -0,0 +1,33 @@ +{ + "title": "GetCodeTransparencyConfig", + "operationId": "GetCodeTransparencyConfig", + "parameters": { + "api-version": "2024-01-11-preview" + }, + "responses": { + "200": { + "body": { + "authentication": { + "allow_unauthenticated": true + }, + "service_identifier": "did:web:servicename.confidential-ledger.azure.com" + } + }, + "500": { + "body": { + "error": { + "code": "zdjbm", + "message": "wftvioyvxidsm" + } + } + }, + "503": { + "body": { + "error": { + "code": "zdjbm", + "message": "wftvioyvxidsm" + } + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetCodeTransparencyVersion.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetCodeTransparencyVersion.json new file mode 100644 index 0000000000..3f6bf0dfcd --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetCodeTransparencyVersion.json @@ -0,0 +1,30 @@ +{ + "title": "GetCodeTransparencyVersion", + "operationId": "GetCodeTransparencyVersion", + "parameters": { + "api-version": "2024-01-11-preview" + }, + "responses": { + "200": { + "body": { + "scitt_version": "0.6.1" + } + }, + "500": { + "body": { + "error": { + "code": "zdjbm", + "message": "wftvioyvxidsm" + } + } + }, + "503": { + "body": { + "error": { + "code": "zdjbm", + "message": "wftvioyvxidsm" + } + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetDidConfig.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetDidConfig.json new file mode 100644 index 0000000000..319e9c3680 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetDidConfig.json @@ -0,0 +1,30 @@ +{ + "title": "GetDidConfig", + "operationId": "GetDidConfig", + "parameters": { + "api-version": "2024-01-11-preview" + }, + "responses": { + "200": { + "body": { + "id": "did:web:servicename.confidential-ledger.azure.com", + "assertionMethod": [ + { + "controller": "did:web:servicename.confidential-ledger.azure.com", + "id": "did:web:servicename.confidential-ledger.azure.com#d40283071e4365dbc2b222464ed41f8fc541c0368c478facf379687272d8f5e4", + "publicKeyJwk": { + "crv": "P-384", + "kty": "EC", + "x": "1LPHv776kFvrLqt7IoOOXXR_tc37XZxEVV7kDpAQnd_FnBFwFLJP5bR4EWDK4dag", + "x5c": [ + "MIIBvTCCAUOgAwIBAgIQNobpRR2ttkHF4x+r18qpSzAKBggqhkjOPQQDAzAWMRQwEgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yNDAyMjAxNTI5NThaFw0yNDA1MjAxNTI5NTdaMBYxFDASBgNVBAMMC0NDRiBOZXR3b3JrMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1LPHv776kFvrLqt7IoOOXXR/tc37XZxEVV7kDpAQnd/FnBFwFLJP5bR4EWDK4dagtFhdFK9jygDjGAnusKNUcBpnNW+VYbZdnQJqMtCczG1/vkdCU2BJFwovqwPY+DqEo1YwVDASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT85f22GsUpfHQ7iZchE3Hffu1phTAfBgNVHSMEGDAWgBT85f22GsUpfHQ7iZchE3Hffu1phTAKBggqhkjOPQQDAwNoADBlAjEAuxwyGmAXHtglqnA+lZwmDGa7FRZxafupNBGmdhUpfZeZeONpM5S51OW/vigJyT2qAjBa6YlKiXlqUvYTGby8/NrhY9Wso1iezhIZl6eD90UY6vDI8P44/doPWBqhye8En/k=" + ], + "y": "tFhdFK9jygDjGAnusKNUcBpnNW-VYbZdnQJqMtCczG1_vkdCU2BJFwovqwPY-DqE" + }, + "type": "JsonWebKey2020" + } + ] + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryDeprecated.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryDeprecated.json new file mode 100644 index 0000000000..6e25099b84 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryDeprecated.json @@ -0,0 +1,13 @@ +{ + "title": "GetEntryDeprecated", + "operationId": "GetEntryDeprecated", + "parameters": { + "api-version": "2024-01-11-preview", + "entryId": "2.131" + }, + "responses": { + "200": { + "body": "" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryReceiptDeprecated.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryReceiptDeprecated.json new file mode 100644 index 0000000000..b833db62aa --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryReceiptDeprecated.json @@ -0,0 +1,13 @@ +{ + "title": "GetEntryReceiptDeprecated", + "operationId": "GetEntryReceiptDeprecated", + "parameters": { + "api-version": "2024-01-11-preview", + "entryId": "2.131" + }, + "responses": { + "200": { + "body": "" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryStatus.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryStatus.json new file mode 100644 index 0000000000..27ac833a04 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetEntryStatus.json @@ -0,0 +1,18 @@ +{ + "title": "GetEntryStatus", + "operationId": "GetEntryStatus", + "parameters": { + "api-version": "2024-01-11-preview", + "operationId": "2.123" + }, + "responses": { + "200": { + "body": { + "entryId": "2.131", + "error": null, + "operationId": "2.123", + "status": "succeeded" + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetParameters.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetParameters.json new file mode 100644 index 0000000000..bc11beb82a --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/GetParameters.json @@ -0,0 +1,17 @@ +{ + "title": "GetParameters", + "operationId": "GetParameters", + "parameters": { + "api-version": "2024-01-11-preview" + }, + "responses": { + "200": { + "body": { + "serviceCertificate": "MIIBvTCCAUOgAwIBAgIQNobpRR2ttkHF4x+r18qpSzAKBggqhkjOPQQDAzAWMRQwEgYDVQQDDAtDQ0YgTmV0d29yazAeFw0yNDAyMjAxNTI5NThaFw0yNDA1MjAxNTI5NTdaMBYxFDASBgNVBAMMC0NDRiBOZXR3b3JrMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE1LPHv776kFvrLqt7IoOOXXR/tc37XZxEVV7kDpAQnd/FnBFwFLJP5bR4EWDK4dagtFhdFK9jygDjGAnusKNUcBpnNW+VYbZdnQJqMtCczG1/vkdCU2BJFwovqwPY+DqEo1YwVDASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT85f22GsUpfHQ7iZchE3Hffu1phTAfBgNVHSMEGDAWgBT85f22GsUpfHQ7iZchE3Hffu1phTAKBggqhkjOPQQDAwNoADBlAjEAuxwyGmAXHtglqnA+lZwmDGa7FRZxafupNBGmdhUpfZeZeONpM5S51OW/vigJyT2qAjBa6YlKiXlqUvYTGby8/NrhY9Wso1iezhIZl6eD90UY6vDI8P44/doPWBqhye8En/k=", + "serviceId": "d40283071e4365dbc2b222464ed41f8fc541c0368c478facf379687272d8f5e4", + "signatureAlgorithm": "ES256", + "treeAlgorithm": "CCF" + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/ListEntryIds.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/ListEntryIds.json new file mode 100644 index 0000000000..f27cd450cd --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/ListEntryIds.json @@ -0,0 +1,38 @@ +{ + "title": "ListEntryIds", + "operationId": "ListEntryIds", + "parameters": { + "api-version": "2024-01-11-preview" + }, + "responses": { + "200": { + "body": { + "transactionIds": [ + "2.131" + ], + "nextLink": "/entries/txIds?from=20&to=40" + } + }, + "500": { + "body": { + "error": { + "code": "zdjbm", + "message": "wftvioyvxidsm" + } + } + }, + "503": { + "body": { + "error": { + "code": "rvfhuvilmtbssrwxftcdd", + "message": "pluidkukb", + "target": "jvzisxtjiwecbbfvoa", + "details": [], + "innererror": { + "code": "lrwpvpuumivkhwpfypjgmdps" + } + } + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/ListEntryStatuses.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/ListEntryStatuses.json new file mode 100644 index 0000000000..e728fe9e18 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2024-01-11-preview/ListEntryStatuses.json @@ -0,0 +1,27 @@ +{ + "title": "ListEntryStatuses", + "operationId": "ListEntryStatuses", + "parameters": { + "api-version": "2024-01-11-preview" + }, + "responses": { + "200": { + "body": { + "operations": [ + { + "entryId": "4.89", + "error": null, + "operationId": "4.87", + "status": "succeeded" + }, + { + "entryId": "4.91", + "error": null, + "operationId": "4.91", + "status": "succeeded" + } + ] + } + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/CreateEntry.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/CreateEntry.json new file mode 100644 index 0000000000..de1740ed4a --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/CreateEntry.json @@ -0,0 +1,54 @@ +{ + "title": "CreateEntry", + "operationId": "CreateEntry", + "parameters": { + "api-version": "2025-01-31-preview", + "body": "{binary}", + "Content-Type": "application/cose", + "accept": "application/cose; application/cbor" + }, + "responses": { + "201": { + "headers": { + "Content-Type": "application/cose" + }, + "body": "{binary}" + }, + "202": { + "headers": { + "Content-Type": "application/cbor" + }, + "body": "{binary}" + }, + "400": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "404": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "429": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "500": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "503": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetEntry.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetEntry.json new file mode 100644 index 0000000000..60f07ade15 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetEntry.json @@ -0,0 +1,47 @@ +{ + "title": "GetEntry", + "operationId": "GetEntry", + "parameters": { + "api-version": "2025-01-31-preview", + "accept": "application/cose", + "entryId": "2.131" + }, + "responses": { + "200": { + "headers": { + "Content-Type": "application/cose" + }, + "body": "{binary}" + }, + "400": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "404": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "429": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "500": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "503": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetEntryStatement.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetEntryStatement.json new file mode 100644 index 0000000000..5c3729397d --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetEntryStatement.json @@ -0,0 +1,47 @@ +{ + "title": "GetEntryStatement", + "operationId": "GetEntryStatement", + "parameters": { + "api-version": "2025-01-31-preview", + "entryId": "2.131", + "accept": "application/cose" + }, + "responses": { + "200": { + "headers": { + "Content-Type": "application/cose" + }, + "body": "{binary}" + }, + "400": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "404": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "429": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "500": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "503": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetOperation.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetOperation.json new file mode 100644 index 0000000000..db68424478 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetOperation.json @@ -0,0 +1,48 @@ +{ + "title": "GetOperation", + "operationId": "GetOperation", + "parameters": { + "api-version": "2025-01-31-preview", + "accept": "application/cbor", + "operationId": "2.131" + }, + "responses": { + "200": { + "headers": { + "Content-Type": "application/cbor" + }, + "body": "{binary}" + }, + "202": {}, + "400": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "404": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "429": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "500": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "503": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetPublicKeys.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetPublicKeys.json new file mode 100644 index 0000000000..f358df2aca --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetPublicKeys.json @@ -0,0 +1,69 @@ +{ + "title": "GetPublicKeys", + "operationId": "GetPublicKeys", + "parameters": { + "api-version": "2025-01-31-preview" + }, + "responses": { + "200": { + "headers": { + "Content-Type": "application/json" + }, + "body": { + "keys": [ + { + "alg": "ismwdpkyzdpcdsxlhdrcyu", + "crv": "krvghelqczlomlykmvq", + "d": "sjrntenzgpayzxqeyylodzzot", + "dp": "okaildkxkhdiuz", + "dq": "cr", + "e": "whulawpi", + "k": "umuebehxryklpmphgvfflgxhmw", + "kid": "sjvhzuxmiidkyt", + "kty": "aleunvywecrqn", + "n": "agbjjczwjkuzidwazv", + "p": "mwqnfqougntmfkkqrhwfawqdfvc", + "q": "ihgggpjpvqrugoctea", + "qi": "hauoxhsdatbqpwyjy", + "use": "sobsnevfheiclufbfronhqufiry", + "x": "jiifvniadtvfdosxgtdgju", + "x5c": [ + "bpqymrr" + ], + "y": "owpcmhs" + } + ] + } + }, + "400": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "404": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "429": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "500": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "503": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + } + } +} diff --git a/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetTransparencyConfigCbor.json b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetTransparencyConfigCbor.json new file mode 100644 index 0000000000..250d299813 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/examples/2025-01-31-preview/GetTransparencyConfigCbor.json @@ -0,0 +1,28 @@ +{ + "title": "GetTransparencyConfigCbor", + "operationId": "GetTransparencyConfigCbor", + "parameters": { + "api-version": "2025-01-31-preview", + "accept": "application/cbor" + }, + "responses": { + "200": { + "headers": { + "Content-Type": "application/cbor" + }, + "body": "{binary}" + }, + "500": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + }, + "503": { + "headers": { + "Content-Type": "application/concise-problem-details+cbor" + }, + "body": "{binary}" + } + } +} 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..82ee7a42a5 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/spec/main.tsp @@ -0,0 +1,479 @@ +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 { + // 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; 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..d089dc9ec0 --- /dev/null +++ b/packages/typespec-test/test/Microsoft.CodeTransparency/tspconfig.yaml @@ -0,0 +1,9 @@ +emit: + - "@azure-tools/typespec-ts" +options: + "@azure-tools/typespec-ts": + azure-sdk-for-js: false + is-modular-library: true + emitter-output-dir: "{project-root}/generated/typespec-ts" + package-details: + name: "@azure/codetransparency" diff --git a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts index 1889345ec9..4989571d8e 100644 --- a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts @@ -229,14 +229,17 @@ export function getDeserializePrivateFunction( } else if (isAzureCoreErrorType(context.program, deserializedType.__raw)) { statements.push(`return ${deserializedRoot}`); } else { + // Check if response type and __raw exist before calling isBinaryPayload + const isBinary = + response.type?.__raw && contentTypes + ? isBinaryPayload(context, response.type.__raw, 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/test/azureIntegration/generated/encode/bytes/src/index.d.ts b/packages/typespec-ts/test/azureIntegration/generated/encode/bytes/src/index.d.ts index 3f904933bf..b4eb35ff90 100644 --- a/packages/typespec-ts/test/azureIntegration/generated/encode/bytes/src/index.d.ts +++ b/packages/typespec-ts/test/azureIntegration/generated/encode/bytes/src/index.d.ts @@ -296,6 +296,24 @@ export declare interface RequestBodyBase64UrlMediaTypesParam { export declare type RequestBodyBase64UrlParameters = RequestBodyBase64UrlMediaTypesParam & RequestBodyBase64UrlBodyParam & RequestParameters; +export declare interface RequestBodyContentTypeCose { + post(options: RequestBodyContentTypeCoseParameters): StreamableMethod; +} + +export declare interface RequestBodyContentTypeCose204Response extends HttpResponse { + status: "204"; +} + +export declare interface RequestBodyContentTypeCoseBodyParam { + body: string | Uint8Array | ReadableStream | NodeJS.ReadableStream; +} + +export declare interface RequestBodyContentTypeCoseMediaTypesParam { + contentType: "application/cose"; +} + +export declare type RequestBodyContentTypeCoseParameters = RequestBodyContentTypeCoseMediaTypesParam & RequestBodyContentTypeCoseBodyParam & RequestParameters; + export declare interface RequestBodyCustomContentType { post(options: RequestBodyCustomContentTypeParameters): StreamableMethod; } @@ -378,6 +396,22 @@ export declare interface ResponseBodyBase64Url200Response extends HttpResponse { export declare type ResponseBodyBase64UrlParameters = RequestParameters; +export declare interface ResponseBodyContentTypeCose { + get(options?: ResponseBodyContentTypeCoseParameters): StreamableMethod; +} + +export declare interface ResponseBodyContentTypeCose200Headers { + "content-type": "application/cose"; +} + +export declare interface ResponseBodyContentTypeCose200Response extends HttpResponse { + status: "200"; + body: Uint8Array; + headers: RawHttpHeaders & ResponseBodyContentTypeCose200Headers; +} + +export declare type ResponseBodyContentTypeCoseParameters = RequestParameters; + export declare interface ResponseBodyCustomContentType { get(options?: ResponseBodyCustomContentTypeParameters): StreamableMethod; } @@ -444,6 +478,8 @@ export declare interface Routes { (path: "/encode/bytes/body/response/custom-content-type"): ResponseBodyCustomContentType; (path: "/encode/bytes/body/response/base64"): ResponseBodyBase64; (path: "/encode/bytes/body/response/base64url"): ResponseBodyBase64Url; + (path: "/encode/bytes/body/request/content-type/cose"): RequestBodyContentTypeCose; + (path: "/encode/bytes/body/response/content-type/cose"): ResponseBodyContentTypeCose; } export { } diff --git a/packages/typespec-ts/test/azureModularIntegration/encodeBytes.spec.ts b/packages/typespec-ts/test/azureModularIntegration/encodeBytes.spec.ts index 48c826ef11..411729042b 100644 --- a/packages/typespec-ts/test/azureModularIntegration/encodeBytes.spec.ts +++ b/packages/typespec-ts/test/azureModularIntegration/encodeBytes.spec.ts @@ -200,4 +200,28 @@ describe("EncodeBytesClient Modular Client", () => { assert.strictEqual(uint8ArrayToString(result, "utf-8"), pngFile); }); }); + + describe.only("request body content type", () => { + const pngFile = readFileSync( + resolve("../../packages/typespec-ts/temp/assets/image.png") + ); + it(`should post bytes with cose content type`, async () => { + const result = await client.requestBodyContentType.cose(pngFile); + assert.isUndefined(result); + }); + }); + + describe.only("request body content type", () => { + const pngFile = readFileSync( + resolve("../../packages/typespec-ts/temp/assets/image.png") + ).toString("utf-8"); + it(`should get bytes with cose content type`, async () => { + const result = await client.responseBodyContentType.cose({ + onResponse: (res) => { + res.headers.get("content-type") === "application/cose"; + } + }); + assert.strictEqual(uint8ArrayToString(result, "utf-8"), pngFile); + }); + }); }); diff --git a/packages/typespec-ts/test/azureModularIntegration/generated/encode/bytes/src/index.d.ts b/packages/typespec-ts/test/azureModularIntegration/generated/encode/bytes/src/index.d.ts index f37ca41640..c62795b5c1 100644 --- a/packages/typespec-ts/test/azureModularIntegration/generated/encode/bytes/src/index.d.ts +++ b/packages/typespec-ts/test/azureModularIntegration/generated/encode/bytes/src/index.d.ts @@ -18,6 +18,8 @@ export declare class BytesClient { private _client; readonly pipeline: Pipeline; constructor(options?: BytesClientOptionalParams); + readonly responseBodyContentType: ResponseBodyContentTypeOperations; + readonly requestBodyContentType: RequestBodyContentTypeOperations; readonly responseBody: ResponseBodyOperations; readonly requestBody: RequestBodyOperations; readonly header: HeaderOperations; @@ -95,6 +97,13 @@ export declare interface RequestBodyBase64OptionalParams extends OperationOption export declare interface RequestBodyBase64UrlOptionalParams extends OperationOptions { } +export declare interface RequestBodyContentTypeCoseOptionalParams extends OperationOptions { +} + +export declare interface RequestBodyContentTypeOperations { + cose: (value: Uint8Array, options?: RequestBodyContentTypeCoseOptionalParams) => Promise; +} + export declare interface RequestBodyCustomContentTypeOptionalParams extends OperationOptions { } @@ -118,6 +127,13 @@ export declare interface ResponseBodyBase64OptionalParams extends OperationOptio export declare interface ResponseBodyBase64UrlOptionalParams extends OperationOptions { } +export declare interface ResponseBodyContentTypeCoseOptionalParams extends OperationOptions { +} + +export declare interface ResponseBodyContentTypeOperations { + cose: (options?: ResponseBodyContentTypeCoseOptionalParams) => Promise; +} + export declare interface ResponseBodyCustomContentTypeOptionalParams extends OperationOptions { } diff --git a/packages/typespec-ts/test/modularUnit/scenarios/operations/bytesResponseBody.md b/packages/typespec-ts/test/modularUnit/scenarios/operations/bytesResponseBody.md new file mode 100644 index 0000000000..2a7b148b79 --- /dev/null +++ b/packages/typespec-ts/test/modularUnit/scenarios/operations/bytesResponseBody.md @@ -0,0 +1,217 @@ +# response body with bytes type should handle undefined response.type + +This test reproduces the issue found in Microsoft.CodeTransparency where the response body is defined as `bytes` type and during code generation, `response.type.__raw` might be undefined, causing a crash in `isBinaryPayload` -> `getEffectiveModelFromType` trying to access `type.kind` on undefined. + +## TypeSpec + +```tsp +@doc("Response with bytes body") +model BytesResponse { + @doc("Content type header") + @header + contentType: "application/octet-stream"; + + @doc("Binary content") + @body + body: bytes; +} + +@route("/data") +@get +op getData(): BytesResponse; +``` + +## operations + +```ts operations +import { TestingContext as Client } from "./index.js"; +import { getBinaryResponse } from "../static-helpers/serialization/get-binary-response.js"; +import { GetDataOptionalParams } from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; + +export function _getDataSend( + context: Client, + options: GetDataOptionalParams = { requestOptions: {} }, +): StreamableMethod { + return context + .path("/data") + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/octet-stream", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getDataDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +export async function getData( + context: Client, + options: GetDataOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _getDataSend(context, options); + const result = await getBinaryResponse(streamableMethod); + return _getDataDeserialize(result); +} +``` + +# response with bytes body and custom CBOR content type + +## TypeSpec + +```tsp + +@doc("CBOR response with bytes body") +model CborResponse { + @doc("CBOR content type") + @header + contentType: "application/cbor"; + + @doc("CBOR binary content") + @body + body: bytes; +} + +@route("/cbor") +@get +op getCbor(): CborResponse; +``` + +## operations + +```ts operations +import { TestingContext as Client } from "./index.js"; +import { getBinaryResponse } from "../static-helpers/serialization/get-binary-response.js"; +import { GetCborOptionalParams } from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; + +export function _getCborSend( + context: Client, + options: GetCborOptionalParams = { requestOptions: {} }, +): StreamableMethod { + return context + .path("/cbor") + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cbor", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getCborDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +export async function getCbor( + context: Client, + options: GetCborOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _getCborSend(context, options); + const result = await getBinaryResponse(streamableMethod); + return _getCborDeserialize(result); +} +``` + +# response with @bodyRoot bytes and COSE content type + +## TypeSpec + +```tsp + +@doc("Response with @bodyRoot bytes") +model RootBytesResponse { + @doc("Status code") + @statusCode + statusCode: 200; + + @doc("Content type") + @header + contentType: "application/cose"; + + @doc("Binary body at root") + @bodyRoot + body: bytes; +} + +@route("/cose") +@get +op getCose(): RootBytesResponse; +``` + +## operations + +```ts operations +import { TestingContext as Client } from "./index.js"; +import { getBinaryResponse } from "../static-helpers/serialization/get-binary-response.js"; +import { GetCoseOptionalParams } from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; + +export function _getCoseSend( + context: Client, + options: GetCoseOptionalParams = { requestOptions: {} }, +): StreamableMethod { + return context + .path("/cose") + .get({ + ...operationOptionsToRequestParameters(options), + headers: { + accept: "application/cose", + ...options.requestOptions?.headers, + }, + }); +} + +export async function _getCoseDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +export async function getCose( + context: Client, + options: GetCoseOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _getCoseSend(context, options); + const result = await getBinaryResponse(streamableMethod); + return _getCoseDeserialize(result); +} +```