diff --git a/packages/typespec-test/test/DocumentTranslation/examples/2024-11-01-preview/DocumentTranslate_MaximumSet_Gen.json b/packages/typespec-test/test/DocumentTranslation/examples/2024-11-01-preview/DocumentTranslate_MaximumSet_Gen.json new file mode 100644 index 0000000000..a356bd6865 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/examples/2024-11-01-preview/DocumentTranslate_MaximumSet_Gen.json @@ -0,0 +1,22 @@ +{ + "title": "Translate a single document", + "operationId": "DocumentTranslationOperations_DocumentTranslate", + "parameters": { + "api-version": "2024-11-01-preview", + "sourceLanguage": "en", + "targetLanguage": "es", + "body":{ + "document": "TXkgdHJhbnNsYXRlZCBkb2N1bWVudA==" + } + }, + "responses": { + "200": { + "headers": { + "x-metered-usage": 739, + "total-image-scans-succeeded": 6, + "total-image-scans-failed": 1 + }, + "body": "TXkgdHJhbnNsYXRlZCBkb2N1bWVudA==" + } + } +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/LICENSE b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/LICENSE new file mode 100644 index 0000000000..63447fd8bb --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/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/DocumentTranslation/generated/typespec-ts/README.md b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/README.md new file mode 100644 index 0000000000..ce4f4ac2d8 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/README.md @@ -0,0 +1,108 @@ +# Azure SingleDocumentTranslation client library for JavaScript + +This package contains an isomorphic SDK (runs both in Node.js and in browsers) for Azure SingleDocumentTranslation client. + +Document translation service + +Key links: + +- [Package (NPM)](https://www.npmjs.com/package/@azure/load-testing) +- [API reference documentation](https://learn.microsoft.com/javascript/api/@azure/load-testing?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/load-testing` package + +Install the Azure SingleDocumentTranslation client library for JavaScript with `npm`: + +```bash +npm install @azure/load-testing +``` + +### Create and authenticate a `SingleDocumentTranslationClient` + +To create a client object to access the Azure SingleDocumentTranslation API, you will need the `endpoint` of your Azure SingleDocumentTranslation resource and a `credential`. The Azure SingleDocumentTranslation client can use Azure Active Directory credentials to authenticate. +You can find the endpoint for your Azure SingleDocumentTranslation 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 SingleDocumentTranslation** 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 { SingleDocumentTranslationClient } from "@azure/load-testing"; +import { DefaultAzureCredential } from "@azure/identity"; + +const client = new SingleDocumentTranslationClient("", new DefaultAzureCredential()); +``` + +For browser environments, use the `InteractiveBrowserCredential` from the `@azure/identity` package to authenticate. + +```ts +import { InteractiveBrowserCredential } from "@azure/identity"; +import { SingleDocumentTranslationClient } from "@azure/load-testing"; + +const credential = new InteractiveBrowserCredential({ + tenantId: "", + clientId: "" + }); +const client = new SingleDocumentTranslationClient("", 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 + +### SingleDocumentTranslationClient + +`SingleDocumentTranslationClient` is the primary interface for developers using the Azure SingleDocumentTranslation client library. Explore the methods on this client object to understand the different features of the Azure SingleDocumentTranslation 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/DocumentTranslation/generated/typespec-ts/api-extractor.json b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/api-extractor.json new file mode 100644 index 0000000000..e633258fb3 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/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/load-testing.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/DocumentTranslation/generated/typespec-ts/eslint.config.mjs b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/eslint.config.mjs new file mode 100644 index 0000000000..9396819633 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/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/DocumentTranslation/generated/typespec-ts/package.json b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/package.json new file mode 100644 index 0000000000..2497ac14e8 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/package.json @@ -0,0 +1,173 @@ +{ + "name": "@azure/load-testing", + "version": "1.0.0-beta.1", + "description": "Azure Load Testing", + "engines": { + "node": ">=20.0.0" + }, + "sideEffects": false, + "autoPublish": false, + "tshy": { + "exports": { + "./package.json": "./package.json", + ".": "./src/index.ts", + "./documentTranslation": "./src/documentTranslation/index.ts", + "./documentTranslation/api": "./src/documentTranslation/api/index.ts", + "./singleDocumentTranslation": "./src/singleDocumentTranslation/index.ts", + "./singleDocumentTranslation/api": "./src/singleDocumentTranslation/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", + "@azure/core-lro": "^3.1.0", + "@azure/abort-controller": "^2.1.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" + } + }, + "./documentTranslation": { + "browser": { + "types": "./dist/browser/documentTranslation/index.d.ts", + "default": "./dist/browser/documentTranslation/index.js" + }, + "react-native": { + "types": "./dist/react-native/documentTranslation/index.d.ts", + "default": "./dist/react-native/documentTranslation/index.js" + }, + "import": { + "types": "./dist/esm/documentTranslation/index.d.ts", + "default": "./dist/esm/documentTranslation/index.js" + }, + "require": { + "types": "./dist/commonjs/documentTranslation/index.d.ts", + "default": "./dist/commonjs/documentTranslation/index.js" + } + }, + "./documentTranslation/api": { + "browser": { + "types": "./dist/browser/documentTranslation/api/index.d.ts", + "default": "./dist/browser/documentTranslation/api/index.js" + }, + "react-native": { + "types": "./dist/react-native/documentTranslation/api/index.d.ts", + "default": "./dist/react-native/documentTranslation/api/index.js" + }, + "import": { + "types": "./dist/esm/documentTranslation/api/index.d.ts", + "default": "./dist/esm/documentTranslation/api/index.js" + }, + "require": { + "types": "./dist/commonjs/documentTranslation/api/index.d.ts", + "default": "./dist/commonjs/documentTranslation/api/index.js" + } + }, + "./singleDocumentTranslation": { + "browser": { + "types": "./dist/browser/singleDocumentTranslation/index.d.ts", + "default": "./dist/browser/singleDocumentTranslation/index.js" + }, + "react-native": { + "types": "./dist/react-native/singleDocumentTranslation/index.d.ts", + "default": "./dist/react-native/singleDocumentTranslation/index.js" + }, + "import": { + "types": "./dist/esm/singleDocumentTranslation/index.d.ts", + "default": "./dist/esm/singleDocumentTranslation/index.js" + }, + "require": { + "types": "./dist/commonjs/singleDocumentTranslation/index.d.ts", + "default": "./dist/commonjs/singleDocumentTranslation/index.js" + } + }, + "./singleDocumentTranslation/api": { + "browser": { + "types": "./dist/browser/singleDocumentTranslation/api/index.d.ts", + "default": "./dist/browser/singleDocumentTranslation/api/index.js" + }, + "react-native": { + "types": "./dist/react-native/singleDocumentTranslation/api/index.d.ts", + "default": "./dist/react-native/singleDocumentTranslation/api/index.js" + }, + "import": { + "types": "./dist/esm/singleDocumentTranslation/api/index.d.ts", + "default": "./dist/esm/singleDocumentTranslation/api/index.js" + }, + "require": { + "types": "./dist/commonjs/singleDocumentTranslation/api/index.d.ts", + "default": "./dist/commonjs/singleDocumentTranslation/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/DocumentTranslation/generated/typespec-ts/review/load-testing.api.md b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/review/load-testing.api.md new file mode 100644 index 0000000000..925dfb1ce3 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/review/load-testing.api.md @@ -0,0 +1,288 @@ +## API Report File for "@azure/load-testing" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { AbortSignalLike } from '@azure/abort-controller'; +import { ClientOptions } from '@azure-rest/core-client'; +import { KeyCredential } from '@azure/core-auth'; +import { OperationOptions } from '@azure-rest/core-client'; +import { OperationState } from '@azure/core-lro'; +import { PathUncheckedResponse } from '@azure-rest/core-client'; +import { Pipeline } from '@azure/core-rest-pipeline'; +import { PollerLike } from '@azure/core-lro'; +import { TokenCredential } from '@azure/core-auth'; + +// @public +export interface BatchOptions { + translateTextWithinImage?: boolean; +} + +// @public +export interface BatchRequest { + source: SourceInput; + storageType?: StorageInputType; + targets: TargetInput[]; +} + +// @public +export interface CancelTranslationOptionalParams extends OperationOptions { +} + +// @public +export type ContinuablePage = TPage & { + continuationToken?: string; +}; + +// @public +export interface DocumentFilter { + prefix?: string; + suffix?: string; +} + +// @public +export interface DocumentStatus { + characterCharged?: number; + createdDateTimeUtc: Date; + error?: TranslationError; + id: string; + lastActionDateTimeUtc: Date; + path?: string; + progress: number; + sourcePath: string; + status: Status; + to: string; + totalImageScansFailed?: number; + totalImageScansSucceeded?: number; +} + +// @public +export interface DocumentTranslateContent { + document: FileContents | { + contents: FileContents; + contentType?: string; + filename?: string; + }; + glossary?: Array; +} + +// @public (undocumented) +export class DocumentTranslationClient { + constructor(endpointParam: string, credential: KeyCredential | TokenCredential, options?: DocumentTranslationClientOptionalParams); + cancelTranslation(translationId: string, options?: CancelTranslationOptionalParams): Promise; + getDocumentsStatus(translationId: string, options?: GetDocumentsStatusOptionalParams): PagedAsyncIterableIterator; + getDocumentStatus(translationId: string, documentId: string, options?: GetDocumentStatusOptionalParams): Promise; + getSupportedFormats(options?: GetSupportedFormatsOptionalParams): Promise; + getTranslationsStatus(options?: GetTranslationsStatusOptionalParams): PagedAsyncIterableIterator; + getTranslationStatus(translationId: string, options?: GetTranslationStatusOptionalParams): Promise; + readonly pipeline: Pipeline; + startTranslation(body: StartTranslationDetails, options?: StartTranslationOptionalParams): PollerLike, TranslationStatus>; +} + +// @public +export interface DocumentTranslationClientOptionalParams extends ClientOptions { + apiVersion?: string; +} + +// @public +export type FileContents = string | NodeJS.ReadableStream | ReadableStream | Uint8Array | Blob; + +// @public +export interface FileFormat { + contentTypes: string[]; + defaultVersion?: string; + fileExtensions: string[]; + format: string; + type?: FileFormatType; + versions?: string[]; +} + +// @public +export type FileFormatType = "document" | "glossary"; + +// @public +export interface GetDocumentsStatusOptionalParams extends OperationOptions { + createdDateTimeUtcEnd?: Date; + createdDateTimeUtcStart?: Date; + documentIds?: string[]; + maxpagesize?: number; + orderby?: string[]; + skip?: number; + statuses?: string[]; + top?: number; +} + +// @public +export interface GetDocumentStatusOptionalParams extends OperationOptions { +} + +// @public +export interface GetSupportedFormatsOptionalParams extends OperationOptions { + typeParam?: FileFormatType; +} + +// @public +export interface GetTranslationsStatusOptionalParams extends OperationOptions { + createdDateTimeUtcEnd?: Date; + createdDateTimeUtcStart?: Date; + maxpagesize?: number; + orderby?: string[]; + skip?: number; + statuses?: string[]; + top?: number; + translationIds?: string[]; +} + +// @public +export interface GetTranslationStatusOptionalParams extends OperationOptions { +} + +// @public +export interface Glossary { + format: string; + glossaryUrl: string; + storageSource?: TranslationStorageSource; + version?: string; +} + +// @public +export interface InnerTranslationError { + code: string; + innerError?: InnerTranslationError; + message: string; + readonly target?: string; +} + +// @public +export enum KnownVersions { + V20240501 = "2024-05-01", + V20241101Preview = "2024-11-01-preview" +} + +// @public +export interface PagedAsyncIterableIterator { + [Symbol.asyncIterator](): PagedAsyncIterableIterator; + byPage: (settings?: TPageSettings) => AsyncIterableIterator>; + next(): Promise>; +} + +// @public +export interface PageSettings { + continuationToken?: string; +} + +// @public +export function restorePoller(client: DocumentTranslationClient, serializedState: string, sourceOperation: (...args: any[]) => PollerLike, TResult>, options?: RestorePollerOptions): PollerLike, TResult>; + +// @public (undocumented) +export interface RestorePollerOptions extends OperationOptions { + abortSignal?: AbortSignalLike; + processResponseBody?: (result: TResponse) => Promise; + updateIntervalInMs?: number; +} + +// @public (undocumented) +export class SingleDocumentTranslationClient { + constructor(endpointParam: string, credential: KeyCredential | TokenCredential, options?: SingleDocumentTranslationClientOptionalParams); + readonly pipeline: Pipeline; + translate(targetLanguage: string, body: DocumentTranslateContent, options?: TranslateOptionalParams): Promise; +} + +// @public +export interface SingleDocumentTranslationClientOptionalParams extends ClientOptions { + apiVersion?: string; +} + +// @public +export interface SourceInput { + filter?: DocumentFilter; + language?: string; + sourceUrl: string; + storageSource?: TranslationStorageSource; +} + +// @public +export interface StartTranslationDetails { + inputs: BatchRequest[]; + options?: BatchOptions; +} + +// @public +export interface StartTranslationOptionalParams extends OperationOptions { + updateIntervalInMs?: number; +} + +// @public +export type Status = "NotStarted" | "Running" | "Succeeded" | "Failed" | "Cancelled" | "Cancelling" | "ValidationFailed"; + +// @public +export type StorageInputType = "Folder" | "File"; + +// @public +export interface SupportedFileFormats { + value: FileFormat[]; +} + +// @public +export interface TargetInput { + category?: string; + glossaries?: Glossary[]; + language: string; + storageSource?: TranslationStorageSource; + targetUrl: string; +} + +// @public +export interface TranslateOptionalParams extends OperationOptions { + allowFallback?: boolean; + category?: string; + clientRequestId?: string; + sourceLanguage?: string; + translateTextWithinImage?: boolean; +} + +// @public +export interface TranslationError { + code: TranslationErrorCode; + innerError?: InnerTranslationError; + message: string; + readonly target?: string; +} + +// @public +export type TranslationErrorCode = "InvalidRequest" | "InvalidArgument" | "InternalServerError" | "ServiceUnavailable" | "ResourceNotFound" | "Unauthorized" | "RequestRateTooHigh"; + +// @public +export interface TranslationStatus { + createdDateTimeUtc: Date; + error?: TranslationError; + id: string; + lastActionDateTimeUtc: Date; + status: Status; + summary: TranslationStatusSummary; +} + +// @public +export interface TranslationStatusSummary { + cancelled: number; + failed: number; + inProgress: number; + notYetStarted: number; + success: number; + total: number; + totalCharacterCharged: number; + totalImageScansFailed?: number; + totalImageScansSucceeded?: number; +} + +// @public +export type TranslationStorageSource = "AzureBlob"; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/rollup.config.js b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/rollup.config.js new file mode 100644 index 0000000000..92fab887b9 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/rollup.config.js @@ -0,0 +1,117 @@ +// 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/DocumentTranslation/generated/typespec-ts/sample.env b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/sample.env new file mode 100644 index 0000000000..508439fc7d --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/sample.env @@ -0,0 +1 @@ +# Feel free to add your own environment variables. \ No newline at end of file diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/samples-dev/singleDocumentTranslationClient/translateSample.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/samples-dev/singleDocumentTranslationClient/translateSample.ts new file mode 100644 index 0000000000..f2f403763a --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/samples-dev/singleDocumentTranslationClient/translateSample.ts @@ -0,0 +1,25 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SingleDocumentTranslationClient } from "@azure/load-testing"; +import { DefaultAzureCredential } from "@azure/identity"; + +/** + * This sample demonstrates how to use this API to submit a single translation request to the Document Translation Service. + * + * @summary use this API to submit a single translation request to the Document Translation Service. + * x-ms-original-file: 2024-11-01-preview/DocumentTranslate_MaximumSet_Gen.json + */ +async function translateASingleDocument(): Promise { + const endpoint = process.env.SINGLE_DOCUMENT_TRANSLATION_ENDPOINT || ""; + const credential = new DefaultAzureCredential(); + const client = new SingleDocumentTranslationClient(endpoint, credential); + const result = await client.translate("es", {}, { sourceLanguage: "en" }); + console.log(result); +} + +async function main(): Promise { + await translateASingleDocument(); +} + +main().catch(console.error); diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/documentTranslationContext.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/documentTranslationContext.ts new file mode 100644 index 0000000000..9fe4ea03af --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/documentTranslationContext.ts @@ -0,0 +1,61 @@ +// 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, TokenCredential } from "@azure/core-auth"; + +export interface DocumentTranslationContext 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 DocumentTranslationClientOptionalParams extends ClientOptions { + /** The API version to use for this operation. */ + /** Known values of {@link KnownVersions} that the service accepts. */ + apiVersion?: string; +} + +export function createDocumentTranslation( + endpointParam: string, + credential: KeyCredential | TokenCredential, + options: DocumentTranslationClientOptionalParams = {}, +): DocumentTranslationContext { + const endpointUrl = options.endpoint ?? `${endpointParam}/translator`; + const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix; + const userAgentInfo = `azsdk-js-load-testing/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 }, + credentials: { + scopes: options.credentials?.scopes ?? ["https://cognitiveservices.azure.com/.default"], + apiKeyHeaderName: options.credentials?.apiKeyHeaderName ?? "Ocp-Apim-Subscription-Key", + }, + }; + const clientContext = getClient(endpointUrl, credential, updatedOptions); + clientContext.pipeline.removePolicy({ name: "ApiVersionPolicy" }); + const apiVersion = options.apiVersion ?? "2024-11-01-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 DocumentTranslationContext; +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/index.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/index.ts new file mode 100644 index 0000000000..e62ceb79c4 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/index.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { + createDocumentTranslation, + DocumentTranslationContext, + DocumentTranslationClientOptionalParams, +} from "./documentTranslationContext.js"; +export { + getSupportedFormats, + getDocumentsStatus, + cancelTranslation, + getTranslationStatus, + getDocumentStatus, + getTranslationsStatus, + startTranslation, +} from "./operations.js"; +export { + GetSupportedFormatsOptionalParams, + GetDocumentsStatusOptionalParams, + CancelTranslationOptionalParams, + GetTranslationStatusOptionalParams, + GetDocumentStatusOptionalParams, + GetTranslationsStatusOptionalParams, + StartTranslationOptionalParams, +} from "./options.js"; diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/operations.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/operations.ts new file mode 100644 index 0000000000..d684bc3c15 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/operations.ts @@ -0,0 +1,550 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { DocumentTranslationContext as Client } from "./index.js"; +import { + StartTranslationDetails, + startTranslationDetailsSerializer, + TranslationStatus, + translationStatusDeserializer, + _TranslationsStatus, + _translationsStatusDeserializer, + DocumentStatus, + documentStatusDeserializer, + _DocumentsStatus, + _documentsStatusDeserializer, + SupportedFileFormats, + supportedFileFormatsDeserializer, +} from "../../models/models.js"; +import { + PagedAsyncIterableIterator, + buildPagedAsyncIterator, +} from "../../static-helpers/pagingHelpers.js"; +import { getLongRunningPoller } from "../../static-helpers/pollingHelpers.js"; +import { expandUrlTemplate } from "../../static-helpers/urlTemplate.js"; +import { + GetSupportedFormatsOptionalParams, + GetDocumentsStatusOptionalParams, + CancelTranslationOptionalParams, + GetTranslationStatusOptionalParams, + GetDocumentStatusOptionalParams, + GetTranslationsStatusOptionalParams, + StartTranslationOptionalParams, +} from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; +import { PollerLike, OperationState } from "@azure/core-lro"; + +export function _getSupportedFormatsSend( + context: Client, + options: GetSupportedFormatsOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/formats{?api%2Dversion,type}", + { + "api%2Dversion": context.apiVersion, + type: options?.typeParam, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { accept: "application/json", ...options.requestOptions?.headers }, + }); +} + +export async function _getSupportedFormatsDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return supportedFileFormatsDeserializer(result.body); +} + +/** + * The list of supported formats supported by the Document Translation + * service. + * The list includes the common file extension, as well as the + * content-type if using the upload API. + */ +export async function getSupportedFormats( + context: Client, + options: GetSupportedFormatsOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _getSupportedFormatsSend(context, options); + return _getSupportedFormatsDeserialize(result); +} + +export function _getDocumentsStatusSend( + context: Client, + translationId: string, + options: GetDocumentsStatusOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/batches/{id}/documents{?api%2Dversion,top,skip,maxpagesize,ids,statuses,createdDateTimeUtcStart,createdDateTimeUtcEnd,orderby}", + { + id: translationId, + "api%2Dversion": context.apiVersion, + top: options?.top, + skip: options?.skip, + maxpagesize: options?.maxpagesize, + ids: !options?.documentIds + ? options?.documentIds + : options?.documentIds.map((p: any) => { + return p; + }), + statuses: !options?.statuses + ? options?.statuses + : options?.statuses.map((p: any) => { + return p; + }), + createdDateTimeUtcStart: !options?.createdDateTimeUtcStart + ? options?.createdDateTimeUtcStart + : options?.createdDateTimeUtcStart.toISOString(), + createdDateTimeUtcEnd: !options?.createdDateTimeUtcEnd + ? options?.createdDateTimeUtcEnd + : options?.createdDateTimeUtcEnd.toISOString(), + orderby: !options?.orderby + ? options?.orderby + : options?.orderby.map((p: any) => { + return p; + }), + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { accept: "application/json", ...options.requestOptions?.headers }, + }); +} + +export async function _getDocumentsStatusDeserialize( + result: PathUncheckedResponse, +): Promise<_DocumentsStatus> { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return _documentsStatusDeserializer(result.body); +} + +/** + * Returns the status for all documents in a batch document translation request. + * + * + * If the number of documents in the response exceeds our paging limit, + * server-side paging is used. + * Paginated responses indicate a partial result and + * include a continuation token in the response. The absence of a continuation + * token means that no additional pages are available. + * + * top, skip + * and maxpagesize query parameters can be used to specify a number of results to + * return and an offset for the collection. + * + * top indicates the total + * number of records the user wants to be returned across all pages. + * skip + * indicates the number of records to skip from the list of document status held + * by the server based on the sorting method specified. By default, we sort by + * descending start time. + * maxpagesize is the maximum items returned in a page. + * If more items are requested via top (or top is not specified and there are + * more items to be returned), @nextLink will contain the link to the next page. + * + * + * orderby query parameter can be used to sort the returned list (ex + * "orderby=createdDateTimeUtc asc" or "orderby=createdDateTimeUtc + * desc"). + * The default sorting is descending by createdDateTimeUtc. + * Some query + * parameters can be used to filter the returned list (ex: + * "status=Succeeded,Cancelled") will only return succeeded and cancelled + * documents. + * createdDateTimeUtcStart and createdDateTimeUtcEnd can be used + * combined or separately to specify a range of datetime to filter the returned + * list by. + * The supported filtering query parameters are (status, ids, + * createdDateTimeUtcStart, createdDateTimeUtcEnd). + * + * When both top + * and skip are included, the server should first apply skip and then top on + * the collection. + * Note: If the server can't honor top and/or skip, the server + * must return an error to the client informing about it instead of just ignoring + * the query options. + * This reduces the risk of the client making assumptions about + * the data returned. + */ +export function getDocumentsStatus( + context: Client, + translationId: string, + options: GetDocumentsStatusOptionalParams = { requestOptions: {} }, +): PagedAsyncIterableIterator { + return buildPagedAsyncIterator( + context, + () => _getDocumentsStatusSend(context, translationId, options), + _getDocumentsStatusDeserialize, + ["200"], + { itemName: "value", nextLinkName: "nextLink" }, + ); +} + +export function _cancelTranslationSend( + context: Client, + translationId: string, + options: CancelTranslationOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/batches/{id}{?api%2Dversion}", + { + id: translationId, + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .delete({ + ...operationOptionsToRequestParameters(options), + headers: { accept: "application/json", ...options.requestOptions?.headers }, + }); +} + +export async function _cancelTranslationDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return translationStatusDeserializer(result.body); +} + +/** + * Cancel a currently processing or queued translation. + * A translation will not be + * cancelled if it is already completed or failed or cancelling. A bad request + * will be returned. + * All documents that have completed translation will not be + * cancelled and will be charged. + * All pending documents will be cancelled if + * possible. + */ +export async function cancelTranslation( + context: Client, + translationId: string, + options: CancelTranslationOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _cancelTranslationSend(context, translationId, options); + return _cancelTranslationDeserialize(result); +} + +export function _getTranslationStatusSend( + context: Client, + translationId: string, + options: GetTranslationStatusOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/batches/{id}{?api%2Dversion}", + { + id: translationId, + "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 _getTranslationStatusDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return translationStatusDeserializer(result.body); +} + +/** + * Returns the status for a document translation request. + * The status includes the + * overall request status, as well as the status for documents that are being + * translated as part of that request. + */ +export async function getTranslationStatus( + context: Client, + translationId: string, + options: GetTranslationStatusOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _getTranslationStatusSend(context, translationId, options); + return _getTranslationStatusDeserialize(result); +} + +export function _getDocumentStatusSend( + context: Client, + translationId: string, + documentId: string, + options: GetDocumentStatusOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/batches/{id}/documents/{documentId}{?api%2Dversion}", + { + id: translationId, + documentId: documentId, + "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 _getDocumentStatusDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return documentStatusDeserializer(result.body); +} + +/** + * Returns the translation status for a specific document based on the request Id + * and document Id. + */ +export async function getDocumentStatus( + context: Client, + translationId: string, + documentId: string, + options: GetDocumentStatusOptionalParams = { requestOptions: {} }, +): Promise { + const result = await _getDocumentStatusSend(context, translationId, documentId, options); + return _getDocumentStatusDeserialize(result); +} + +export function _getTranslationsStatusSend( + context: Client, + options: GetTranslationsStatusOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/batches{?api%2Dversion,top,skip,maxpagesize,ids,statuses,createdDateTimeUtcStart,createdDateTimeUtcEnd,orderby}", + { + "api%2Dversion": context.apiVersion, + top: options?.top, + skip: options?.skip, + maxpagesize: options?.maxpagesize, + ids: !options?.translationIds + ? options?.translationIds + : options?.translationIds.map((p: any) => { + return p; + }), + statuses: !options?.statuses + ? options?.statuses + : options?.statuses.map((p: any) => { + return p; + }), + createdDateTimeUtcStart: !options?.createdDateTimeUtcStart + ? options?.createdDateTimeUtcStart + : options?.createdDateTimeUtcStart.toISOString(), + createdDateTimeUtcEnd: !options?.createdDateTimeUtcEnd + ? options?.createdDateTimeUtcEnd + : options?.createdDateTimeUtcEnd.toISOString(), + orderby: !options?.orderby + ? options?.orderby + : options?.orderby.map((p: any) => { + return p; + }), + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .get({ + ...operationOptionsToRequestParameters(options), + headers: { accept: "application/json", ...options.requestOptions?.headers }, + }); +} + +export async function _getTranslationsStatusDeserialize( + result: PathUncheckedResponse, +): Promise<_TranslationsStatus> { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return _translationsStatusDeserializer(result.body); +} + +/** + * Returns a list of batch requests submitted and the status for each + * request. + * This list only contains batch requests submitted by the user (based on + * the resource). + * + * If the number of requests exceeds our paging limit, + * server-side paging is used. Paginated responses indicate a partial result and + * include a continuation token in the response. + * The absence of a continuation + * token means that no additional pages are available. + * + * top, skip + * and maxpagesize query parameters can be used to specify a number of results to + * return and an offset for the collection. + * + * top indicates the total + * number of records the user wants to be returned across all pages. + * skip + * indicates the number of records to skip from the list of batches based on the + * sorting method specified. By default, we sort by descending start + * time. + * maxpagesize is the maximum items returned in a page. If more items are + * requested via top (or top is not specified and there are more items to be + * returned), @nextLink will contain the link to the next page. + * + * + * orderby query parameter can be used to sort the returned list (ex + * "orderby=createdDateTimeUtc asc" or "orderby=createdDateTimeUtc + * desc"). + * The default sorting is descending by createdDateTimeUtc. + * Some query + * parameters can be used to filter the returned list (ex: + * "status=Succeeded,Cancelled") will only return succeeded and cancelled + * operations. + * createdDateTimeUtcStart and createdDateTimeUtcEnd can be used + * combined or separately to specify a range of datetime to filter the returned + * list by. + * The supported filtering query parameters are (status, ids, + * createdDateTimeUtcStart, createdDateTimeUtcEnd). + * + * The server honors + * the values specified by the client. However, clients must be prepared to handle + * responses that contain a different page size or contain a continuation token. + * + * + * When both top and skip are included, the server should first apply + * skip and then top on the collection. + * Note: If the server can't honor top + * and/or skip, the server must return an error to the client informing about it + * instead of just ignoring the query options. + * This reduces the risk of the client + * making assumptions about the data returned. + */ +export function getTranslationsStatus( + context: Client, + options: GetTranslationsStatusOptionalParams = { requestOptions: {} }, +): PagedAsyncIterableIterator { + return buildPagedAsyncIterator( + context, + () => _getTranslationsStatusSend(context, options), + _getTranslationsStatusDeserialize, + ["200"], + { itemName: "value", nextLinkName: "nextLink" }, + ); +} + +export function _startTranslationSend( + context: Client, + body: StartTranslationDetails, + options: StartTranslationOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document/batches{?api%2Dversion}", + { + "api%2Dversion": context.apiVersion, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .post({ + ...operationOptionsToRequestParameters(options), + contentType: "application/json", + body: startTranslationDetailsSerializer(body), + }); +} + +export async function _startTranslationDeserialize( + result: PathUncheckedResponse, +): Promise { + const expectedStatuses = ["202", "200", "201"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return translationStatusDeserializer(result.body); +} + +/** + * Use this API to submit a bulk (batch) translation request to the Document + * Translation service. + * Each request can contain multiple documents and must + * contain a source and destination container for each document. + * + * The + * prefix and suffix filter (if supplied) are used to filter folders. The prefix + * is applied to the subpath after the container name. + * + * Glossaries / + * Translation memory can be included in the request and are applied by the + * service when the document is translated. + * + * If the glossary is + * invalid or unreachable during translation, an error is indicated in the + * document status. + * If a file with the same name already exists at the + * destination, it will be overwritten. The targetUrl for each target language + * must be unique. + */ +export function startTranslation( + context: Client, + body: StartTranslationDetails, + options: StartTranslationOptionalParams = { requestOptions: {} }, +): PollerLike, TranslationStatus> { + return getLongRunningPoller(context, _startTranslationDeserialize, ["202", "200", "201"], { + updateIntervalInMs: options?.updateIntervalInMs, + abortSignal: options?.abortSignal, + getInitialResponse: () => _startTranslationSend(context, body, options), + resourceLocationConfig: "operation-location", + }) as PollerLike, TranslationStatus>; +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/options.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/options.ts new file mode 100644 index 0000000000..740edc42c3 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/api/options.ts @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { FileFormatType } from "../../models/models.js"; +import { OperationOptions } from "@azure-rest/core-client"; + +/** Optional parameters. */ +export interface GetSupportedFormatsOptionalParams extends OperationOptions { + /** the type of format like document or glossary */ + typeParam?: FileFormatType; +} + +/** Optional parameters. */ +export interface GetDocumentsStatusOptionalParams extends OperationOptions { + /** + * top indicates the total number of records the user wants to be returned across + * all pages. + * + * Clients MAY use top and skip query parameters to + * specify a number of results to return and an offset into the collection. + * When + * both top and skip are given by a client, the server SHOULD first apply skip + * and then top on the collection. + * + * Note: If the server can't honor + * top and/or skip, the server MUST return an error to the client informing + * about it instead of just ignoring the query options. + */ + top?: number; + /** + * skip indicates the number of records to skip from the list of records held by + * the server based on the sorting method specified. By default, we sort by + * descending start time. + * + * Clients MAY use top and skip query + * parameters to specify a number of results to return and an offset into the + * collection. + * When both top and skip are given by a client, the server SHOULD + * first apply skip and then top on the collection. + * + * Note: If the + * server can't honor top and/or skip, the server MUST return an error to the + * client informing about it instead of just ignoring the query options. + */ + skip?: number; + /** + * maxpagesize is the maximum items returned in a page. If more items are + * requested via top (or top is not specified and there are more items to be + * returned), @nextLink will contain the link to the next page. + * + * + * Clients MAY request server-driven paging with a specific page size by + * specifying a maxpagesize preference. The server SHOULD honor this preference + * if the specified page size is smaller than the server's default page size. + */ + maxpagesize?: number; + /** Ids to use in filtering */ + documentIds?: string[]; + /** Statuses to use in filtering */ + statuses?: string[]; + /** the start datetime to get items after */ + createdDateTimeUtcStart?: Date; + /** the end datetime to get items before */ + createdDateTimeUtcEnd?: Date; + /** the sorting query for the collection (ex: 'CreatedDateTimeUtc asc','CreatedDateTimeUtc desc') */ + orderby?: string[]; +} + +/** Optional parameters. */ +export interface CancelTranslationOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetTranslationStatusOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetDocumentStatusOptionalParams extends OperationOptions {} + +/** Optional parameters. */ +export interface GetTranslationsStatusOptionalParams extends OperationOptions { + /** + * top indicates the total number of records the user wants to be returned across + * all pages. + * + * Clients MAY use top and skip query parameters to + * specify a number of results to return and an offset into the collection. + * When + * both top and skip are given by a client, the server SHOULD first apply skip + * and then top on the collection. + * + * Note: If the server can't honor + * top and/or skip, the server MUST return an error to the client informing + * about it instead of just ignoring the query options. + */ + top?: number; + /** + * skip indicates the number of records to skip from the list of records held by + * the server based on the sorting method specified. By default, we sort by + * descending start time. + * + * Clients MAY use top and skip query + * parameters to specify a number of results to return and an offset into the + * collection. + * When both top and skip are given by a client, the server SHOULD + * first apply skip and then top on the collection. + * + * Note: If the + * server can't honor top and/or skip, the server MUST return an error to the + * client informing about it instead of just ignoring the query options. + */ + skip?: number; + /** + * maxpagesize is the maximum items returned in a page. If more items are + * requested via top (or top is not specified and there are more items to be + * returned), @nextLink will contain the link to the next page. + * + * + * Clients MAY request server-driven paging with a specific page size by + * specifying a maxpagesize preference. The server SHOULD honor this preference + * if the specified page size is smaller than the server's default page size. + */ + maxpagesize?: number; + /** Ids to use in filtering */ + translationIds?: string[]; + /** Statuses to use in filtering */ + statuses?: string[]; + /** the start datetime to get items after */ + createdDateTimeUtcStart?: Date; + /** the end datetime to get items before */ + createdDateTimeUtcEnd?: Date; + /** the sorting query for the collection (ex: 'CreatedDateTimeUtc asc','CreatedDateTimeUtc desc') */ + orderby?: string[]; +} + +/** Optional parameters. */ +export interface StartTranslationOptionalParams extends OperationOptions { + /** Delay to wait until next poll, in milliseconds. */ + updateIntervalInMs?: number; +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/documentTranslationClient.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/documentTranslationClient.ts new file mode 100644 index 0000000000..02c5965f9f --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/documentTranslationClient.ts @@ -0,0 +1,257 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + createDocumentTranslation, + DocumentTranslationContext, + DocumentTranslationClientOptionalParams, +} from "./api/index.js"; +import { + StartTranslationDetails, + TranslationStatus, + DocumentStatus, + SupportedFileFormats, +} from "../models/models.js"; +import { PagedAsyncIterableIterator } from "../static-helpers/pagingHelpers.js"; +import { + getSupportedFormats, + getDocumentsStatus, + cancelTranslation, + getTranslationStatus, + getDocumentStatus, + getTranslationsStatus, + startTranslation, +} from "./api/operations.js"; +import { + GetSupportedFormatsOptionalParams, + GetDocumentsStatusOptionalParams, + CancelTranslationOptionalParams, + GetTranslationStatusOptionalParams, + GetDocumentStatusOptionalParams, + GetTranslationsStatusOptionalParams, + StartTranslationOptionalParams, +} from "./api/options.js"; +import { KeyCredential, TokenCredential } from "@azure/core-auth"; +import { PollerLike, OperationState } from "@azure/core-lro"; +import { Pipeline } from "@azure/core-rest-pipeline"; + +export { DocumentTranslationClientOptionalParams } from "./api/documentTranslationContext.js"; + +export class DocumentTranslationClient { + private _client: DocumentTranslationContext; + /** The pipeline used by this client to make requests */ + public readonly pipeline: Pipeline; + + constructor( + endpointParam: string, + credential: KeyCredential | TokenCredential, + options: DocumentTranslationClientOptionalParams = {}, + ) { + const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix; + const userAgentPrefix = prefixFromOptions + ? `${prefixFromOptions} azsdk-js-client` + : `azsdk-js-client`; + this._client = createDocumentTranslation(endpointParam, credential, { + ...options, + userAgentOptions: { userAgentPrefix }, + }); + this.pipeline = this._client.pipeline; + } + + /** + * The list of supported formats supported by the Document Translation + * service. + * The list includes the common file extension, as well as the + * content-type if using the upload API. + */ + getSupportedFormats( + options: GetSupportedFormatsOptionalParams = { requestOptions: {} }, + ): Promise { + return getSupportedFormats(this._client, options); + } + + /** + * Returns the status for all documents in a batch document translation request. + * + * + * If the number of documents in the response exceeds our paging limit, + * server-side paging is used. + * Paginated responses indicate a partial result and + * include a continuation token in the response. The absence of a continuation + * token means that no additional pages are available. + * + * top, skip + * and maxpagesize query parameters can be used to specify a number of results to + * return and an offset for the collection. + * + * top indicates the total + * number of records the user wants to be returned across all pages. + * skip + * indicates the number of records to skip from the list of document status held + * by the server based on the sorting method specified. By default, we sort by + * descending start time. + * maxpagesize is the maximum items returned in a page. + * If more items are requested via top (or top is not specified and there are + * more items to be returned), @nextLink will contain the link to the next page. + * + * + * orderby query parameter can be used to sort the returned list (ex + * "orderby=createdDateTimeUtc asc" or "orderby=createdDateTimeUtc + * desc"). + * The default sorting is descending by createdDateTimeUtc. + * Some query + * parameters can be used to filter the returned list (ex: + * "status=Succeeded,Cancelled") will only return succeeded and cancelled + * documents. + * createdDateTimeUtcStart and createdDateTimeUtcEnd can be used + * combined or separately to specify a range of datetime to filter the returned + * list by. + * The supported filtering query parameters are (status, ids, + * createdDateTimeUtcStart, createdDateTimeUtcEnd). + * + * When both top + * and skip are included, the server should first apply skip and then top on + * the collection. + * Note: If the server can't honor top and/or skip, the server + * must return an error to the client informing about it instead of just ignoring + * the query options. + * This reduces the risk of the client making assumptions about + * the data returned. + */ + getDocumentsStatus( + translationId: string, + options: GetDocumentsStatusOptionalParams = { requestOptions: {} }, + ): PagedAsyncIterableIterator { + return getDocumentsStatus(this._client, translationId, options); + } + + /** + * Cancel a currently processing or queued translation. + * A translation will not be + * cancelled if it is already completed or failed or cancelling. A bad request + * will be returned. + * All documents that have completed translation will not be + * cancelled and will be charged. + * All pending documents will be cancelled if + * possible. + */ + cancelTranslation( + translationId: string, + options: CancelTranslationOptionalParams = { requestOptions: {} }, + ): Promise { + return cancelTranslation(this._client, translationId, options); + } + + /** + * Returns the status for a document translation request. + * The status includes the + * overall request status, as well as the status for documents that are being + * translated as part of that request. + */ + getTranslationStatus( + translationId: string, + options: GetTranslationStatusOptionalParams = { requestOptions: {} }, + ): Promise { + return getTranslationStatus(this._client, translationId, options); + } + + /** + * Returns the translation status for a specific document based on the request Id + * and document Id. + */ + getDocumentStatus( + translationId: string, + documentId: string, + options: GetDocumentStatusOptionalParams = { requestOptions: {} }, + ): Promise { + return getDocumentStatus(this._client, translationId, documentId, options); + } + + /** + * Returns a list of batch requests submitted and the status for each + * request. + * This list only contains batch requests submitted by the user (based on + * the resource). + * + * If the number of requests exceeds our paging limit, + * server-side paging is used. Paginated responses indicate a partial result and + * include a continuation token in the response. + * The absence of a continuation + * token means that no additional pages are available. + * + * top, skip + * and maxpagesize query parameters can be used to specify a number of results to + * return and an offset for the collection. + * + * top indicates the total + * number of records the user wants to be returned across all pages. + * skip + * indicates the number of records to skip from the list of batches based on the + * sorting method specified. By default, we sort by descending start + * time. + * maxpagesize is the maximum items returned in a page. If more items are + * requested via top (or top is not specified and there are more items to be + * returned), @nextLink will contain the link to the next page. + * + * + * orderby query parameter can be used to sort the returned list (ex + * "orderby=createdDateTimeUtc asc" or "orderby=createdDateTimeUtc + * desc"). + * The default sorting is descending by createdDateTimeUtc. + * Some query + * parameters can be used to filter the returned list (ex: + * "status=Succeeded,Cancelled") will only return succeeded and cancelled + * operations. + * createdDateTimeUtcStart and createdDateTimeUtcEnd can be used + * combined or separately to specify a range of datetime to filter the returned + * list by. + * The supported filtering query parameters are (status, ids, + * createdDateTimeUtcStart, createdDateTimeUtcEnd). + * + * The server honors + * the values specified by the client. However, clients must be prepared to handle + * responses that contain a different page size or contain a continuation token. + * + * + * When both top and skip are included, the server should first apply + * skip and then top on the collection. + * Note: If the server can't honor top + * and/or skip, the server must return an error to the client informing about it + * instead of just ignoring the query options. + * This reduces the risk of the client + * making assumptions about the data returned. + */ + getTranslationsStatus( + options: GetTranslationsStatusOptionalParams = { requestOptions: {} }, + ): PagedAsyncIterableIterator { + return getTranslationsStatus(this._client, options); + } + + /** + * Use this API to submit a bulk (batch) translation request to the Document + * Translation service. + * Each request can contain multiple documents and must + * contain a source and destination container for each document. + * + * The + * prefix and suffix filter (if supplied) are used to filter folders. The prefix + * is applied to the subpath after the container name. + * + * Glossaries / + * Translation memory can be included in the request and are applied by the + * service when the document is translated. + * + * If the glossary is + * invalid or unreachable during translation, an error is indicated in the + * document status. + * If a file with the same name already exists at the + * destination, it will be overwritten. The targetUrl for each target language + * must be unique. + */ + startTranslation( + body: StartTranslationDetails, + options: StartTranslationOptionalParams = { requestOptions: {} }, + ): PollerLike, TranslationStatus> { + return startTranslation(this._client, body, options); + } +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/index.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/index.ts new file mode 100644 index 0000000000..feb04c2567 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/index.ts @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { DocumentTranslationClient } from "./documentTranslationClient.js"; +export { restorePoller, RestorePollerOptions } from "./restorePollerHelpers.js"; +export { + DocumentTranslationContext, + DocumentTranslationClientOptionalParams, + GetSupportedFormatsOptionalParams, + GetDocumentsStatusOptionalParams, + CancelTranslationOptionalParams, + GetTranslationStatusOptionalParams, + GetDocumentStatusOptionalParams, + GetTranslationsStatusOptionalParams, + StartTranslationOptionalParams, +} from "./api/index.js"; diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/restorePollerHelpers.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/restorePollerHelpers.ts new file mode 100644 index 0000000000..983b5a1fea --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/documentTranslation/restorePollerHelpers.ts @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { DocumentTranslationClient } from "./documentTranslationClient.js"; +import { _startTranslationDeserialize } from "./api/operations.js"; +import { getLongRunningPoller } from "../static-helpers/pollingHelpers.js"; +import { OperationOptions, PathUncheckedResponse } from "@azure-rest/core-client"; +import { AbortSignalLike } from "@azure/abort-controller"; +import { + PollerLike, + OperationState, + deserializeState, + ResourceLocationConfig, +} from "@azure/core-lro"; + +export interface RestorePollerOptions< + TResult, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +> extends OperationOptions { + /** Delay to wait until next poll, in milliseconds. */ + updateIntervalInMs?: number; + /** + * The signal which can be used to abort requests. + */ + abortSignal?: AbortSignalLike; + /** Deserialization function for raw response body */ + processResponseBody?: (result: TResponse) => Promise; +} + +/** + * Creates a poller from the serialized state of another poller. This can be + * useful when you want to create pollers on a different host or a poller + * needs to be constructed after the original one is not in scope. + */ +export function restorePoller( + client: DocumentTranslationClient, + serializedState: string, + sourceOperation: (...args: any[]) => PollerLike, TResult>, + options?: RestorePollerOptions, +): PollerLike, TResult> { + const pollerConfig = deserializeState(serializedState).config; + const { initialRequestUrl, requestMethod, metadata } = pollerConfig; + if (!initialRequestUrl || !requestMethod) { + throw new Error( + `Invalid serialized state: ${serializedState} for sourceOperation ${sourceOperation?.name}`, + ); + } + const resourceLocationConfig = metadata?.["resourceLocationConfig"] as + | ResourceLocationConfig + | undefined; + const { deserializer, expectedStatuses = [] } = + getDeserializationHelper(initialRequestUrl, requestMethod) ?? {}; + const deserializeHelper = options?.processResponseBody ?? deserializer; + if (!deserializeHelper) { + throw new Error( + `Please ensure the operation is in this client! We can't find its deserializeHelper for ${sourceOperation?.name}.`, + ); + } + return getLongRunningPoller( + (client as any)["_client"] ?? client, + deserializeHelper as (result: TResponse) => Promise, + expectedStatuses, + { + updateIntervalInMs: options?.updateIntervalInMs, + abortSignal: options?.abortSignal, + resourceLocationConfig, + restoreFrom: serializedState, + initialRequestUrl, + }, + ); +} + +interface DeserializationHelper { + deserializer: (result: PathUncheckedResponse) => Promise; + expectedStatuses: string[]; +} + +const deserializeMap: Record = { + "POST /document/batches": { + deserializer: _startTranslationDeserialize, + expectedStatuses: ["202", "200", "201"], + }, +}; + +function getDeserializationHelper( + urlStr: string, + method: string, +): DeserializationHelper | undefined { + const path = new URL(urlStr).pathname; + const pathParts = path.split("/"); + + // Traverse list to match the longest candidate + // matchedLen: the length of candidate path + // matchedValue: the matched status code array + let matchedLen = -1, + matchedValue: DeserializationHelper | undefined; + + // Iterate the responseMap to find a match + for (const [key, value] of Object.entries(deserializeMap)) { + // Extracting the path from the map key which is in format + // GET /path/foo + if (!key.startsWith(method)) { + continue; + } + const candidatePath = getPathFromMapKey(key); + // Get each part of the url path + const candidateParts = candidatePath.split("/"); + + // track if we have found a match to return the values found. + let found = true; + for (let i = candidateParts.length - 1, j = pathParts.length - 1; i >= 1 && j >= 1; i--, j--) { + if (candidateParts[i]?.startsWith("{") && candidateParts[i]?.indexOf("}") !== -1) { + const start = candidateParts[i]!.indexOf("}") + 1, + end = candidateParts[i]?.length; + // If the current part of the candidate is a "template" part + // Try to use the suffix of pattern to match the path + // {guid} ==> $ + // {guid}:export ==> :export$ + const isMatched = new RegExp(`${candidateParts[i]?.slice(start, end)}`).test( + pathParts[j] || "", + ); + + if (!isMatched) { + found = false; + break; + } + continue; + } + + // If the candidate part is not a template and + // the parts don't match mark the candidate as not found + // to move on with the next candidate path. + if (candidateParts[i] !== pathParts[j]) { + found = false; + break; + } + } + + // We finished evaluating the current candidate parts + // Update the matched value if and only if we found the longer pattern + if (found && candidatePath.length > matchedLen) { + matchedLen = candidatePath.length; + matchedValue = value; + } + } + + return matchedValue; +} + +function getPathFromMapKey(mapKey: string): string { + const pathStart = mapKey.indexOf("/"); + return mapKey.slice(pathStart); +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/index.ts new file mode 100644 index 0000000000..36fee5bacb --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/index.ts @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { FileContents } from "./static-helpers/multipartHelpers.js"; +import { + PageSettings, + ContinuablePage, + PagedAsyncIterableIterator, +} from "./static-helpers/pagingHelpers.js"; + +export { DocumentTranslationClient } from "./documentTranslation/documentTranslationClient.js"; +export { restorePoller, RestorePollerOptions } from "./documentTranslation/restorePollerHelpers.js"; +export { + StartTranslationDetails, + BatchRequest, + SourceInput, + DocumentFilter, + TranslationStorageSource, + TargetInput, + Glossary, + StorageInputType, + BatchOptions, + TranslationStatus, + Status, + TranslationError, + TranslationErrorCode, + InnerTranslationError, + TranslationStatusSummary, + DocumentStatus, + SupportedFileFormats, + FileFormat, + FileFormatType, + DocumentTranslateContent, + KnownVersions, +} from "./models/index.js"; +export { + DocumentTranslationClientOptionalParams, + GetSupportedFormatsOptionalParams, + GetDocumentsStatusOptionalParams, + CancelTranslationOptionalParams, + GetTranslationStatusOptionalParams, + GetDocumentStatusOptionalParams, + GetTranslationsStatusOptionalParams, + StartTranslationOptionalParams, +} from "./documentTranslation/api/index.js"; +export { PageSettings, ContinuablePage, PagedAsyncIterableIterator }; +export { FileContents }; +export { SingleDocumentTranslationClient } from "./singleDocumentTranslation/singleDocumentTranslationClient.js"; +export { + TranslateOptionalParams, + SingleDocumentTranslationClientOptionalParams, +} from "./singleDocumentTranslation/api/index.js"; diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/logger.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/logger.ts new file mode 100644 index 0000000000..31a2389c94 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/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("load-testing"); diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/models/index.ts new file mode 100644 index 0000000000..00e4ab2893 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/models/index.ts @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { + StartTranslationDetails, + BatchRequest, + SourceInput, + DocumentFilter, + TranslationStorageSource, + TargetInput, + Glossary, + StorageInputType, + BatchOptions, + TranslationStatus, + Status, + TranslationError, + TranslationErrorCode, + InnerTranslationError, + TranslationStatusSummary, + DocumentStatus, + SupportedFileFormats, + FileFormat, + FileFormatType, + DocumentTranslateContent, + KnownVersions, +} from "./models.js"; diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/models/models.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/models/models.ts new file mode 100644 index 0000000000..7b927c771f --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/models/models.ts @@ -0,0 +1,513 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { FileContents, createFilePartDescriptor } from "../static-helpers/multipartHelpers.js"; + +/** + * This file contains only generated model types and their (de)serializers. + * Disable the following rules for internal models with '_' prefix and deserializers which require 'any' for raw JSON input. + */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +/** Translation job submission batch request */ +export interface StartTranslationDetails { + /** The input list of documents or folders containing documents */ + inputs: BatchRequest[]; + /** The batch operation options */ + options?: BatchOptions; +} + +export function startTranslationDetailsSerializer(item: StartTranslationDetails): any { + return { + inputs: batchRequestArraySerializer(item["inputs"]), + options: !item["options"] ? item["options"] : batchOptionsSerializer(item["options"]), + }; +} + +export function batchRequestArraySerializer(result: Array): any[] { + return result.map((item) => { + return batchRequestSerializer(item); + }); +} + +/** Definition for the input batch translation request */ +export interface BatchRequest { + /** Source of the input documents */ + source: SourceInput; + /** Location of the destination for the output */ + targets: TargetInput[]; + /** Storage type of the input documents source string */ + storageType?: StorageInputType; +} + +export function batchRequestSerializer(item: BatchRequest): any { + return { + source: sourceInputSerializer(item["source"]), + targets: targetInputArraySerializer(item["targets"]), + storageType: item["storageType"], + }; +} + +/** Source of the input documents */ +export interface SourceInput { + /** Location of the folder / container or single file with your documents */ + sourceUrl: string; + /** Document filter */ + filter?: DocumentFilter; + /** + * Language code + * If none is specified, we will perform auto detect on the document + */ + language?: string; + /** Storage Source */ + storageSource?: TranslationStorageSource; +} + +export function sourceInputSerializer(item: SourceInput): any { + return { + sourceUrl: item["sourceUrl"], + filter: !item["filter"] ? item["filter"] : documentFilterSerializer(item["filter"]), + language: item["language"], + storageSource: item["storageSource"], + }; +} + +/** Document filter */ +export interface DocumentFilter { + /** + * A case-sensitive prefix string to filter documents in the source path for + * translation. + * For example, when using a Azure storage blob Uri, use the prefix + * to restrict sub folders for translation. + */ + prefix?: string; + /** + * A case-sensitive suffix string to filter documents in the source path for + * translation. + * This is most often use for file extensions + */ + suffix?: string; +} + +export function documentFilterSerializer(item: DocumentFilter): any { + return { prefix: item["prefix"], suffix: item["suffix"] }; +} + +/** Storage Source */ +export type TranslationStorageSource = "AzureBlob"; + +export function targetInputArraySerializer(result: Array): any[] { + return result.map((item) => { + return targetInputSerializer(item); + }); +} + +/** Destination for the finished translated documents */ +export interface TargetInput { + /** Location of the folder / container with your documents */ + targetUrl: string; + /** Category / custom system for translation request */ + category?: string; + /** Target Language */ + language: string; + /** List of Glossary */ + glossaries?: Glossary[]; + /** Storage Source */ + storageSource?: TranslationStorageSource; +} + +export function targetInputSerializer(item: TargetInput): any { + return { + targetUrl: item["targetUrl"], + category: item["category"], + language: item["language"], + glossaries: !item["glossaries"] + ? item["glossaries"] + : glossaryArraySerializer(item["glossaries"]), + storageSource: item["storageSource"], + }; +} + +export function glossaryArraySerializer(result: Array): any[] { + return result.map((item) => { + return glossarySerializer(item); + }); +} + +/** Glossary / translation memory for the request */ +export interface Glossary { + /** + * Location of the glossary. + * We will use the file extension to extract the + * formatting if the format parameter is not supplied. + * + * If the translation + * language pair is not present in the glossary, it will not be applied + */ + glossaryUrl: string; + /** Format */ + format: string; + /** Optional Version. If not specified, default is used. */ + version?: string; + /** Storage Source */ + storageSource?: TranslationStorageSource; +} + +export function glossarySerializer(item: Glossary): any { + return { + glossaryUrl: item["glossaryUrl"], + format: item["format"], + version: item["version"], + storageSource: item["storageSource"], + }; +} + +/** Storage type of the input documents source string */ +export type StorageInputType = "Folder" | "File"; + +/** Translation batch request options */ +export interface BatchOptions { + /** Translation text within an image option */ + translateTextWithinImage?: boolean; +} + +export function batchOptionsSerializer(item: BatchOptions): any { + return { translateTextWithinImage: item["translateTextWithinImage"] }; +} + +/** Translation job status response */ +export interface TranslationStatus { + /** Id of the translation operation. */ + id: string; + /** Operation created date time */ + createdDateTimeUtc: Date; + /** Date time in which the operation's status has been updated */ + lastActionDateTimeUtc: Date; + /** List of possible statuses for job or document */ + status: Status; + /** + * This contains an outer error with error code, message, details, target and an + * inner error with more descriptive details. + */ + error?: TranslationError; + /** Status Summary */ + summary: TranslationStatusSummary; +} + +export function translationStatusDeserializer(item: any): TranslationStatus { + return { + id: item["id"], + createdDateTimeUtc: new Date(item["createdDateTimeUtc"]), + lastActionDateTimeUtc: new Date(item["lastActionDateTimeUtc"]), + status: item["status"], + error: !item["error"] ? item["error"] : translationErrorDeserializer(item["error"]), + summary: translationStatusSummaryDeserializer(item["summary"]), + }; +} + +/** List of possible statuses for job or document */ +export type Status = + | "NotStarted" + | "Running" + | "Succeeded" + | "Failed" + | "Cancelled" + | "Cancelling" + | "ValidationFailed"; + +/** + * This contains an outer error with error code, message, details, target and an + * inner error with more descriptive details. + */ +export interface TranslationError { + /** Enums containing high level error codes. */ + code: TranslationErrorCode; + /** Gets high level error message. */ + message: string; + /** + * Gets the source of the error. + * For example it would be "documents" or + * "document id" in case of invalid document. + */ + readonly target?: string; + /** + * New Inner Error format which conforms to Cognitive Services API Guidelines + * which is available at + * https://microsoft.sharepoint.com/%3Aw%3A/t/CognitiveServicesPMO/EUoytcrjuJdKpeOKIK_QRC8BPtUYQpKBi8JsWyeDMRsWlQ?e=CPq8ow. + * This + * contains required properties ErrorCode, message and optional properties target, + * details(key value pair), inner error(this can be nested). + */ + innerError?: InnerTranslationError; +} + +export function translationErrorDeserializer(item: any): TranslationError { + return { + code: item["code"], + message: item["message"], + target: item["target"], + innerError: !item["innerError"] + ? item["innerError"] + : innerTranslationErrorDeserializer(item["innerError"]), + }; +} + +/** Enums containing high level error codes. */ +export type TranslationErrorCode = + | "InvalidRequest" + | "InvalidArgument" + | "InternalServerError" + | "ServiceUnavailable" + | "ResourceNotFound" + | "Unauthorized" + | "RequestRateTooHigh"; + +/** + * New Inner Error format which conforms to Cognitive Services API Guidelines + * which is available at + * https://microsoft.sharepoint.com/%3Aw%3A/t/CognitiveServicesPMO/EUoytcrjuJdKpeOKIK_QRC8BPtUYQpKBi8JsWyeDMRsWlQ?e=CPq8ow. + * This + * contains required properties ErrorCode, message and optional properties target, + * details(key value pair), inner error(this can be nested). + */ +export interface InnerTranslationError { + /** Gets code error string. */ + code: string; + /** Gets high level error message. */ + message: string; + /** + * Gets the source of the error. + * For example it would be "documents" or + * "document id" in case of invalid document. + */ + readonly target?: string; + /** + * New Inner Error format which conforms to Cognitive Services API Guidelines + * which is available at + * https://microsoft.sharepoint.com/%3Aw%3A/t/CognitiveServicesPMO/EUoytcrjuJdKpeOKIK_QRC8BPtUYQpKBi8JsWyeDMRsWlQ?e=CPq8ow. + * This + * contains required properties ErrorCode, message and optional properties target, + * details(key value pair), inner error(this can be nested). + */ + innerError?: InnerTranslationError; +} + +export function innerTranslationErrorDeserializer(item: any): InnerTranslationError { + return { + code: item["code"], + message: item["message"], + target: item["target"], + innerError: !item["innerError"] + ? item["innerError"] + : innerTranslationErrorDeserializer(item["innerError"]), + }; +} + +/** Status Summary */ +export interface TranslationStatusSummary { + /** Total count */ + total: number; + /** Failed count */ + failed: number; + /** Number of Success */ + success: number; + /** Number of in progress */ + inProgress: number; + /** Count of not yet started */ + notYetStarted: number; + /** Number of cancelled */ + cancelled: number; + /** Total characters charged by the API */ + totalCharacterCharged: number; + /** Total image scans charged by the API */ + totalImageScansSucceeded?: number; + /** Total image scans failed */ + totalImageScansFailed?: number; +} + +export function translationStatusSummaryDeserializer(item: any): TranslationStatusSummary { + return { + total: item["total"], + failed: item["failed"], + success: item["success"], + inProgress: item["inProgress"], + notYetStarted: item["notYetStarted"], + cancelled: item["cancelled"], + totalCharacterCharged: item["totalCharacterCharged"], + totalImageScansSucceeded: item["totalImageScansSucceeded"], + totalImageScansFailed: item["totalImageScansFailed"], + }; +} + +/** Translation job Status Response */ +export interface _TranslationsStatus { + /** The summary status of individual operation */ + value: TranslationStatus[]; + /** Url for the next page. Null if no more pages available */ + nextLink?: string; +} + +export function _translationsStatusDeserializer(item: any): _TranslationsStatus { + return { + value: translationStatusArrayDeserializer(item["value"]), + nextLink: item["nextLink"], + }; +} + +export function translationStatusArrayDeserializer(result: Array): any[] { + return result.map((item) => { + return translationStatusDeserializer(item); + }); +} + +/** Document Status Response */ +export interface DocumentStatus { + /** Location of the document or folder */ + path?: string; + /** Location of the source document */ + sourcePath: string; + /** Operation created date time */ + createdDateTimeUtc: Date; + /** Date time in which the operation's status has been updated */ + lastActionDateTimeUtc: Date; + /** List of possible statuses for job or document */ + status: Status; + /** To language */ + to: string; + /** + * This contains an outer error with error code, message, details, target and an + * inner error with more descriptive details. + */ + error?: TranslationError; + /** Progress of the translation if available */ + progress: number; + /** Document Id */ + id: string; + /** Character charged by the API */ + characterCharged?: number; + /** Total image scans charged by the API */ + totalImageScansSucceeded?: number; + /** Total image scans failed */ + totalImageScansFailed?: number; +} + +export function documentStatusDeserializer(item: any): DocumentStatus { + return { + path: item["path"], + sourcePath: item["sourcePath"], + createdDateTimeUtc: new Date(item["createdDateTimeUtc"]), + lastActionDateTimeUtc: new Date(item["lastActionDateTimeUtc"]), + status: item["status"], + to: item["to"], + error: !item["error"] ? item["error"] : translationErrorDeserializer(item["error"]), + progress: item["progress"], + id: item["id"], + characterCharged: item["characterCharged"], + totalImageScansSucceeded: item["totalImageScansSucceeded"], + totalImageScansFailed: item["totalImageScansFailed"], + }; +} + +/** Documents Status Response */ +export interface _DocumentsStatus { + /** The detail status of individual documents */ + value: DocumentStatus[]; + /** Url for the next page. Null if no more pages available */ + nextLink?: string; +} + +export function _documentsStatusDeserializer(item: any): _DocumentsStatus { + return { + value: documentStatusArrayDeserializer(item["value"]), + nextLink: item["nextLink"], + }; +} + +export function documentStatusArrayDeserializer(result: Array): any[] { + return result.map((item) => { + return documentStatusDeserializer(item); + }); +} + +/** List of supported file formats */ +export interface SupportedFileFormats { + /** list of objects */ + value: FileFormat[]; +} + +export function supportedFileFormatsDeserializer(item: any): SupportedFileFormats { + return { + value: fileFormatArrayDeserializer(item["value"]), + }; +} + +export function fileFormatArrayDeserializer(result: Array): any[] { + return result.map((item) => { + return fileFormatDeserializer(item); + }); +} + +/** File Format */ +export interface FileFormat { + /** Name of the format */ + format: string; + /** Supported file extension for this format */ + fileExtensions: string[]; + /** Supported Content-Types for this format */ + contentTypes: string[]; + /** Default version if none is specified */ + defaultVersion?: string; + /** Supported Version */ + versions?: string[]; + /** Supported Type for this format */ + type?: FileFormatType; +} + +export function fileFormatDeserializer(item: any): FileFormat { + return { + format: item["format"], + fileExtensions: item["fileExtensions"].map((p: any) => { + return p; + }), + contentTypes: item["contentTypes"].map((p: any) => { + return p; + }), + defaultVersion: item["defaultVersion"], + versions: !item["versions"] + ? item["versions"] + : item["versions"].map((p: any) => { + return p; + }), + type: item["type"], + }; +} + +/** Format types */ +export type FileFormatType = "document" | "glossary"; + +/** Document Translate Request Content */ +export interface DocumentTranslateContent { + /** Document to be translated in the form */ + document: FileContents | { contents: FileContents; contentType?: string; filename?: string }; + /** Glossary-translation memory will be used during translation in the form. */ + glossary?: Array< + FileContents | { contents: FileContents; contentType?: string; filename?: string } + >; +} + +export function documentTranslateContentSerializer(item: DocumentTranslateContent): any { + return [ + createFilePartDescriptor("document", item["document"], "application/octet-stream"), + ...(item["glossary"] === undefined + ? [] + : [createFilePartDescriptor("glossary", item["glossary"], "application/json")]), + ]; +} + +/** Document Translation supported versions */ +export enum KnownVersions { + /** 2024-05-01 */ + V20240501 = "2024-05-01", + /** 2024-11-01-preview */ + V20241101Preview = "2024-11-01-preview", +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/index.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/index.ts new file mode 100644 index 0000000000..c7ed85da11 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/index.ts @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { translate } from "./operations.js"; +export { TranslateOptionalParams } from "./options.js"; +export { + createSingleDocumentTranslation, + SingleDocumentTranslationContext, + SingleDocumentTranslationClientOptionalParams, +} from "./singleDocumentTranslationContext.js"; diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/operations.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/operations.ts new file mode 100644 index 0000000000..fccfe0a10a --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/operations.ts @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { SingleDocumentTranslationContext as Client } from "./index.js"; +import { + DocumentTranslateContent, + documentTranslateContentSerializer, +} from "../../models/models.js"; +import { getBinaryResponse } from "../../static-helpers/serialization/get-binary-response.js"; +import { expandUrlTemplate } from "../../static-helpers/urlTemplate.js"; +import { TranslateOptionalParams } from "./options.js"; +import { + StreamableMethod, + PathUncheckedResponse, + createRestError, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; + +export function _translateSend( + context: Client, + targetLanguage: string, + body: DocumentTranslateContent, + options: TranslateOptionalParams = { requestOptions: {} }, +): StreamableMethod { + const path = expandUrlTemplate( + "/document:translate{?api%2Dversion,sourceLanguage,targetLanguage,category,allowFallback,translateTextWithinImage}", + { + "api%2Dversion": context.apiVersion, + sourceLanguage: options?.sourceLanguage, + targetLanguage: targetLanguage, + category: options?.category, + allowFallback: options?.allowFallback, + translateTextWithinImage: options?.translateTextWithinImage, + }, + { + allowReserved: options?.requestOptions?.skipUrlEncoding, + }, + ); + return context + .path(path) + .post({ + ...operationOptionsToRequestParameters(options), + contentType: "multipart/form-data", + headers: { + ...(options?.clientRequestId !== undefined + ? { "x-ms-client-request-id": options?.clientRequestId } + : {}), + accept: "application/octet-stream", + ...options.requestOptions?.headers, + }, + body: documentTranslateContentSerializer(body), + }); +} + +export async function _translateDeserialize(result: PathUncheckedResponse): Promise { + const expectedStatuses = ["200"]; + if (!expectedStatuses.includes(result.status)) { + throw createRestError(result); + } + + return result.body; +} + +/** Use this API to submit a single translation request to the Document Translation Service. */ +export async function translate( + context: Client, + targetLanguage: string, + body: DocumentTranslateContent, + options: TranslateOptionalParams = { requestOptions: {} }, +): Promise { + const streamableMethod = _translateSend(context, targetLanguage, body, options); + const result = await getBinaryResponse(streamableMethod); + return _translateDeserialize(result); +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/options.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/options.ts new file mode 100644 index 0000000000..21b56aeb77 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/options.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { OperationOptions } from "@azure-rest/core-client"; + +/** Optional parameters. */ +export interface TranslateOptionalParams extends OperationOptions { + /** + * Specifies source language of the input document. + * If this parameter isn't specified, automatic language detection is applied to determine the source language. + * For example if the source document is written in English, then use sourceLanguage=en + */ + sourceLanguage?: string; + /** + * A string specifying the category (domain) of the translation. This parameter is used to get translations + * from a customized system built with Custom Translator. Add the Category ID from your Custom Translator + * project details to this parameter to use your deployed customized system. Default value is: general. + */ + category?: string; + /** + * Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. + * Possible values are: true (default) or false. + */ + allowFallback?: boolean; + /** Optional boolean parameter to translate text within an image in the document */ + translateTextWithinImage?: boolean; + /** An opaque, globally-unique, client-generated string identifier for the request. */ + clientRequestId?: string; +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/singleDocumentTranslationContext.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/singleDocumentTranslationContext.ts new file mode 100644 index 0000000000..1367b83de2 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/api/singleDocumentTranslationContext.ts @@ -0,0 +1,61 @@ +// 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, TokenCredential } from "@azure/core-auth"; + +export interface SingleDocumentTranslationContext 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 SingleDocumentTranslationClientOptionalParams extends ClientOptions { + /** The API version to use for this operation. */ + /** Known values of {@link KnownVersions} that the service accepts. */ + apiVersion?: string; +} + +export function createSingleDocumentTranslation( + endpointParam: string, + credential: KeyCredential | TokenCredential, + options: SingleDocumentTranslationClientOptionalParams = {}, +): SingleDocumentTranslationContext { + const endpointUrl = options.endpoint ?? `${endpointParam}/translator`; + const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix; + const userAgentInfo = `azsdk-js-load-testing/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 }, + credentials: { + scopes: options.credentials?.scopes ?? ["https://cognitiveservices.azure.com/.default"], + apiKeyHeaderName: options.credentials?.apiKeyHeaderName ?? "Ocp-Apim-Subscription-Key", + }, + }; + const clientContext = getClient(endpointUrl, credential, updatedOptions); + clientContext.pipeline.removePolicy({ name: "ApiVersionPolicy" }); + const apiVersion = options.apiVersion ?? "2024-11-01-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 SingleDocumentTranslationContext; +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/index.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/index.ts new file mode 100644 index 0000000000..b9ea658307 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/index.ts @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +export { SingleDocumentTranslationClient } from "./singleDocumentTranslationClient.js"; +export { + TranslateOptionalParams, + SingleDocumentTranslationContext, + SingleDocumentTranslationClientOptionalParams, +} from "./api/index.js"; diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/singleDocumentTranslationClient.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/singleDocumentTranslationClient.ts new file mode 100644 index 0000000000..99bf1802c5 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/singleDocumentTranslation/singleDocumentTranslationClient.ts @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + createSingleDocumentTranslation, + SingleDocumentTranslationContext, + SingleDocumentTranslationClientOptionalParams, +} from "./api/index.js"; +import { DocumentTranslateContent } from "../models/models.js"; +import { translate } from "./api/operations.js"; +import { TranslateOptionalParams } from "./api/options.js"; +import { KeyCredential, TokenCredential } from "@azure/core-auth"; +import { Pipeline } from "@azure/core-rest-pipeline"; + +export { SingleDocumentTranslationClientOptionalParams } from "./api/singleDocumentTranslationContext.js"; + +export class SingleDocumentTranslationClient { + private _client: SingleDocumentTranslationContext; + /** The pipeline used by this client to make requests */ + public readonly pipeline: Pipeline; + + constructor( + endpointParam: string, + credential: KeyCredential | TokenCredential, + options: SingleDocumentTranslationClientOptionalParams = {}, + ) { + const prefixFromOptions = options?.userAgentOptions?.userAgentPrefix; + const userAgentPrefix = prefixFromOptions + ? `${prefixFromOptions} azsdk-js-client` + : `azsdk-js-client`; + this._client = createSingleDocumentTranslation(endpointParam, credential, { + ...options, + userAgentOptions: { userAgentPrefix }, + }); + this.pipeline = this._client.pipeline; + } + + /** Use this API to submit a single translation request to the Document Translation Service. */ + translate( + targetLanguage: string, + body: DocumentTranslateContent, + options: TranslateOptionalParams = { requestOptions: {} }, + ): Promise { + return translate(this._client, targetLanguage, body, options); + } +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/multipartHelpers.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/multipartHelpers.ts new file mode 100644 index 0000000000..a2770ad2ab --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/multipartHelpers.ts @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * Valid values for the contents of a binary file. + */ +export type FileContents = + | string + | NodeJS.ReadableStream + | ReadableStream + | Uint8Array + | Blob; + +export function createFilePartDescriptor( + partName: string, + fileInput: any, + defaultContentType?: string, +): any { + if (fileInput.contents) { + return { + name: partName, + body: fileInput.contents, + contentType: fileInput.contentType ?? defaultContentType, + filename: fileInput.filename, + }; + } else { + return { + name: partName, + body: fileInput, + contentType: defaultContentType, + }; + } +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/pagingHelpers.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/pagingHelpers.ts new file mode 100644 index 0000000000..5a3472a3f0 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/pagingHelpers.ts @@ -0,0 +1,245 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { Client, createRestError, PathUncheckedResponse } from "@azure-rest/core-client"; +import { RestError } from "@azure/core-rest-pipeline"; + +/** + * Options for the byPage method + */ +export interface PageSettings { + /** + * A reference to a specific page to start iterating from. + */ + continuationToken?: string; +} + +/** + * An interface that describes a page of results. + */ +export type ContinuablePage = TPage & { + /** + * The token that keeps track of where to continue the iterator + */ + continuationToken?: string; +}; + +/** + * An interface that allows async iterable iteration both to completion and by page. + */ +export interface PagedAsyncIterableIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * The next method, part of the iteration protocol + */ + next(): Promise>; + /** + * The connection to the async iterator, part of the iteration protocol + */ + [Symbol.asyncIterator](): PagedAsyncIterableIterator; + /** + * Return an AsyncIterableIterator that works a page at a time + */ + byPage: (settings?: TPageSettings) => AsyncIterableIterator>; +} + +/** + * An interface that describes how to communicate with the service. + */ +export interface PagedResult< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +> { + /** + * Link to the first page of results. + */ + firstPageLink?: string; + /** + * A method that returns a page of results. + */ + getPage: (pageLink?: string) => Promise<{ page: TPage; nextPageLink?: string } | undefined>; + /** + * a function to implement the `byPage` method on the paged async iterator. + */ + byPage?: (settings?: TPageSettings) => AsyncIterableIterator>; + + /** + * A function to extract elements from a page. + */ + toElements?: (page: TPage) => TElement[]; +} + +/** + * Options for the paging helper + */ +export interface BuildPagedAsyncIteratorOptions { + itemName?: string; + nextLinkName?: string; + nextLinkMethod?: "GET" | "POST"; +} + +/** + * Helper to paginate results in a generic way and return a PagedAsyncIterableIterator + */ +export function buildPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, + TResponse extends PathUncheckedResponse = PathUncheckedResponse, +>( + client: Client, + getInitialResponse: () => PromiseLike, + processResponseBody: (result: TResponse) => PromiseLike, + expectedStatuses: string[], + options: BuildPagedAsyncIteratorOptions = {}, +): PagedAsyncIterableIterator { + const itemName = options.itemName ?? "value"; + const nextLinkName = options.nextLinkName ?? "nextLink"; + const nextLinkMethod = options.nextLinkMethod ?? "GET"; + const pagedResult: PagedResult = { + getPage: async (pageLink?: string) => { + const result = + pageLink === undefined + ? await getInitialResponse() + : nextLinkMethod === "POST" + ? await client.pathUnchecked(pageLink).post() + : await client.pathUnchecked(pageLink).get(); + checkPagingRequest(result, expectedStatuses); + const results = await processResponseBody(result as TResponse); + const nextLink = getNextLink(results, nextLinkName); + const values = getElements(results, itemName) as TPage; + return { + page: values, + nextPageLink: nextLink, + }; + }, + byPage: (settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }, + }; + return getPagedAsyncIterator(pagedResult); +} + +/** + * returns an async iterator that iterates over results. It also has a `byPage` + * method that returns pages of items at once. + * + * @param pagedResult - an object that specifies how to get pages. + * @returns a paged async iterator that iterates over results. + */ + +function getPagedAsyncIterator< + TElement, + TPage = TElement[], + TPageSettings extends PageSettings = PageSettings, +>( + pagedResult: PagedResult, +): PagedAsyncIterableIterator { + const iter = getItemAsyncIterator(pagedResult); + return { + next() { + return iter.next(); + }, + [Symbol.asyncIterator]() { + return this; + }, + byPage: + pagedResult?.byPage ?? + ((settings?: TPageSettings) => { + const { continuationToken } = settings ?? {}; + return getPageAsyncIterator(pagedResult, { + pageLink: continuationToken, + }); + }), + }; +} + +async function* getItemAsyncIterator( + pagedResult: PagedResult, +): AsyncIterableIterator { + const pages = getPageAsyncIterator(pagedResult); + for await (const page of pages) { + yield* page as unknown as TElement[]; + } +} + +async function* getPageAsyncIterator( + pagedResult: PagedResult, + options: { + pageLink?: string; + } = {}, +): AsyncIterableIterator> { + const { pageLink } = options; + let response = await pagedResult.getPage(pageLink ?? pagedResult.firstPageLink); + if (!response) { + return; + } + let result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + while (response.nextPageLink) { + response = await pagedResult.getPage(response.nextPageLink); + if (!response) { + return; + } + result = response.page as ContinuablePage; + result.continuationToken = response.nextPageLink; + yield result; + } +} + +/** + * Gets for the value of nextLink in the body + */ +function getNextLink(body: unknown, nextLinkName?: string): string | undefined { + if (!nextLinkName) { + return undefined; + } + + const nextLink = (body as Record)[nextLinkName]; + + if (typeof nextLink !== "string" && typeof nextLink !== "undefined" && nextLink !== null) { + throw new RestError( + `Body Property ${nextLinkName} should be a string or undefined or null but got ${typeof nextLink}`, + ); + } + + if (nextLink === null) { + return undefined; + } + + return nextLink; +} + +/** + * Gets the elements of the current request in the body. + */ +function getElements(body: unknown, itemName: string): T[] { + const value = (body as Record)[itemName] as T[]; + if (!Array.isArray(value)) { + throw new RestError( + `Couldn't paginate response\n Body doesn't contain an array property with name: ${itemName}`, + ); + } + + return value ?? []; +} + +/** + * Checks if a request failed + */ +function checkPagingRequest(response: PathUncheckedResponse, expectedStatuses: string[]): void { + if (!expectedStatuses.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response, + ); + } +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/pollingHelpers.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/pollingHelpers.ts new file mode 100644 index 0000000000..f01c41bab6 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/pollingHelpers.ts @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import { + PollerLike, + OperationState, + ResourceLocationConfig, + RunningOperation, + createHttpPoller, + OperationResponse, +} from "@azure/core-lro"; + +import { Client, PathUncheckedResponse, createRestError } from "@azure-rest/core-client"; +import { AbortSignalLike } from "@azure/abort-controller"; + +export interface GetLongRunningPollerOptions { + /** Delay to wait until next poll, in milliseconds. */ + updateIntervalInMs?: number; + /** + * The signal which can be used to abort requests. + */ + abortSignal?: AbortSignalLike; + /** + * The potential location of the result of the LRO if specified by the LRO extension in the swagger. + */ + resourceLocationConfig?: ResourceLocationConfig; + /** + * The original url of the LRO + * Should not be null when restoreFrom is set + */ + initialRequestUrl?: string; + /** + * A serialized poller which can be used to resume an existing paused Long-Running-Operation. + */ + restoreFrom?: string; + /** + * The function to get the initial response + */ + getInitialResponse?: () => PromiseLike; +} +export function getLongRunningPoller( + client: Client, + processResponseBody: (result: TResponse) => Promise, + expectedStatuses: string[], + options: GetLongRunningPollerOptions, +): PollerLike, TResult> { + const { restoreFrom, getInitialResponse } = options; + if (!restoreFrom && !getInitialResponse) { + throw new Error("Either restoreFrom or getInitialResponse must be specified"); + } + let initialResponse: TResponse | undefined = undefined; + const pollAbortController = new AbortController(); + const poller: RunningOperation = { + sendInitialRequest: async () => { + if (!getInitialResponse) { + throw new Error("getInitialResponse is required when initializing a new poller"); + } + initialResponse = await getInitialResponse(); + return getLroResponse(initialResponse, expectedStatuses); + }, + sendPollRequest: async ( + path: string, + pollOptions?: { + abortSignal?: AbortSignalLike; + }, + ) => { + // The poll request would both listen to the user provided abort signal and the poller's own abort signal + function abortListener(): void { + pollAbortController.abort(); + } + const abortSignal = pollAbortController.signal; + if (options.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (pollOptions?.abortSignal?.aborted) { + pollAbortController.abort(); + } else if (!abortSignal.aborted) { + options.abortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + pollOptions?.abortSignal?.addEventListener("abort", abortListener, { + once: true, + }); + } + let response; + try { + response = await client.pathUnchecked(path).get({ abortSignal }); + } finally { + options.abortSignal?.removeEventListener("abort", abortListener); + pollOptions?.abortSignal?.removeEventListener("abort", abortListener); + } + + return getLroResponse(response as TResponse, expectedStatuses); + }, + }; + return createHttpPoller(poller, { + intervalInMs: options?.updateIntervalInMs, + resourceLocationConfig: options?.resourceLocationConfig, + restoreFrom: options?.restoreFrom, + processResult: (result: unknown) => { + return processResponseBody(result as TResponse); + }, + }); +} +/** + * Converts a Rest Client response to a response that the LRO implementation understands + * @param response - a rest client http response + * @param deserializeFn - deserialize function to convert Rest response to modular output + * @returns - An LRO response that the LRO implementation understands + */ +function getLroResponse( + response: TResponse, + expectedStatuses: string[], +): OperationResponse { + if (!expectedStatuses.includes(response.status)) { + throw createRestError(response); + } + + return { + flatResponse: response, + rawResponse: { + ...response, + statusCode: Number.parseInt(response.status), + body: response.body, + }, + }; +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/serialization/get-binary-response-browser.mts b/packages/typespec-test/test/DocumentTranslation/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/DocumentTranslation/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/DocumentTranslation/generated/typespec-ts/src/static-helpers/serialization/get-binary-response.ts b/packages/typespec-test/test/DocumentTranslation/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/DocumentTranslation/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/DocumentTranslation/generated/typespec-ts/src/static-helpers/urlTemplate.ts b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/urlTemplate.ts new file mode 100644 index 0000000000..c710989869 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/src/static-helpers/urlTemplate.ts @@ -0,0 +1,227 @@ +// 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 { + const result = 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(""); + }); + + return normalizeUnreserved(result); +} + +/** + * Normalize an expanded URI by decoding percent-encoded unreserved characters. + * RFC 3986 unreserved: "-" / "." / "~" + */ +function normalizeUnreserved(uri: string): string { + return uri.replace(/%([0-9A-Fa-f]{2})/g, (match, hex) => { + const char = String.fromCharCode(parseInt(hex, 16)); + // Decode only if it's unreserved + if (/[\-.~]/.test(char)) { + return char; + } + return match; // leave other encodings intact + }); +} diff --git a/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/tsconfig.json b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/tsconfig.json new file mode 100644 index 0000000000..1c76cd27c3 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/generated/typespec-ts/tsconfig.json @@ -0,0 +1,26 @@ +{ + "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, + "paths": { + "@azure/load-testing": ["./src/index"] + } + }, + "include": ["src/**/*.ts", "samples-dev/**/*.ts"] +} diff --git a/packages/typespec-test/test/DocumentTranslation/spec/client.tsp b/packages/typespec-test/test/DocumentTranslation/spec/client.tsp new file mode 100644 index 0000000000..552e0ebb48 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/spec/client.tsp @@ -0,0 +1,275 @@ +/** + * PLACEHOLDER + * Add readme and sample + */ +import "./main.tsp"; +import "@azure-tools/typespec-client-generator-core"; + +using Azure.ClientGenerator.Core; +using TypeSpec.Versioning; + +@TypeSpec.Versioning.useDependency( + DocumentTranslation.Versions.v2024_11_01_preview +) +namespace ClientCustomizations; + +@client({ + name: "DocumentTranslationClient", + service: DocumentTranslation, +}) +interface DocumentTranslationClient { + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + startTranslation is DocumentTranslation.DocumentTranslationOperations.startTranslation; + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/use-standard-names" "Existing name" + getTranslationsStatus is DocumentTranslation.DocumentTranslationOperations.getTranslationsStatus; + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + getDocumentStatus is DocumentTranslation.DocumentTranslationOperations.getDocumentStatus; + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + getTranslationStatus is DocumentTranslation.DocumentTranslationOperations.getTranslationStatus; + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/use-standard-names" "Existing name" + cancelTranslation is DocumentTranslation.DocumentTranslationOperations.cancelTranslation; + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/use-standard-names" "Existing name" + getDocumentsStatus is DocumentTranslation.DocumentTranslationOperations.getDocumentsStatus; + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + getSupportedFormats is DocumentTranslation.DocumentTranslationOperations.getSupportedFormats; +} + +@client({ + name: "SingleDocumentTranslationClient", + service: DocumentTranslation, +}) +interface SingleDocumentTranslationClient { + #suppress "@azure-tools/typespec-azure-core/byos" + documentTranslate is DocumentTranslation.DocumentTranslationOperations.documentTranslate; +} + +@@clientName(SingleDocumentTranslationClient.documentTranslate, "translate"); + +@@clientName(DocumentTranslation.StatusSummary, "translationStatusSummary"); + +@@clientName(DocumentTranslation.StorageSource, "translationStorageSource"); + +@@clientName(DocumentTranslation.GetTranslationsStatusOptions.ids, + "translationIds" +); + +@@clientName(DocumentTranslation.GetDocumentsStatusOptions.id, "translationId"); + +@@clientName(DocumentTranslation.GetDocumentsStatusOptions.ids, "documentIds"); + +@@clientName(DocumentTranslation.DocumentTranslateBody.body, + "documentTranslateContent", + "csharp, java" +); + +@@clientName(DocumentTranslation.StartTranslationDetails, + "translationBatch", + "csharp, java" +); + +@@clientName(DocumentTranslation.SourceInput, + "TranslationSource", + "csharp, java" +); + +@@clientName(DocumentTranslation.TargetInput, + "TranslationTarget", + "csharp, java" +); + +@@clientName(DocumentTranslation.BatchRequest, "documentBatch", "python"); + +@@convenientAPI(DocumentTranslationClient.getSupportedFormats, false, "java"); + +@@convenientAPI(DocumentTranslationClient.getTranslationsStatus, false, "java"); + +@@convenientAPI(DocumentTranslationClient.getDocumentsStatus, false, "java"); + +@@access(DocumentTranslationClient.getSupportedFormats, + Access.internal, + "python" +); +@@access(DocumentTranslation.SupportedFileFormats, Access.internal, "java"); +@@usage(DocumentTranslation.SupportedFileFormats, Usage.output, "java"); + +@@access(DocumentTranslation.FileFormat, Access.public, "java"); +@@usage(DocumentTranslation.FileFormat, Usage.output, "java"); + +@@access(DocumentTranslation.FileFormatType, Access.public, "java"); +@@usage(DocumentTranslation.FileFormatType, Usage.output, "java"); + +@@access(DocumentTranslation.FileFormat, Access.public, "python"); +@@usage(DocumentTranslation.FileFormat, Usage.output, "python"); +@@access(DocumentTranslation.FileFormatType, Access.public, "python"); +@@usage(DocumentTranslation.FileFormatType, Usage.output, "python"); + +@@clientName(DocumentTranslationClient.startTranslation, + "_begin_translation", + "python" +); + +@@clientName(DocumentTranslationClient.getDocumentsStatus, + "list_document_statuses", + "python" +); + +@@clientName(DocumentTranslationClient.getTranslationsStatus, + "list_translation_statuses", + "python" +); + +@@clientName(DocumentTranslation.TranslationStatus.createdDateTimeUtc, + "created_on", + "python" +); + +@@clientName(DocumentTranslation.TranslationStatus.lastActionDateTimeUtc, + "last_updated_on", + "python" +); + +@@clientName(DocumentTranslation.Glossary.format, "file_format", "python"); + +@@clientName(DocumentTranslation.Glossary.version, "format_version", "python"); + +@@clientName(DocumentTranslation.Glossary, + "TranslationGlossary", + "python, java" +); + +@@clientName(DocumentTranslation.DocumentStatus.createdDateTimeUtc, + "created_on", + "python" +); + +@@clientName(DocumentTranslation.DocumentStatus.lastActionDateTimeUtc, + "last_updated_on", + "python" +); + +@@clientName(DocumentTranslation.DocumentStatus.path, + "translated_document_url", + "python" +); + +@@clientName(DocumentTranslation.DocumentStatus.sourcePath, + "source_document_url", + "python" +); + +@@clientName(DocumentTranslation.DocumentStatus.to, "translated_to", "python"); + +@@clientName(DocumentTranslation.DocumentStatus.progress, + "translation_progress", + "python" +); + +@@clientName(DocumentTranslation.DocumentStatus.characterCharged, + "characters_charged", + "python" +); + +@@clientName(DocumentTranslation.StatusSummary.totalCharacterCharged, + "total_characters_charged", + "python" +); + +@@clientName(DocumentTranslation.StatusSummary.cancelled, "canceled", "python"); + +@@clientName(DocumentTranslation.FileFormat.format, "file_format", "python"); + +@@clientName(DocumentTranslation.FileFormat.defaultVersion, + "default_format_version", + "python" +); + +@@clientName(DocumentTranslation.FileFormat.versions, + "format_versions", + "python" +); + +@@clientName(DocumentTranslation.FileFormat, + "DocumentTranslationFileFormat", + "python" +); + +@@clientName(DocumentTranslation.TargetInput.category, "category_id", "python"); + +@@clientName(DocumentTranslation.TargetInput, "TranslationTarget", "python"); + +@@clientName(DocumentTranslation.TranslationError, + "DocumentTranslationError", + "python" +); + +@@clientName(DocumentTranslation.Status.Cancelled, "canceled", "python"); + +@@clientName(DocumentTranslation.Status.Cancelling, "canceling", "python"); + +@@clientName(DocumentTranslation.BatchRequest, + "documentTranslationInput", + "java" +); + +@@clientName(DocumentTranslationClient.startTranslation, "translation", "java"); + +@@clientName(DocumentTranslationClient.getDocumentsStatus, + "listDocumentStatuses", + "java" +); + +@@clientName(DocumentTranslationClient.getTranslationsStatus, + "listTranslationStatuses", + "java" +); + +@@clientName(DocumentTranslation.TranslationStatus, + "translationStatusResult", + "java" +); + +@@clientName(DocumentTranslation.DocumentStatus, + "documentStatusResult", + "java" +); + +@@clientName(DocumentTranslation.Status, "translationStatus", "java"); + +@@clientName(DocumentTranslation.TranslationStatus.createdDateTimeUtc, + "createdOn", + "java" +); + +@@clientName(DocumentTranslation.TranslationStatus.lastActionDateTimeUtc, + "lastUpdatedOn", + "java" +); + +@@clientName(DocumentTranslation.StatusSummary.cancelled, + "cancelledCount", + "java" +); + +@@clientName(DocumentTranslation.StatusSummary.failed, "failedCount", "java"); + +@@clientName(DocumentTranslation.StatusSummary.inProgress, + "inProgressCount", + "java" +); + +@@clientName(DocumentTranslation.StatusSummary.notYetStarted, + "notYetStartedCount", + "java" +); + +@@clientName(DocumentTranslation.StatusSummary.success, "successCount", "java"); + +@@clientName(DocumentTranslation.StatusSummary.total, "totalCount", "java"); + +@@clientName(DocumentTranslation.StatusSummary.totalCharacterCharged, + "totalCharactersChargedCount", + "java" +); diff --git a/packages/typespec-test/test/DocumentTranslation/spec/main.tsp b/packages/typespec-test/test/DocumentTranslation/spec/main.tsp new file mode 100644 index 0000000000..487d842fdb --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/spec/main.tsp @@ -0,0 +1,40 @@ +import "@typespec/rest"; +import "@typespec/http"; +import "./routes.tsp"; +import "@typespec/versioning"; +import "@azure-tools/typespec-azure-core"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; +using Azure.Core; + +@service(#{ title: "Azure.AI.DocumentTranslation" }) +@versioned(DocumentTranslation.Versions) +@useAuth( + ApiKeyAuth | OAuth2Auth<[ + { + type: OAuth2FlowType.authorizationCode, + authorizationUrl: "https://login.microsoftonline.com/common/oauth2/authorize", + tokenUrl: "https://login.microsoftonline.com/common/oauth2/token", + scopes: ["https://cognitiveservices.azure.com/.default"], + } + ]> +) +@server( + "{endpoint}/translator", + "Document translation service endpoint", + { + @doc("Supported document Translation endpoint, protocol and hostname, for example: https://{TranslatorResourceName}.cognitiveservices.azure.com/translator.") + endpoint: url, + } +) +@doc("Document translation service") +namespace DocumentTranslation; + +#suppress "@azure-tools/typespec-azure-core/documentation-required" "https://github.com/Azure/typespec-azure/issues/3107" +@doc("Document Translation supported versions") +enum Versions { + v2024_05_01: "2024-05-01", + v2024_11_01_preview: "2024-11-01-preview", +} diff --git a/packages/typespec-test/test/DocumentTranslation/spec/models.tsp b/packages/typespec-test/test/DocumentTranslation/spec/models.tsp new file mode 100644 index 0000000000..e35e10ca08 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/spec/models.tsp @@ -0,0 +1,685 @@ +import "@typespec/rest"; +import "@typespec/http"; +import "@azure-tools/typespec-azure-core"; + +using TypeSpec.Rest; +using TypeSpec.Http; +using Azure.Core; +using TypeSpec.Versioning; + +namespace DocumentTranslation; +@doc("Document Translate Request Content") +model DocumentTranslateContent { + @doc("Document to be translated in the form") + document: HttpPart; + + @doc("Glossary-translation memory will be used during translation in the form. ") + glossary?: HttpPart; +} + +@doc("Document Translate Result / Response.") +model DocumentTranslateResult { + @doc("response content type") + @header + contentType: "application/octet-stream"; + + @header("x-metered-usage") + @doc("Specifies consumption (the number of characters for which the user will be charged) for the translation job request") + @added(Versions.v2024_11_01_preview) + meteredUsage: int32; + + @header("total-image-scans-succeeded") + @doc("Specifies the number of successful image translations within a document translation job") + @added(Versions.v2024_11_01_preview) + totalImageScansSucceeded: int32; + + @header("total-image-scans-failed") + @doc("Specifies the number of failed image translations within a document translation job") + @added(Versions.v2024_11_01_preview) + totalImageScansFailed: int32; + + @doc("Request response, response is a translated document.") + @body + document: bytes; +} + +@doc("Format types") +union FileFormatType { + @doc("Document type file format") + Document: "document", + + @doc("Glossary type file format") + Glossary: "glossary", + + string, +} + +@doc("Storage Source") +union StorageSource { + @doc("Azure blob storage source") + AzureBlob: "AzureBlob", + + string, +} + +@doc("List of possible additional output formats") +union AdditionalOutputFileFormat { + @doc("No additional format") + None: "None", + + @doc("OpenXmlWord format") + OpenXmlWord: "OpenXmlWord", + + string, +} + +@doc("Storage type of the input documents source string") +union StorageInputType { + @doc("Folder storage input type") + Folder: "Folder", + + @doc("File storage input type") + File: "File", + + string, +} + +@doc("Enums containing high level error codes.") +union TranslationErrorCode { + @doc("InvalidRequest") + InvalidRequest: "InvalidRequest", + + @doc("InvalidArgument") + InvalidArgument: "InvalidArgument", + + @doc("InternalServerError") + InternalServerError: "InternalServerError", + + @doc("ServiceUnavailable") + ServiceUnavailable: "ServiceUnavailable", + + @doc("ResourceNotFound") + ResourceNotFound: "ResourceNotFound", + + @doc("Unauthorized") + Unauthorized: "Unauthorized", + + @doc("RequestRateTooHigh") + RequestRateTooHigh: "RequestRateTooHigh", + + string, +} + +@doc("List of possible statuses for job or document") +union Status { + @doc("NotStarted") + NotStarted: "NotStarted", + + @doc("Running") + Running: "Running", + + @doc("Succeeded") + Succeeded: "Succeeded", + + @doc("Failed") + Failed: "Failed", + + @doc("Cancelled") + Cancelled: "Cancelled", + + @doc("Cancelling") + Cancelling: "Cancelling", + + @doc("ValidationFailed") + ValidationFailed: "ValidationFailed", + + string, +} + +@doc("Translation job submission batch request") +model StartTranslationDetails { + @doc("The input list of documents or folders containing documents") + inputs: BatchRequest[]; + + @doc("The batch operation options") + @added(Versions.v2024_11_01_preview) + options?: BatchOptions; +} + +@doc("Translation batch request options") +@added(Versions.v2024_11_01_preview) +model BatchOptions { + @doc("Translation text within an image option") + translateTextWithinImage?: boolean = false; +} + +@doc("Definition for the input batch translation request") +model BatchRequest { + @doc("Source of the input documents") + source: SourceInput; + + @doc("Location of the destination for the output") + targets: TargetInput[]; + + @doc("Storage type of the input documents source string") + storageType?: StorageInputType; +} + +@doc("Source of the input documents") +model SourceInput { + @doc("Location of the folder / container or single file with your documents") + sourceUrl: string; + + @doc("Document filter") + filter?: DocumentFilter; + + @doc(""" + Language code + If none is specified, we will perform auto detect on the document + """) + language?: string; + + @doc("Storage Source") + storageSource?: StorageSource; +} + +@doc("Document filter") +model DocumentFilter { + @doc(""" + A case-sensitive prefix string to filter documents in the source path for + translation. + For example, when using a Azure storage blob Uri, use the prefix + to restrict sub folders for translation. + """) + prefix?: string; + + @doc(""" + A case-sensitive suffix string to filter documents in the source path for + translation. + This is most often use for file extensions + """) + suffix?: string; +} + +@doc("Destination for the finished translated documents") +model TargetInput { + @doc("Location of the folder / container with your documents") + targetUrl: string; + + @doc("Category / custom system for translation request") + category?: string; + + @doc("Target Language") + language: string; + + @doc("List of Glossary") + glossaries?: Glossary[]; + + @doc("Storage Source") + storageSource?: StorageSource; +} + +@doc("Glossary / translation memory for the request") +model Glossary { + @doc(""" + Location of the glossary. + We will use the file extension to extract the + formatting if the format parameter is not supplied. + + If the translation + language pair is not present in the glossary, it will not be applied + """) + glossaryUrl: string; + + @doc("Format") + format: string; + + @doc("Optional Version. If not specified, default is used.") + version?: string; + + @doc("Storage Source") + storageSource?: StorageSource; +} + +@doc(""" + Contains unified error information used for HTTP responses across any Cognitive + Service. Instances + can be created either through + Microsoft.CloudAI.Containers.HttpStatusExceptionV2 or by returning it directly + from + a controller. + """) +@error +model TranslationErrorResponse { + @doc(""" + This contains an outer error with error code, message, details, target and an + inner error with more descriptive details. + """) + error?: TranslationError; +} + +@doc(""" + This contains an outer error with error code, message, details, target and an + inner error with more descriptive details. + """) +model TranslationError { + @doc("Enums containing high level error codes.") + code: TranslationErrorCode; + + @doc("Gets high level error message.") + message: string; + + @doc(""" + Gets the source of the error. + For example it would be \"documents\" or + \"document id\" in case of invalid document. + """) + @visibility(Lifecycle.Read) + target?: string; + + @doc(""" + New Inner Error format which conforms to Cognitive Services API Guidelines + which is available at + https://microsoft.sharepoint.com/%3Aw%3A/t/CognitiveServicesPMO/EUoytcrjuJdKpeOKIK_QRC8BPtUYQpKBi8JsWyeDMRsWlQ?e=CPq8ow. + This + contains required properties ErrorCode, message and optional properties target, + details(key value pair), inner error(this can be nested). + """) + innerError?: InnerTranslationError; +} + +@doc(""" + New Inner Error format which conforms to Cognitive Services API Guidelines + which is available at + https://microsoft.sharepoint.com/%3Aw%3A/t/CognitiveServicesPMO/EUoytcrjuJdKpeOKIK_QRC8BPtUYQpKBi8JsWyeDMRsWlQ?e=CPq8ow. + This + contains required properties ErrorCode, message and optional properties target, + details(key value pair), inner error(this can be nested). + """) +model InnerTranslationError { + @doc("Gets code error string.") + code: string; + + @doc("Gets high level error message.") + message: string; + + @doc(""" + Gets the source of the error. + For example it would be \"documents\" or + \"document id\" in case of invalid document. + """) + @visibility(Lifecycle.Read) + target?: string; + + @doc(""" + New Inner Error format which conforms to Cognitive Services API Guidelines + which is available at + https://microsoft.sharepoint.com/%3Aw%3A/t/CognitiveServicesPMO/EUoytcrjuJdKpeOKIK_QRC8BPtUYQpKBi8JsWyeDMRsWlQ?e=CPq8ow. + This + contains required properties ErrorCode, message and optional properties target, + details(key value pair), inner error(this can be nested). + """) + innerError?: InnerTranslationError; +} + +@doc("Translation job Status Response") +model TranslationsStatus { + @doc("The summary status of individual operation") + @pageItems + value: TranslationStatus[]; + + @doc("Url for the next page. Null if no more pages available") + @nextLink + nextLink?: string; +} + +@doc("Translation job status response") +model TranslationStatus { + @doc("Id of the translation operation.") + id: string; + + @doc("Operation created date time") + // FIXME: (utcDateTime) Please double check that this is the correct type for your scenario. + createdDateTimeUtc: utcDateTime; + + @doc("Date time in which the operation's status has been updated") + // FIXME: (utcDateTime) Please double check that this is the correct type for your scenario. + lastActionDateTimeUtc: utcDateTime; + + @doc("List of possible statuses for job or document") + status: Status; + + @doc(""" + This contains an outer error with error code, message, details, target and an + inner error with more descriptive details. + """) + error?: TranslationError; + + @doc("Status Summary") + summary: StatusSummary; +} + +@doc("Status Summary") +model StatusSummary { + @doc("Total count") + total: int32; + + @doc("Failed count") + failed: int32; + + @doc("Number of Success") + success: int32; + + @doc("Number of in progress") + inProgress: int32; + + @doc("Count of not yet started") + notYetStarted: int32; + + @doc("Number of cancelled") + cancelled: int32; + + @doc("Total characters charged by the API") + totalCharacterCharged: int64; + + @doc("Total image scans charged by the API") + @added(Versions.v2024_11_01_preview) + totalImageScansSucceeded?: int32; + + @doc("Total image scans failed") + @added(Versions.v2024_11_01_preview) + totalImageScansFailed?: int32; +} + +@doc("Document Status Response") +model DocumentStatus { + @doc("Location of the document or folder") + path?: string; + + @doc("Location of the source document") + sourcePath: string; + + @doc("Operation created date time") + // FIXME: (utcDateTime) Please double check that this is the correct type for your scenario. + createdDateTimeUtc: utcDateTime; + + @doc("Date time in which the operation's status has been updated") + // FIXME: (utcDateTime) Please double check that this is the correct type for your scenario. + lastActionDateTimeUtc: utcDateTime; + + @doc("List of possible statuses for job or document") + status: Status; + + @doc("To language") + to: string; + + @doc(""" + This contains an outer error with error code, message, details, target and an + inner error with more descriptive details. + """) + error?: TranslationError; + + @doc("Progress of the translation if available") + @maxValue(1) + progress: float32; + + @doc("Document Id") + id: uuid; + + @doc("Character charged by the API") + characterCharged?: int32; + + @doc("Total image scans charged by the API") + @added(Versions.v2024_11_01_preview) + totalImageScansSucceeded?: int32; + + @doc("Total image scans failed") + @added(Versions.v2024_11_01_preview) + totalImageScansFailed?: int32; +} + +@doc("Documents Status Response") +model DocumentsStatus { + @doc("The detail status of individual documents") + @pageItems + value: DocumentStatus[]; + + @doc("Url for the next page. Null if no more pages available") + @nextLink + nextLink?: string; +} + +@doc("List of supported file formats") +model SupportedFileFormats { + @doc("list of objects") + value: FileFormat[]; +} + +@doc("File Format") +model FileFormat { + @doc("Name of the format") + format: string; + + @doc("Supported file extension for this format") + fileExtensions: string[]; + + @doc("Supported Content-Types for this format") + contentTypes: string[]; + + @doc("Default version if none is specified") + defaultVersion?: string; + + @doc("Supported Version") + versions?: string[]; + + @doc("Supported Type for this format") + type?: FileFormatType; +} + +@doc("Document Translate Request Body") +model DocumentTranslateBody { + @doc(""" + Specifies source language of the input document. + If this parameter isn't specified, automatic language detection is applied to determine the source language. + For example if the source document is written in English, then use sourceLanguage=en + """) + @query("sourceLanguage") + sourceLanguage?: string; + + @doc(""" + Specifies the language of the output document. + The target language must be one of the supported languages included in the translation scope. + For example if you want to translate the document in German language, then use targetLanguage=de + """) + @query("targetLanguage") + targetLanguage: string; + + @doc(""" + A string specifying the category (domain) of the translation. This parameter is used to get translations + from a customized system built with Custom Translator. Add the Category ID from your Custom Translator + project details to this parameter to use your deployed customized system. Default value is: general. + """) + @query("category") + category?: string = "general"; + + @doc(""" + Specifies that the service is allowed to fall back to a general system when a custom system doesn't exist. + Possible values are: true (default) or false. + """) + @query("allowFallback") + allowFallback?: boolean; + + @doc("Content Type as multipart/form-data") + @header + contentType: "multipart/form-data"; + + @doc("Document Translate Request Content") + @multipartBody + body: DocumentTranslateContent; + + @doc("Optional boolean parameter to translate text within an image in the document") + @query("translateTextWithinImage") + @added(Versions.v2024_11_01_preview) + translateTextWithinImage?: boolean; +} + +@doc("Start Translation Request Body") +model StartTranslationBody { + @doc("Translation job submission batch request") + @body + body: StartTranslationDetails; +} + +@doc("Get Translations Status options") +model GetTranslationsStatusOptions { + @doc(""" + top indicates the total number of records the user wants to be returned across + all pages. + + Clients MAY use top and skip query parameters to + specify a number of results to return and an offset into the collection. + When + both top and skip are given by a client, the server SHOULD first apply skip + and then top on the collection. + + Note: If the server can't honor + top and/or skip, the server MUST return an error to the client informing + about it instead of just ignoring the query options. + """) + @maxValue(2147483647) + @query("top") + top?: int32; + + @doc(""" + skip indicates the number of records to skip from the list of records held by + the server based on the sorting method specified. By default, we sort by + descending start time. + + Clients MAY use top and skip query + parameters to specify a number of results to return and an offset into the + collection. + When both top and skip are given by a client, the server SHOULD + first apply skip and then top on the collection. + + Note: If the + server can't honor top and/or skip, the server MUST return an error to the + client informing about it instead of just ignoring the query options. + """) + @maxValue(2147483647) + @query("skip") + skip?: int32; + + @doc(""" + maxpagesize is the maximum items returned in a page. If more items are + requested via top (or top is not specified and there are more items to be + returned), @nextLink will contain the link to the next page. + + + Clients MAY request server-driven paging with a specific page size by + specifying a maxpagesize preference. The server SHOULD honor this preference + if the specified page size is smaller than the server's default page size. + """) + @maxValue(100) + @minValue(1) + @query("maxpagesize") + maxpagesize?: int32 = 50; + + @doc("Ids to use in filtering") + @query("ids") + ids?: uuid[]; + + @doc("Statuses to use in filtering") + @query("statuses") + statuses?: string[]; + + @doc("the start datetime to get items after") + @query("createdDateTimeUtcStart") + createdDateTimeUtcStart?: utcDateTime; + + @doc("the end datetime to get items before") + @query("createdDateTimeUtcEnd") + createdDateTimeUtcEnd?: utcDateTime; + + @doc("the sorting query for the collection (ex: 'CreatedDateTimeUtc asc','CreatedDateTimeUtc desc')") + @query("orderby") + orderby?: string[]; +} + +@doc("Get Documents Status options") +model GetDocumentsStatusOptions { + @doc("Format - uuid. The operation id") + @path + id: uuid; + + @doc(""" + top indicates the total number of records the user wants to be returned across + all pages. + + Clients MAY use top and skip query parameters to + specify a number of results to return and an offset into the collection. + When + both top and skip are given by a client, the server SHOULD first apply skip + and then top on the collection. + + Note: If the server can't honor + top and/or skip, the server MUST return an error to the client informing + about it instead of just ignoring the query options. + """) + @maxValue(2147483647) + @query("top") + top?: int32; + + @doc(""" + skip indicates the number of records to skip from the list of records held by + the server based on the sorting method specified. By default, we sort by + descending start time. + + Clients MAY use top and skip query + parameters to specify a number of results to return and an offset into the + collection. + When both top and skip are given by a client, the server SHOULD + first apply skip and then top on the collection. + + Note: If the + server can't honor top and/or skip, the server MUST return an error to the + client informing about it instead of just ignoring the query options. + """) + @maxValue(2147483647) + @query("skip") + skip?: int32; + + @doc(""" + maxpagesize is the maximum items returned in a page. If more items are + requested via top (or top is not specified and there are more items to be + returned), @nextLink will contain the link to the next page. + + + Clients MAY request server-driven paging with a specific page size by + specifying a maxpagesize preference. The server SHOULD honor this preference + if the specified page size is smaller than the server's default page size. + """) + @maxValue(100) + @minValue(1) + @query("maxpagesize") + maxpagesize?: int32 = 50; + + @doc("Ids to use in filtering") + @query("ids") + ids?: uuid[]; + + @doc("Statuses to use in filtering") + @query("statuses") + statuses?: string[]; + + @doc("the start datetime to get items after") + @query("createdDateTimeUtcStart") + createdDateTimeUtcStart?: utcDateTime; + + @doc("the end datetime to get items before") + @query("createdDateTimeUtcEnd") + createdDateTimeUtcEnd?: utcDateTime; + + @doc("the sorting query for the collection (ex: 'CreatedDateTimeUtc asc','CreatedDateTimeUtc desc')") + @query("orderby") + orderby?: string[]; +} diff --git a/packages/typespec-test/test/DocumentTranslation/spec/routes.tsp b/packages/typespec-test/test/DocumentTranslation/spec/routes.tsp new file mode 100644 index 0000000000..b2e766e64f --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/spec/routes.tsp @@ -0,0 +1,282 @@ +import "@azure-tools/typespec-azure-core"; +import "@typespec/rest"; +import "./models.tsp"; +import "@azure-tools/typespec-client-generator-core"; + +using TypeSpec.Versioning; +using TypeSpec.Rest; +using TypeSpec.Http; +using Azure.Core; +using Azure.Core.Traits; +using Azure.ClientGenerator.Core; + +namespace DocumentTranslation; + +alias ServiceTraits = SupportsRepeatableRequests & + SupportsConditionalRequests & + SupportsClientRequestId; + +interface DocumentTranslationOperations { + #suppress "@azure-tools/typespec-azure-core/byos" + @summary("Submit a single document translation request to the Document Translation service") + @doc("Use this API to submit a single translation request to the Document Translation Service.") + @route("document:translate") + @post + documentTranslate is RpcOperation< + DocumentTranslateBody, + DocumentTranslateResult, + ServiceTraits + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/long-running-polling-operation-required" + @summary("Submit a document translation request to the Document Translation service") + @doc(""" + Use this API to submit a bulk (batch) translation request to the Document + Translation service. + Each request can contain multiple documents and must + contain a source and destination container for each document. + + The + prefix and suffix filter (if supplied) are used to filter folders. The prefix + is applied to the subpath after the container name. + + Glossaries / + Translation memory can be included in the request and are applied by the + service when the document is translated. + + If the glossary is + invalid or unreachable during translation, an error is indicated in the + document status. + If a file with the same name already exists at the + destination, it will be overwritten. The targetUrl for each target language + must be unique. + """) + @finalOperation(DocumentTranslationOperations.getTranslationStatus) + @pollingOperation(DocumentTranslationOperations.getTranslationStatus) + @route("/document/batches") + @post + startTranslation is Azure.Core.Foundations.Operation< + StartTranslationBody, + { + @doc("Request has been accepted successfully.") + @statusCode + code: 202; + + @pollingLocation + @doc("Link to the translation operation status") + @header("Operation-Location") + operationLocation: string; + } + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/use-standard-names" "Existing name" + @summary("Returns a list of batch requests submitted and the status for each request") + @doc(""" + Returns a list of batch requests submitted and the status for each + request. + This list only contains batch requests submitted by the user (based on + the resource). + + If the number of requests exceeds our paging limit, + server-side paging is used. Paginated responses indicate a partial result and + include a continuation token in the response. + The absence of a continuation + token means that no additional pages are available. + + top, skip + and maxpagesize query parameters can be used to specify a number of results to + return and an offset for the collection. + + top indicates the total + number of records the user wants to be returned across all pages. + skip + indicates the number of records to skip from the list of batches based on the + sorting method specified. By default, we sort by descending start + time. + maxpagesize is the maximum items returned in a page. If more items are + requested via top (or top is not specified and there are more items to be + returned), @nextLink will contain the link to the next page. + + + orderby query parameter can be used to sort the returned list (ex + \"orderby=createdDateTimeUtc asc\" or \"orderby=createdDateTimeUtc + desc\"). + The default sorting is descending by createdDateTimeUtc. + Some query + parameters can be used to filter the returned list (ex: + \"status=Succeeded,Cancelled\") will only return succeeded and cancelled + operations. + createdDateTimeUtcStart and createdDateTimeUtcEnd can be used + combined or separately to specify a range of datetime to filter the returned + list by. + The supported filtering query parameters are (status, ids, + createdDateTimeUtcStart, createdDateTimeUtcEnd). + + The server honors + the values specified by the client. However, clients must be prepared to handle + responses that contain a different page size or contain a continuation token. + + + When both top and skip are included, the server should first apply + skip and then top on the collection. + Note: If the server can't honor top + and/or skip, the server must return an error to the client informing about it + instead of just ignoring the query options. + This reduces the risk of the client + making assumptions about the data returned. + """) + @route("/document/batches") + @get + @list + getTranslationsStatus is Azure.Core.Foundations.Operation< + GetTranslationsStatusOptions, + TranslationsStatus + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + @summary("Returns the status for a specific document") + @doc(""" + Returns the translation status for a specific document based on the request Id + and document Id. + """) + @route("/document/batches/{id}/documents/{documentId}") + @get + getDocumentStatus is Azure.Core.Foundations.Operation< + { + @doc("Format - uuid. The batch id") + @clientName("translationId") + @path + id: uuid; + + @doc("Format - uuid. The document id") + @path + documentId: uuid; + }, + DocumentStatus + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + @summary("Returns the status for a document translation request") + @doc(""" + Returns the status for a document translation request. + The status includes the + overall request status, as well as the status for documents that are being + translated as part of that request. + """) + @route("/document/batches/{id}") + @get + getTranslationStatus is Azure.Core.Foundations.Operation< + { + @doc("Format - uuid. The operation id") + @clientName("translationId") + @path + id: uuid; + }, + TranslationStatus + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/use-standard-names" "Existing name" + @summary("Cancel a currently processing or queued translation") + @doc(""" + Cancel a currently processing or queued translation. + A translation will not be + cancelled if it is already completed or failed or cancelling. A bad request + will be returned. + All documents that have completed translation will not be + cancelled and will be charged. + All pending documents will be cancelled if + possible. + """) + @route("/document/batches/{id}") + @delete + cancelTranslation is Azure.Core.Foundations.Operation< + { + @doc("Format - uuid. The operation-id") + @clientName("translationId") + @path + id: uuid; + }, + TranslationStatus + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + #suppress "@azure-tools/typespec-azure-core/use-standard-names" "Existing name" + @summary("Returns the status for all documents in a batch document translation request") + @doc(""" + Returns the status for all documents in a batch document translation request. + + + If the number of documents in the response exceeds our paging limit, + server-side paging is used. + Paginated responses indicate a partial result and + include a continuation token in the response. The absence of a continuation + token means that no additional pages are available. + + top, skip + and maxpagesize query parameters can be used to specify a number of results to + return and an offset for the collection. + + top indicates the total + number of records the user wants to be returned across all pages. + skip + indicates the number of records to skip from the list of document status held + by the server based on the sorting method specified. By default, we sort by + descending start time. + maxpagesize is the maximum items returned in a page. + If more items are requested via top (or top is not specified and there are + more items to be returned), @nextLink will contain the link to the next page. + + + orderby query parameter can be used to sort the returned list (ex + \"orderby=createdDateTimeUtc asc\" or \"orderby=createdDateTimeUtc + desc\"). + The default sorting is descending by createdDateTimeUtc. + Some query + parameters can be used to filter the returned list (ex: + \"status=Succeeded,Cancelled\") will only return succeeded and cancelled + documents. + createdDateTimeUtcStart and createdDateTimeUtcEnd can be used + combined or separately to specify a range of datetime to filter the returned + list by. + The supported filtering query parameters are (status, ids, + createdDateTimeUtcStart, createdDateTimeUtcEnd). + + When both top + and skip are included, the server should first apply skip and then top on + the collection. + Note: If the server can't honor top and/or skip, the server + must return an error to the client informing about it instead of just ignoring + the query options. + This reduces the risk of the client making assumptions about + the data returned. + """) + @route("/document/batches/{id}/documents") + @get + @list + getDocumentsStatus is Azure.Core.Foundations.Operation< + GetDocumentsStatusOptions, + DocumentsStatus + >; + + #suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Doesn't fit standard ops" + @summary("Returns a list of supported document formats") + @doc(""" + The list of supported formats supported by the Document Translation + service. + The list includes the common file extension, as well as the + content-type if using the upload API. + """) + @route("/document/formats") + @get + getSupportedFormats is Azure.Core.Foundations.Operation< + { + @doc("the type of format like document or glossary ") + @query("type") + type?: FileFormatType; + }, + SupportedFileFormats + >; +} diff --git a/packages/typespec-test/test/DocumentTranslation/tspconfig.yaml b/packages/typespec-test/test/DocumentTranslation/tspconfig.yaml new file mode 100644 index 0000000000..1f17ee61a8 --- /dev/null +++ b/packages/typespec-test/test/DocumentTranslation/tspconfig.yaml @@ -0,0 +1,10 @@ +emit: + - "@azure-tools/typespec-ts" +options: + "@azure-tools/typespec-ts": + azure-sdk-for-js: false + generate-sample: true + emitter-output-dir: "{project-root}/generated/typespec-ts" + package-details: + name: "@azure/load-testing" + description: "Azure Load Testing" diff --git a/packages/typespec-ts/test/modularUnit/scenarios/samples/parameters/multipartBodyRequired.md b/packages/typespec-ts/test/modularUnit/scenarios/samples/parameters/multipartBodyRequired.md new file mode 100644 index 0000000000..120bff190c --- /dev/null +++ b/packages/typespec-ts/test/modularUnit/scenarios/samples/parameters/multipartBodyRequired.md @@ -0,0 +1,93 @@ +# Should generate correct sample code for required multipart body with required field + +## TypeSpec + +```tsp +import "@typespec/http"; +import "@typespec/rest"; +import "@azure-tools/typespec-client-generator-core"; + +using TypeSpec.Http; +using TypeSpec.Rest; + +@service(#{ + title: "Translation Service", +}) +namespace TranslationService; + +model DocumentTranslateContent { + document: HttpPart; + glossary?: HttpPart; +} + +model DocumentTranslateBody { + @query("sourceLanguage") + sourceLanguage?: string; + + @query("targetLanguage") + targetLanguage: string; + + @doc("Content Type as multipart/form-data") + @header + contentType: "multipart/form-data"; + + @multipartBody + body: DocumentTranslateContent; +} + +@route("/translate") +interface TranslationOperations { + @post + translate(...DocumentTranslateBody): { + @statusCode statusCode: 200; + @body result: bytes; + }; +} +``` + +## Examples + +```json +{ + "title": "Translate a document", + "operationId": "TranslationOperations_Translate", + "parameters": { + "sourceLanguage": "en", + "targetLanguage": "es", + "body": { + "document": "TXkgdHJhbnNsYXRlZCBkb2N1bWVudA==" + } + }, + "responses": { + "200": { + "body": "VHJhZHVjaWRvIGRvY3VtZW50bw==" + } + } +} +``` + +## Provide generated samples + +```ts samples +/** This file path is /samples-dev/translateSample.ts */ +import { TranslationServiceClient } from "@azure/internal-test"; + +/** + * This sample demonstrates how to execute translate + * + * @summary execute translate + * x-ms-original-file: 2021-10-01-preview/json.json + */ +async function translateADocument(): Promise { + const endpoint = process.env.TRANSLATION_SERVICE_ENDPOINT || ""; + const client = new TranslationServiceClient(endpoint); + const result = await client.translate("es", {}, { sourceLanguage: "en" }); + console.log(result); +} + +async function main(): Promise { + await translateADocument(); +} + +main().catch(console.error); +```