diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/.eslintrc.json b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/.eslintrc.json new file mode 100644 index 0000000000..619797ac39 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/.eslintrc.json @@ -0,0 +1,11 @@ +{ + "plugins": ["@azure/azure-sdk"], + "extends": ["plugin:@azure/azure-sdk/azure-sdk-base"], + "rules": { + "@azure/azure-sdk/ts-modules-only-named": "warn", + "@azure/azure-sdk/ts-apiextractor-json-types": "warn", + "@azure/azure-sdk/ts-package-json-types": "warn", + "@azure/azure-sdk/ts-package-json-engine-is-present": "warn", + "tsdoc/syntax": "warn" + } +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/README.md b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/README.md new file mode 100644 index 0000000000..5bdfac0c5b --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/README.md @@ -0,0 +1,57 @@ +# Azure ContentSafety REST client library for JavaScript + +Analyze harmful content + +**Please rely heavily on our [REST client docs](https://github.com/Azure/azure-sdk-for-js/blob/main/documentation/rest-clients.md) to use this library** + +Key links: + +- [Package (NPM)](https://www.npmjs.com/package/@azure-rest/ai-content-safety) +- [API reference documentation](https://docs.microsoft.com/javascript/api/@azure-rest/ai-content-safety) + +## Getting started + +### Currently supported environments + +- LTS versions of Node.js + +### Prerequisites + +- You must have an [Azure subscription](https://azure.microsoft.com/free/) to use this package. + +### Install the `@azure-rest/ai-content-safety` package + +Install the Azure ContentSafety REST client REST client library for JavaScript with `npm`: + +```bash +npm install @azure-rest/ai-content-safety +``` + +### Create and authenticate a `ContentSafetyClient` + +To use an [Azure Active Directory (AAD) token credential](https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/identity/identity/samples/AzureIdentityExamples.md#authenticating-with-a-pre-fetched-access-token), +provide an instance of the desired credential type obtained from the +[@azure/identity](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#credentials) library. + +To authenticate with AAD, you must first `npm` install [`@azure/identity`](https://www.npmjs.com/package/@azure/identity) + +After setup, you can choose which type of [credential](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#credentials) from `@azure/identity` to use. +As an example, [DefaultAzureCredential](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/identity/identity#defaultazurecredential) +can be used to authenticate the client. + +Set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables: +AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_CLIENT_SECRET + +## 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`: + +```javascript +const { setLogLevel } = require("@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). diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/api-extractor.json b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/api-extractor.json new file mode 100644 index 0000000000..c3efb349c4 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/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": "./types/src/index.d.ts", + "docModel": { "enabled": true }, + "apiReport": { "enabled": true, "reportFolder": "./review" }, + "dtsRollup": { + "enabled": true, + "untrimmedFilePath": "", + "publicTrimmedFilePath": "./types/ai-content-safety.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/contentsafety_modular/generated/typespec-ts/package.json b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/package.json new file mode 100644 index 0000000000..62f212d2f1 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/package.json @@ -0,0 +1,97 @@ +{ + "name": "@azure-rest/ai-content-safety", + "sdk-type": "client", + "author": "Microsoft Corporation", + "version": "1.0.0", + "description": "ContentSafety Service", + "keywords": ["node", "azure", "cloud", "typescript", "browser", "isomorphic"], + "license": "MIT", + "type": "module", + "main": "dist/index.js", + "module": "./dist-esm/src/index.js", + "types": "./types/ai-content-safety.d.ts", + "exports": { + ".": { + "types": "./types/src/index.d.ts", + "require": "./dist/index.cjs", + "import": "./dist-esm/src/index.js" + }, + "./api": { + "types": "./types/src/api/index.d.ts", + "import": "./dist-esm/src/api/index.js" + }, + "./models": { + "types": "./types/src/models/index.d.ts", + "import": "./dist-esm/src/models/index.js" + } + }, + "repository": "github:Azure/azure-sdk-for-js", + "bugs": { "url": "https://github.com/Azure/azure-sdk-for-js/issues" }, + "files": [ + "dist/", + "dist-esm/", + "types/ai-content-safety.d.ts", + "README.md", + "LICENSE", + "review/*" + ], + "engines": { "node": ">=14.0.0" }, + "scripts": { + "audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit", + "build:browser": "echo skipped.", + "build:node": "echo skipped.", + "build:samples": "echo skipped.", + "build:test": "echo skipped.", + "build:debug": "echo skipped.", + "check-format": "prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"*.{js,json}\" ", + "clean": "rimraf dist dist-browser dist-esm test-dist temp types *.tgz *.log", + "execute:samples": "echo skipped", + "extract-api": "rimraf review && mkdirp ./review && api-extractor run --local", + "format": "prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"*.{js,json}\" ", + "generate:client": "echo skipped", + "integration-test:browser": "echo skipped", + "integration-test:node": "echo skipped", + "integration-test": "echo skipped", + "lint:fix": "eslint package.json api-extractor.json src --ext .ts --fix --fix-type [problem,suggestion]", + "lint": "eslint package.json api-extractor.json src --ext .ts", + "pack": "npm pack 2>&1", + "test:browser": "echo skipped", + "test:node": "echo skipped", + "test": "echo \"Error: no test specified\" && exit 1", + "unit-test": "echo skipped", + "unit-test:node": "echo skipped", + "unit-test:browser": "echo skipped", + "build": "npm run clean && tsc && rollup -c 2>&1 && npm run minify && mkdirp ./review && npm run extract-api", + "minify": "uglifyjs -c -m --comments --source-map \"content='./dist/index.js.map'\" -o ./dist/index.min.js ./dist/index.js" + }, + "sideEffects": false, + "autoPublish": false, + "dependencies": { + "@azure/core-auth": "^1.3.0", + "@azure-rest/core-client": "^1.1.4", + "@azure/core-rest-pipeline": "^1.12.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0", + "@azure/core-paging": "^1.5.0", + "@azure/core-util": "^1.4.0" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.31.1", + "autorest": "latest", + "@types/node": "^14.0.0", + "dotenv": "^16.0.0", + "eslint": "^8.0.0", + "mkdirp": "^2.1.2", + "prettier": "^2.5.1", + "rimraf": "^3.0.0", + "source-map-support": "^0.5.9", + "typescript": "~5.0.0", + "@rollup/plugin-commonjs": "^24.0.0", + "@rollup/plugin-json": "^6.0.0", + "@rollup/plugin-multi-entry": "^6.0.0", + "@rollup/plugin-node-resolve": "^13.1.3", + "rollup": "^2.66.1", + "rollup-plugin-sourcemaps": "^0.6.3", + "uglify-js": "^3.4.9" + } +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/review/ai-content-safety.api.md b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/review/ai-content-safety.api.md new file mode 100644 index 0000000000..3949faeb43 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/review/ai-content-safety.api.md @@ -0,0 +1,169 @@ +## API Report File for "@azure-rest/ai-content-safety" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { ClientOptions } from '@azure-rest/core-client'; +import { KeyCredential } from '@azure/core-auth'; +import { OperationOptions } from '@azure-rest/core-client'; +import { TokenCredential } from '@azure/core-auth'; + +// @public (undocumented) +export interface AddOrUpdateBlockItemsRequestOptions extends OperationOptions { +} + +// @public +export interface AddOrUpdateBlockItemsResult { + value?: TextBlockItem[]; +} + +// @public +export type AnalyzeImageOutputType = string; + +// @public (undocumented) +export interface AnalyzeImageRequestOptions extends OperationOptions { + categories?: ImageCategory[]; + outputType?: AnalyzeImageOutputType; +} + +// @public +export interface AnalyzeImageResult { + analyzeResults: ImageAnalyzeSeverityResult[]; +} + +// @public +export type AnalyzeTextOutputType = string; + +// @public (undocumented) +export interface AnalyzeTextRequestOptions extends OperationOptions { + blocklistNames?: string[]; + breakByBlocklists?: boolean; + categories?: TextCategory[]; + outputType?: AnalyzeTextOutputType; +} + +// @public +export interface AnalyzeTextResult { + analyzeResults: TextAnalyzeSeverityResult[]; + blocklistsMatchResults?: TextBlocklistMatchResult[]; +} + +// @public (undocumented) +export class ContentSafetyClient { + constructor(endpoint: string, credential: KeyCredential | TokenCredential, options?: ContentSafetyClientOptions); + addOrUpdateBlockItems(blockItems: TextBlockItemInfo[], blocklistName: string, options?: AddOrUpdateBlockItemsRequestOptions): Promise; + analyzeImage(image: ImageData_2, options?: AnalyzeImageRequestOptions): Promise; + analyzeText(text: string, options?: AnalyzeTextRequestOptions): Promise; + createOrUpdateTextBlocklist(blocklistName: string, options?: CreateOrUpdateTextBlocklistOptions): Promise; + deleteTextBlocklist(blocklistName: string, options?: DeleteTextBlocklistOptions): Promise; + getTextBlocklist(blocklistName: string, options?: GetTextBlocklistOptions): Promise; + getTextBlocklistItem(blocklistName: string, blockItemId: string, options?: GetTextBlocklistItemOptions): Promise; + listTextBlocklistItems(blocklistName: string, options?: ListTextBlocklistItemsOptions): Promise; + listTextBlocklists(options?: ListTextBlocklistsOptions): Promise; + removeBlockItems(blockItemIds: string[], blocklistName: string, options?: RemoveBlockItemsRequestOptions): Promise; +} + +// @public (undocumented) +export interface ContentSafetyClientOptions extends ClientOptions { +} + +// @public (undocumented) +export interface CreateOrUpdateTextBlocklistOptions extends OperationOptions { + contentType?: string; + description?: string; +} + +// @public (undocumented) +export interface DeleteTextBlocklistOptions extends OperationOptions { +} + +// @public (undocumented) +export interface GetTextBlocklistItemOptions extends OperationOptions { +} + +// @public (undocumented) +export interface GetTextBlocklistOptions extends OperationOptions { +} + +// @public +export interface ImageAnalyzeSeverityResult { + category: ImageCategory; + severity?: number; +} + +// @public +export type ImageCategory = string; + +// @public +interface ImageData_2 { + blobUrl?: string; + content?: Uint8Array; +} +export { ImageData_2 as ImageData } + +// @public (undocumented) +export interface ListTextBlocklistItemsOptions extends OperationOptions { + maxpagesize?: number; + skip?: number; + top?: number; +} + +// @public (undocumented) +export interface ListTextBlocklistsOptions extends OperationOptions { +} + +// @public +export interface PagedTextBlockItem { + nextLink?: string; + value: TextBlockItem[]; +} + +// @public +export interface PagedTextBlocklist { + nextLink?: string; + value: TextBlocklist[]; +} + +// @public (undocumented) +export interface RemoveBlockItemsRequestOptions extends OperationOptions { +} + +// @public +export interface TextAnalyzeSeverityResult { + category: TextCategory; + severity?: number; +} + +// @public +export interface TextBlockItem { + readonly blockItemId: string; + description?: string; + text: string; +} + +// @public +export interface TextBlockItemInfo { + description?: string; + text: string; +} + +// @public +export interface TextBlocklist { + readonly blocklistName: string; + description?: string; +} + +// @public +export interface TextBlocklistMatchResult { + blockItemId: string; + blockItemText: string; + blocklistName: string; +} + +// @public +export type TextCategory = string; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/rollup.config.js b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/rollup.config.js new file mode 100644 index 0000000000..61251d7a8d --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/rollup.config.js @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import nodeResolve from "@rollup/plugin-node-resolve"; +import cjs from "@rollup/plugin-commonjs"; +import sourcemaps from "rollup-plugin-sourcemaps"; +import multiEntry from "@rollup/plugin-multi-entry"; +import json from "@rollup/plugin-json"; + +import nodeBuiltins from "builtin-modules"; + +// #region Warning Handler + +/** + * A function that can determine whether a rollup warning should be ignored. If + * the function returns `true`, then the warning will not be displayed. + */ + +function ignoreNiseSinonEval(warning) { + return ( + warning.code === "EVAL" && + warning.id && + (warning.id.includes("node_modules/nise") || + warning.id.includes("node_modules/sinon")) === true + ); +} + +function ignoreChaiCircularDependency(warning) { + return ( + warning.code === "CIRCULAR_DEPENDENCY" && + warning.importer && + warning.importer.includes("node_modules/chai") === true + ); +} + +const warningInhibitors = [ignoreChaiCircularDependency, ignoreNiseSinonEval]; + +/** + * Construct a warning handler for the shared rollup configuration + * that ignores certain warnings that are not relevant to testing. + */ +function makeOnWarnForTesting() { + return (warning, warn) => { + // If every inhibitor returns false (i.e. no inhibitors), then show the warning + if (warningInhibitors.every((inhib) => !inhib(warning))) { + warn(warning); + } + }; +} + +// #endregion + +function makeBrowserTestConfig() { + const config = { + input: { + include: ["dist-esm/test/**/*.spec.js"], + exclude: ["dist-esm/test/**/node/**"], + }, + output: { + file: `dist-test/index.browser.js`, + format: "umd", + sourcemap: true, + }, + preserveSymlinks: false, + plugins: [ + multiEntry({ exports: false }), + nodeResolve({ + mainFields: ["module", "browser"], + }), + cjs(), + json(), + sourcemaps(), + //viz({ filename: "dist-test/browser-stats.html", sourcemap: true }) + ], + onwarn: makeOnWarnForTesting(), + // Disable tree-shaking of test code. In rollup-plugin-node-resolve@5.0.0, + // rollup started respecting the "sideEffects" field in package.json. Since + // our package.json sets "sideEffects=false", this also applies to test + // code, which causes all tests to be removed by tree-shaking. + treeshake: false, + }; + + return config; +} + +const defaultConfigurationOptions = { + disableBrowserBundle: false, +}; + +export function makeConfig(pkg, options) { + options = { + ...defaultConfigurationOptions, + ...(options || {}), + }; + + const baseConfig = { + // Use the package's module field if it has one + input: pkg["module"] || "dist-esm/src/index.js", + external: [ + ...nodeBuiltins, + ...Object.keys(pkg.dependencies), + ...Object.keys(pkg.devDependencies), + ], + output: { file: "dist/index.js", format: "cjs", sourcemap: true }, + preserveSymlinks: false, + plugins: [sourcemaps(), nodeResolve()], + }; + + const config = [baseConfig]; + + if (!options.disableBrowserBundle) { + config.push(makeBrowserTestConfig()); + } + + return config; +} + +export default makeConfig(require("./package.json")); diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/ContentSafetyClient.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/ContentSafetyClient.ts new file mode 100644 index 0000000000..87a787dc58 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/ContentSafetyClient.ts @@ -0,0 +1,149 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { TokenCredential, KeyCredential } from "@azure/core-auth"; +import { + AnalyzeTextResult, + ImageData, + AnalyzeImageResult, + TextBlocklist, + TextBlockItemInfo, + AddOrUpdateBlockItemsResult, + TextBlockItem, + PagedTextBlocklist, + PagedTextBlockItem, +} from "./models/models.js"; +import { + AnalyzeTextRequestOptions, + AnalyzeImageRequestOptions, + GetTextBlocklistOptions, + CreateOrUpdateTextBlocklistOptions, + DeleteTextBlocklistOptions, + ListTextBlocklistsOptions, + AddOrUpdateBlockItemsRequestOptions, + RemoveBlockItemsRequestOptions, + GetTextBlocklistItemOptions, + ListTextBlocklistItemsOptions, +} from "./models/options.js"; +import { + createContentSafety, + ContentSafetyClientOptions, + ContentSafetyContext, + analyzeText, + analyzeImage, + getTextBlocklist, + createOrUpdateTextBlocklist, + deleteTextBlocklist, + listTextBlocklists, + addOrUpdateBlockItems, + removeBlockItems, + getTextBlocklistItem, + listTextBlocklistItems, +} from "./api/index.js"; + +export { ContentSafetyClientOptions } from "./api/ContentSafetyContext.js"; + +export class ContentSafetyClient { + private _client: ContentSafetyContext; + + /** Analyze harmful content */ + constructor( + endpoint: string, + credential: KeyCredential | TokenCredential, + options: ContentSafetyClientOptions = {} + ) { + this._client = createContentSafety(endpoint, credential, options); + } + + /** A sync API for harmful content analysis for text. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence. */ + analyzeText( + text: string, + options: AnalyzeTextRequestOptions = { requestOptions: {} } + ): Promise { + return analyzeText(this._client, text, options); + } + + /** A sync API for harmful content analysis for image. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence. */ + analyzeImage( + image: ImageData, + options: AnalyzeImageRequestOptions = { requestOptions: {} } + ): Promise { + return analyzeImage(this._client, image, options); + } + + /** Returns text blocklist details. */ + getTextBlocklist( + blocklistName: string, + options: GetTextBlocklistOptions = { requestOptions: {} } + ): Promise { + return getTextBlocklist(this._client, blocklistName, options); + } + + /** Updates a text blocklist, if blocklistName does not exist, create a new blocklist. */ + createOrUpdateTextBlocklist( + blocklistName: string, + options: CreateOrUpdateTextBlocklistOptions = { requestOptions: {} } + ): Promise { + return createOrUpdateTextBlocklist(this._client, blocklistName, options); + } + + /** Deletes a text blocklist. */ + deleteTextBlocklist( + blocklistName: string, + options: DeleteTextBlocklistOptions = { requestOptions: {} } + ): Promise { + return deleteTextBlocklist(this._client, blocklistName, options); + } + + /** Get all text blocklists details. */ + listTextBlocklists( + options: ListTextBlocklistsOptions = { requestOptions: {} } + ): Promise { + return listTextBlocklists(this._client, options); + } + + /** Add or update blockItems to a text blocklist. You can add or update at most 100 BlockItems in one request. */ + addOrUpdateBlockItems( + blockItems: TextBlockItemInfo[], + blocklistName: string, + options: AddOrUpdateBlockItemsRequestOptions = { requestOptions: {} } + ): Promise { + return addOrUpdateBlockItems( + this._client, + blockItems, + blocklistName, + options + ); + } + + /** Remove blockItems from a text blocklist. You can remove at most 100 BlockItems in one request. */ + removeBlockItems( + blockItemIds: string[], + blocklistName: string, + options: RemoveBlockItemsRequestOptions = { requestOptions: {} } + ): Promise { + return removeBlockItems(this._client, blockItemIds, blocklistName, options); + } + + /** Get blockItem By blockItemId from a text blocklist. */ + getTextBlocklistItem( + blocklistName: string, + blockItemId: string, + options: GetTextBlocklistItemOptions = { requestOptions: {} } + ): Promise { + return getTextBlocklistItem( + this._client, + blocklistName, + blockItemId, + options + ); + } + + /** Get all blockItems in a text blocklist */ + listTextBlocklistItems( + blocklistName: string, + options: ListTextBlocklistItemsOptions = { requestOptions: {} } + ): Promise { + return listTextBlocklistItems(this._client, blocklistName, options); + } +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/ContentSafetyContext.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/ContentSafetyContext.ts new file mode 100644 index 0000000000..a84d67ff40 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/ContentSafetyContext.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { ClientOptions } from "@azure-rest/core-client"; +import { ContentSafetyContext } from "../rest/index.js"; +import { KeyCredential } from "@azure/core-auth"; +import { TokenCredential } from "@azure/core-auth"; +import getClient from "../rest/index.js"; + +export interface ContentSafetyClientOptions extends ClientOptions {} + +export { ContentSafetyContext } from "../rest/index.js"; + +/** Analyze harmful content */ +export function createContentSafety( + endpoint: string, + credential: KeyCredential | TokenCredential, + options: ContentSafetyClientOptions = {} +): ContentSafetyContext { + const baseUrl = endpoint; + const clientContext = getClient(baseUrl, credential, options); + return clientContext; +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/index.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/index.ts new file mode 100644 index 0000000000..d1dcd88891 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/index.ts @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export { + createContentSafety, + ContentSafetyClientOptions, + ContentSafetyContext, +} from "./ContentSafetyContext.js"; +export { + analyzeText, + analyzeImage, + getTextBlocklist, + createOrUpdateTextBlocklist, + deleteTextBlocklist, + listTextBlocklists, + addOrUpdateBlockItems, + removeBlockItems, + getTextBlocklistItem, + listTextBlocklistItems, +} from "./operations.js"; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts new file mode 100644 index 0000000000..a4cd1514cf --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/api/operations.ts @@ -0,0 +1,505 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AnalyzeTextResult, + ImageData, + AnalyzeImageResult, + TextBlocklist, + TextBlockItemInfo, + AddOrUpdateBlockItemsResult, + TextBlockItem, + PagedTextBlocklist, + PagedTextBlockItem, +} from "../models/models.js"; +import { + isUnexpected, + ContentSafetyContext as Client, + AddOrUpdateBlockItems200Response, + AddOrUpdateBlockItemsDefaultResponse, + AnalyzeImage200Response, + AnalyzeImageDefaultResponse, + AnalyzeText200Response, + AnalyzeTextDefaultResponse, + CreateOrUpdateTextBlocklist200Response, + CreateOrUpdateTextBlocklist201Response, + CreateOrUpdateTextBlocklistDefaultResponse, + DeleteTextBlocklist204Response, + DeleteTextBlocklistDefaultResponse, + GetTextBlocklist200Response, + GetTextBlocklistDefaultResponse, + GetTextBlocklistItem200Response, + GetTextBlocklistItemDefaultResponse, + ListTextBlocklistItems200Response, + ListTextBlocklistItemsDefaultResponse, + ListTextBlocklists200Response, + ListTextBlocklistsDefaultResponse, + RemoveBlockItems204Response, + RemoveBlockItemsDefaultResponse, +} from "../rest/index.js"; +import { + StreamableMethod, + operationOptionsToRequestParameters, +} from "@azure-rest/core-client"; +import { uint8ArrayToString } from "@azure/core-util"; +import { + AnalyzeTextRequestOptions, + AnalyzeImageRequestOptions, + GetTextBlocklistOptions, + CreateOrUpdateTextBlocklistOptions, + DeleteTextBlocklistOptions, + ListTextBlocklistsOptions, + AddOrUpdateBlockItemsRequestOptions, + RemoveBlockItemsRequestOptions, + GetTextBlocklistItemOptions, + ListTextBlocklistItemsOptions, +} from "../models/options.js"; + +export function _analyzeTextSend( + context: Client, + text: string, + options: AnalyzeTextRequestOptions = { requestOptions: {} } +): StreamableMethod { + return context + .path("/text:analyze") + .post({ + ...operationOptionsToRequestParameters(options), + body: { + text: text, + categories: options?.categories, + blocklistNames: options?.blocklistNames, + breakByBlocklists: options?.breakByBlocklists, + outputType: options?.outputType, + }, + }); +} + +export async function _analyzeTextDeserialize( + result: AnalyzeText200Response | AnalyzeTextDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + blocklistsMatchResults: (result.body["blocklistsMatchResults"] ?? []).map( + (p) => ({ + blocklistName: p["blocklistName"], + blockItemId: p["blockItemId"], + blockItemText: p["blockItemText"], + }) + ), + analyzeResults: (result.body["analyzeResults"] ?? []).map((p) => ({ + category: p["category"], + severity: p["severity"], + })), + }; +} + +/** A sync API for harmful content analysis for text. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence. */ +export async function analyzeText( + context: Client, + text: string, + options: AnalyzeTextRequestOptions = { requestOptions: {} } +): Promise { + const result = await _analyzeTextSend(context, text, options); + return _analyzeTextDeserialize(result); +} + +export function _analyzeImageSend( + context: Client, + image: ImageData, + options: AnalyzeImageRequestOptions = { requestOptions: {} } +): StreamableMethod { + return context + .path("/image:analyze") + .post({ + ...operationOptionsToRequestParameters(options), + body: { + image: { + content: + image["content"] !== undefined + ? uint8ArrayToString(image["content"], "base64") + : undefined, + blobUrl: image["blobUrl"], + }, + categories: options?.categories, + outputType: options?.outputType, + }, + }); +} + +export async function _analyzeImageDeserialize( + result: AnalyzeImage200Response | AnalyzeImageDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + analyzeResults: (result.body["analyzeResults"] ?? []).map((p) => ({ + category: p["category"], + severity: p["severity"], + })), + }; +} + +/** A sync API for harmful content analysis for image. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence. */ +export async function analyzeImage( + context: Client, + image: ImageData, + options: AnalyzeImageRequestOptions = { requestOptions: {} } +): Promise { + const result = await _analyzeImageSend(context, image, options); + return _analyzeImageDeserialize(result); +} + +export function _getTextBlocklistSend( + context: Client, + blocklistName: string, + options: GetTextBlocklistOptions = { requestOptions: {} } +): StreamableMethod< + GetTextBlocklist200Response | GetTextBlocklistDefaultResponse +> { + return context + .path("/text/blocklists/{blocklistName}", blocklistName) + .get({ ...operationOptionsToRequestParameters(options) }); +} + +export async function _getTextBlocklistDeserialize( + result: GetTextBlocklist200Response | GetTextBlocklistDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + blocklistName: result.body["blocklistName"], + description: result.body["description"], + }; +} + +/** Returns text blocklist details. */ +export async function getTextBlocklist( + context: Client, + blocklistName: string, + options: GetTextBlocklistOptions = { requestOptions: {} } +): Promise { + const result = await _getTextBlocklistSend(context, blocklistName, options); + return _getTextBlocklistDeserialize(result); +} + +export function _createOrUpdateTextBlocklistSend( + context: Client, + blocklistName: string, + options: CreateOrUpdateTextBlocklistOptions = { requestOptions: {} } +): StreamableMethod< + | CreateOrUpdateTextBlocklist200Response + | CreateOrUpdateTextBlocklist201Response + | CreateOrUpdateTextBlocklistDefaultResponse +> { + return context + .path("/text/blocklists/{blocklistName}", blocklistName) + .patch({ + ...operationOptionsToRequestParameters(options), + contentType: + (options.contentType as any) ?? "application/merge-patch+json", + body: { description: options?.description }, + }); +} + +export async function _createOrUpdateTextBlocklistDeserialize( + result: + | CreateOrUpdateTextBlocklist200Response + | CreateOrUpdateTextBlocklist201Response + | CreateOrUpdateTextBlocklistDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + blocklistName: result.body["blocklistName"], + description: result.body["description"], + }; +} + +/** Updates a text blocklist, if blocklistName does not exist, create a new blocklist. */ +export async function createOrUpdateTextBlocklist( + context: Client, + blocklistName: string, + options: CreateOrUpdateTextBlocklistOptions = { requestOptions: {} } +): Promise { + const result = await _createOrUpdateTextBlocklistSend( + context, + blocklistName, + options + ); + return _createOrUpdateTextBlocklistDeserialize(result); +} + +export function _deleteTextBlocklistSend( + context: Client, + blocklistName: string, + options: DeleteTextBlocklistOptions = { requestOptions: {} } +): StreamableMethod< + DeleteTextBlocklist204Response | DeleteTextBlocklistDefaultResponse +> { + return context + .path("/text/blocklists/{blocklistName}", blocklistName) + .delete({ ...operationOptionsToRequestParameters(options) }); +} + +export async function _deleteTextBlocklistDeserialize( + result: DeleteTextBlocklist204Response | DeleteTextBlocklistDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return; +} + +/** Deletes a text blocklist. */ +export async function deleteTextBlocklist( + context: Client, + blocklistName: string, + options: DeleteTextBlocklistOptions = { requestOptions: {} } +): Promise { + const result = await _deleteTextBlocklistSend( + context, + blocklistName, + options + ); + return _deleteTextBlocklistDeserialize(result); +} + +export function _listTextBlocklistsSend( + context: Client, + options: ListTextBlocklistsOptions = { requestOptions: {} } +): StreamableMethod< + ListTextBlocklists200Response | ListTextBlocklistsDefaultResponse +> { + return context + .path("/text/blocklists") + .get({ ...operationOptionsToRequestParameters(options) }); +} + +export async function _listTextBlocklistsDeserialize( + result: ListTextBlocklists200Response | ListTextBlocklistsDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + value: (result.body["value"] ?? []).map((p) => ({ + blocklistName: p["blocklistName"], + description: p["description"], + })), + nextLink: result.body["nextLink"], + }; +} + +/** Get all text blocklists details. */ +export async function listTextBlocklists( + context: Client, + options: ListTextBlocklistsOptions = { requestOptions: {} } +): Promise { + const result = await _listTextBlocklistsSend(context, options); + return _listTextBlocklistsDeserialize(result); +} + +export function _addOrUpdateBlockItemsSend( + context: Client, + blockItems: TextBlockItemInfo[], + blocklistName: string, + options: AddOrUpdateBlockItemsRequestOptions = { requestOptions: {} } +): StreamableMethod< + AddOrUpdateBlockItems200Response | AddOrUpdateBlockItemsDefaultResponse +> { + return context + .path( + "/text/blocklists/{blocklistName}:addOrUpdateBlockItems", + blocklistName + ) + .post({ + ...operationOptionsToRequestParameters(options), + body: { blockItems: blockItems }, + }); +} + +export async function _addOrUpdateBlockItemsDeserialize( + result: + | AddOrUpdateBlockItems200Response + | AddOrUpdateBlockItemsDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + value: (result.body["value"] ?? []).map((p) => ({ + blockItemId: p["blockItemId"], + description: p["description"], + text: p["text"], + })), + }; +} + +/** Add or update blockItems to a text blocklist. You can add or update at most 100 BlockItems in one request. */ +export async function addOrUpdateBlockItems( + context: Client, + blockItems: TextBlockItemInfo[], + blocklistName: string, + options: AddOrUpdateBlockItemsRequestOptions = { requestOptions: {} } +): Promise { + const result = await _addOrUpdateBlockItemsSend( + context, + blockItems, + blocklistName, + options + ); + return _addOrUpdateBlockItemsDeserialize(result); +} + +export function _removeBlockItemsSend( + context: Client, + blockItemIds: string[], + blocklistName: string, + options: RemoveBlockItemsRequestOptions = { requestOptions: {} } +): StreamableMethod< + RemoveBlockItems204Response | RemoveBlockItemsDefaultResponse +> { + return context + .path("/text/blocklists/{blocklistName}:removeBlockItems", blocklistName) + .post({ + ...operationOptionsToRequestParameters(options), + body: { blockItemIds: blockItemIds }, + }); +} + +export async function _removeBlockItemsDeserialize( + result: RemoveBlockItems204Response | RemoveBlockItemsDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return; +} + +/** Remove blockItems from a text blocklist. You can remove at most 100 BlockItems in one request. */ +export async function removeBlockItems( + context: Client, + blockItemIds: string[], + blocklistName: string, + options: RemoveBlockItemsRequestOptions = { requestOptions: {} } +): Promise { + const result = await _removeBlockItemsSend( + context, + blockItemIds, + blocklistName, + options + ); + return _removeBlockItemsDeserialize(result); +} + +export function _getTextBlocklistItemSend( + context: Client, + blocklistName: string, + blockItemId: string, + options: GetTextBlocklistItemOptions = { requestOptions: {} } +): StreamableMethod< + GetTextBlocklistItem200Response | GetTextBlocklistItemDefaultResponse +> { + return context + .path( + "/text/blocklists/{blocklistName}/blockItems/{blockItemId}", + blocklistName, + blockItemId + ) + .get({ ...operationOptionsToRequestParameters(options) }); +} + +export async function _getTextBlocklistItemDeserialize( + result: GetTextBlocklistItem200Response | GetTextBlocklistItemDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + blockItemId: result.body["blockItemId"], + description: result.body["description"], + text: result.body["text"], + }; +} + +/** Get blockItem By blockItemId from a text blocklist. */ +export async function getTextBlocklistItem( + context: Client, + blocklistName: string, + blockItemId: string, + options: GetTextBlocklistItemOptions = { requestOptions: {} } +): Promise { + const result = await _getTextBlocklistItemSend( + context, + blocklistName, + blockItemId, + options + ); + return _getTextBlocklistItemDeserialize(result); +} + +export function _listTextBlocklistItemsSend( + context: Client, + blocklistName: string, + options: ListTextBlocklistItemsOptions = { requestOptions: {} } +): StreamableMethod< + ListTextBlocklistItems200Response | ListTextBlocklistItemsDefaultResponse +> { + return context + .path("/text/blocklists/{blocklistName}/blockItems", blocklistName) + .get({ + ...operationOptionsToRequestParameters(options), + queryParameters: { + top: options?.top, + skip: options?.skip, + maxpagesize: options?.maxpagesize, + }, + }); +} + +export async function _listTextBlocklistItemsDeserialize( + result: + | ListTextBlocklistItems200Response + | ListTextBlocklistItemsDefaultResponse +): Promise { + if (isUnexpected(result)) { + throw result.body; + } + + return { + value: (result.body["value"] ?? []).map((p) => ({ + blockItemId: p["blockItemId"], + description: p["description"], + text: p["text"], + })), + nextLink: result.body["nextLink"], + }; +} + +/** Get all blockItems in a text blocklist */ +export async function listTextBlocklistItems( + context: Client, + blocklistName: string, + options: ListTextBlocklistItemsOptions = { requestOptions: {} } +): Promise { + const result = await _listTextBlocklistItemsSend( + context, + blocklistName, + options + ); + return _listTextBlocklistItemsDeserialize(result); +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts new file mode 100644 index 0000000000..b88674e51a --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/index.ts @@ -0,0 +1,35 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export { + ContentSafetyClient, + ContentSafetyClientOptions, +} from "./ContentSafetyClient.js"; +export { + TextCategory, + AnalyzeTextOutputType, + AnalyzeTextResult, + TextBlocklistMatchResult, + TextAnalyzeSeverityResult, + ImageData, + ImageCategory, + AnalyzeImageOutputType, + AnalyzeImageResult, + ImageAnalyzeSeverityResult, + TextBlocklist, + TextBlockItemInfo, + AddOrUpdateBlockItemsResult, + TextBlockItem, + PagedTextBlocklist, + PagedTextBlockItem, + AnalyzeTextRequestOptions, + AnalyzeImageRequestOptions, + GetTextBlocklistOptions, + CreateOrUpdateTextBlocklistOptions, + DeleteTextBlocklistOptions, + ListTextBlocklistsOptions, + AddOrUpdateBlockItemsRequestOptions, + RemoveBlockItemsRequestOptions, + GetTextBlocklistItemOptions, + ListTextBlocklistItemsOptions, +} from "./models/index.js"; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/logger.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/logger.ts new file mode 100644 index 0000000000..8236c0444b --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/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("ai-content-safety"); diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts new file mode 100644 index 0000000000..c5627b47ac --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/index.ts @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +export { + TextCategory, + AnalyzeTextOutputType, + AnalyzeTextResult, + TextBlocklistMatchResult, + TextAnalyzeSeverityResult, + ImageData, + ImageCategory, + AnalyzeImageOutputType, + AnalyzeImageResult, + ImageAnalyzeSeverityResult, + TextBlocklist, + TextBlockItemInfo, + AddOrUpdateBlockItemsResult, + TextBlockItem, + PagedTextBlocklist, + PagedTextBlockItem, +} from "./models.js"; +export { + AnalyzeTextRequestOptions, + AnalyzeImageRequestOptions, + GetTextBlocklistOptions, + CreateOrUpdateTextBlocklistOptions, + DeleteTextBlocklistOptions, + ListTextBlocklistsOptions, + AddOrUpdateBlockItemsRequestOptions, + RemoveBlockItemsRequestOptions, + GetTextBlocklistItemOptions, + ListTextBlocklistItemsOptions, +} from "./options.js"; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/models.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/models.ts new file mode 100644 index 0000000000..66fd6b8b05 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/models.ts @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** Text analyze category */ +/** "Hate", "SelfHarm", "Sexual", "Violence" */ +export type TextCategory = string; +/** The type of text analysis output. */ +/** "FourLevels", "EightLevels" */ +export type AnalyzeTextOutputType = string; + +/** The analysis response of the text */ +export interface AnalyzeTextResult { + /** The details of blocklist match. */ + blocklistsMatchResults?: TextBlocklistMatchResult[]; + /** Analysis result for categories. */ + analyzeResults: TextAnalyzeSeverityResult[]; +} + +/** The result of blocklist match. */ +export interface TextBlocklistMatchResult { + /** The name of matched blocklist. */ + blocklistName: string; + /** The id of matched item. */ + blockItemId: string; + /** The content of matched item. */ + blockItemText: string; +} + +/** Text analysis result. */ +export interface TextAnalyzeSeverityResult { + /** The text category. */ + category: TextCategory; + /** This field is decided by outputType in request, if choose "FourLevels", the value could be 0,2,4,6. The higher the severity of input content, the larger this value is. */ + severity?: number; +} + +/** The content or blob url of image, could be base64 encoding bytes or blob url. You can choose only one of them. If both are given, the request will be refused. The maximum size of image is 2048 pixels * 2048 pixels, no larger than 4MB at the same time. The minimum size of image is 50 pixels * 50 pixels. */ +export interface ImageData { + /** Base64 encoding of image. */ + content?: Uint8Array; + /** The blob url of image. */ + blobUrl?: string; +} + +/** Image analyze category */ +/** "Hate", "SelfHarm", "Sexual", "Violence" */ +export type ImageCategory = string; +/** The type of image analysis output. */ +/** "FourLevels" */ +export type AnalyzeImageOutputType = string; + +/** The analysis response of the image. */ +export interface AnalyzeImageResult { + /** Analysis result for categories. */ + analyzeResults: ImageAnalyzeSeverityResult[]; +} + +/** Image analysis result. */ +export interface ImageAnalyzeSeverityResult { + /** The image category. */ + category: ImageCategory; + /** This field is decided by outputType in request, if choose "FourLevels", the value could be 0,2,4,6. The higher the severity of input content, the larger this value is. */ + severity?: number; +} + +/** Text Blocklist. */ +export interface TextBlocklist { + /** Text blocklist name. */ + readonly blocklistName: string; + /** Text blocklist description. */ + description?: string; +} + +/** Block item info in text blocklist. */ +export interface TextBlockItemInfo { + /** Block item description. */ + description?: string; + /** Block item content. */ + text: string; +} + +/** The response of adding blockItems to text blocklist. */ +export interface AddOrUpdateBlockItemsResult { + /** Array of blockItems added. */ + value?: TextBlockItem[]; +} + +/** Item in TextBlocklist. */ +export interface TextBlockItem { + /** Block Item Id. It will be uuid. */ + readonly blockItemId: string; + /** Block item description. */ + description?: string; + /** Block item content. */ + text: string; +} + +/** Paged collection of TextBlocklist items */ +export interface PagedTextBlocklist { + /** The TextBlocklist items on this page */ + value: TextBlocklist[]; + /** The link to the next page of items */ + nextLink?: string; +} + +/** Paged collection of TextBlockItem items */ +export interface PagedTextBlockItem { + /** The TextBlockItem items on this page */ + value: TextBlockItem[]; + /** The link to the next page of items */ + nextLink?: string; +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/options.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/options.ts new file mode 100644 index 0000000000..1d09a11f7f --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/models/options.ts @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { OperationOptions } from "@azure-rest/core-client"; +import { + TextCategory, + AnalyzeTextOutputType, + ImageCategory, + AnalyzeImageOutputType, +} from "./models.js"; + +export interface AnalyzeTextRequestOptions extends OperationOptions { + /** The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned. */ + categories?: TextCategory[]; + /** The names of blocklists. */ + blocklistNames?: string[]; + /** When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit. */ + breakByBlocklists?: boolean; + /** The type of text analysis output. If not assigned, the default value is "FourLevels". */ + outputType?: AnalyzeTextOutputType; +} + +export interface AnalyzeImageRequestOptions extends OperationOptions { + /** The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned. */ + categories?: ImageCategory[]; + /** The type of image analysis output. If not assigned, the default value is "FourLevels". */ + outputType?: AnalyzeImageOutputType; +} + +export interface GetTextBlocklistOptions extends OperationOptions {} + +export interface CreateOrUpdateTextBlocklistOptions extends OperationOptions { + /** Text blocklist description. */ + description?: string; + /** This request has a JSON Merge Patch body. */ + contentType?: string; +} + +export interface DeleteTextBlocklistOptions extends OperationOptions {} + +export interface ListTextBlocklistsOptions extends OperationOptions {} + +export interface AddOrUpdateBlockItemsRequestOptions extends OperationOptions {} + +export interface RemoveBlockItemsRequestOptions extends OperationOptions {} + +export interface GetTextBlocklistItemOptions extends OperationOptions {} + +export interface ListTextBlocklistItemsOptions extends OperationOptions { + /** The number of result items to return. */ + top?: number; + /** The number of result items to skip. */ + skip?: number; + /** The maximum number of result items per page. */ + maxpagesize?: number; +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/clientDefinitions.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/clientDefinitions.ts new file mode 100644 index 0000000000..08ee7ed1f9 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/clientDefinitions.ts @@ -0,0 +1,160 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AnalyzeTextParameters, + AnalyzeImageParameters, + GetTextBlocklistParameters, + CreateOrUpdateTextBlocklistParameters, + DeleteTextBlocklistParameters, + ListTextBlocklistsParameters, + AddOrUpdateBlockItemsParameters, + RemoveBlockItemsParameters, + GetTextBlocklistItemParameters, + ListTextBlocklistItemsParameters, +} from "./parameters.js"; +import { + AnalyzeText200Response, + AnalyzeTextDefaultResponse, + AnalyzeImage200Response, + AnalyzeImageDefaultResponse, + GetTextBlocklist200Response, + GetTextBlocklistDefaultResponse, + CreateOrUpdateTextBlocklist200Response, + CreateOrUpdateTextBlocklist201Response, + CreateOrUpdateTextBlocklistDefaultResponse, + DeleteTextBlocklist204Response, + DeleteTextBlocklistDefaultResponse, + ListTextBlocklists200Response, + ListTextBlocklistsDefaultResponse, + AddOrUpdateBlockItems200Response, + AddOrUpdateBlockItemsDefaultResponse, + RemoveBlockItems204Response, + RemoveBlockItemsDefaultResponse, + GetTextBlocklistItem200Response, + GetTextBlocklistItemDefaultResponse, + ListTextBlocklistItems200Response, + ListTextBlocklistItemsDefaultResponse, +} from "./responses.js"; +import { Client, StreamableMethod } from "@azure-rest/core-client"; + +export interface AnalyzeText { + /** A sync API for harmful content analysis for text. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence. */ + post( + options: AnalyzeTextParameters + ): StreamableMethod; +} + +export interface AnalyzeImage { + /** A sync API for harmful content analysis for image. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence. */ + post( + options: AnalyzeImageParameters + ): StreamableMethod; +} + +export interface GetTextBlocklist { + /** Returns text blocklist details. */ + get( + options?: GetTextBlocklistParameters + ): StreamableMethod< + GetTextBlocklist200Response | GetTextBlocklistDefaultResponse + >; + /** Updates a text blocklist, if blocklistName does not exist, create a new blocklist. */ + patch( + options: CreateOrUpdateTextBlocklistParameters + ): StreamableMethod< + | CreateOrUpdateTextBlocklist200Response + | CreateOrUpdateTextBlocklist201Response + | CreateOrUpdateTextBlocklistDefaultResponse + >; + /** Deletes a text blocklist. */ + delete( + options?: DeleteTextBlocklistParameters + ): StreamableMethod< + DeleteTextBlocklist204Response | DeleteTextBlocklistDefaultResponse + >; +} + +export interface ListTextBlocklists { + /** Get all text blocklists details. */ + get( + options?: ListTextBlocklistsParameters + ): StreamableMethod< + ListTextBlocklists200Response | ListTextBlocklistsDefaultResponse + >; +} + +export interface AddOrUpdateBlockItems { + /** Add or update blockItems to a text blocklist. You can add or update at most 100 BlockItems in one request. */ + post( + options?: AddOrUpdateBlockItemsParameters + ): StreamableMethod< + AddOrUpdateBlockItems200Response | AddOrUpdateBlockItemsDefaultResponse + >; +} + +export interface RemoveBlockItems { + /** Remove blockItems from a text blocklist. You can remove at most 100 BlockItems in one request. */ + post( + options?: RemoveBlockItemsParameters + ): StreamableMethod< + RemoveBlockItems204Response | RemoveBlockItemsDefaultResponse + >; +} + +export interface GetTextBlocklistItem { + /** Get blockItem By blockItemId from a text blocklist. */ + get( + options?: GetTextBlocklistItemParameters + ): StreamableMethod< + GetTextBlocklistItem200Response | GetTextBlocklistItemDefaultResponse + >; +} + +export interface ListTextBlocklistItems { + /** Get all blockItems in a text blocklist */ + get( + options?: ListTextBlocklistItemsParameters + ): StreamableMethod< + ListTextBlocklistItems200Response | ListTextBlocklistItemsDefaultResponse + >; +} + +export interface Routes { + /** Resource for '/text:analyze' has methods for the following verbs: post */ + (path: "/text:analyze"): AnalyzeText; + /** Resource for '/image:analyze' has methods for the following verbs: post */ + (path: "/image:analyze"): AnalyzeImage; + /** Resource for '/text/blocklists/\{blocklistName\}' has methods for the following verbs: get, patch, delete */ + ( + path: "/text/blocklists/{blocklistName}", + blocklistName: string + ): GetTextBlocklist; + /** Resource for '/text/blocklists' has methods for the following verbs: get */ + (path: "/text/blocklists"): ListTextBlocklists; + /** Resource for '/text/blocklists/\{blocklistName\}:addOrUpdateBlockItems' has methods for the following verbs: post */ + ( + path: "/text/blocklists/{blocklistName}:addOrUpdateBlockItems", + blocklistName: string + ): AddOrUpdateBlockItems; + /** Resource for '/text/blocklists/\{blocklistName\}:removeBlockItems' has methods for the following verbs: post */ + ( + path: "/text/blocklists/{blocklistName}:removeBlockItems", + blocklistName: string + ): RemoveBlockItems; + /** Resource for '/text/blocklists/\{blocklistName\}/blockItems/\{blockItemId\}' has methods for the following verbs: get */ + ( + path: "/text/blocklists/{blocklistName}/blockItems/{blockItemId}", + blocklistName: string, + blockItemId: string + ): GetTextBlocklistItem; + /** Resource for '/text/blocklists/\{blocklistName\}/blockItems' has methods for the following verbs: get */ + ( + path: "/text/blocklists/{blocklistName}/blockItems", + blocklistName: string + ): ListTextBlocklistItems; +} + +export type ContentSafetyContext = Client & { + path: Routes; +}; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/contentSafetyClient.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/contentSafetyClient.ts new file mode 100644 index 0000000000..3e4ef67b12 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/contentSafetyClient.ts @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { getClient, ClientOptions } from "@azure-rest/core-client"; +import { logger } from "../logger.js"; +import { TokenCredential, KeyCredential } from "@azure/core-auth"; +import { ContentSafetyContext } from "./clientDefinitions.js"; + +/** + * Initialize a new instance of `ContentSafetyContext` + * @param endpoint - Supported Cognitive Services endpoints (protocol and hostname, for example: + * https://.cognitiveservices.azure.com). + * @param credentials - uniquely identify client credential + * @param options - the parameter for all optional parameters + */ +export default function createClient( + endpoint: string, + credentials: TokenCredential | KeyCredential, + options: ClientOptions = {} +): ContentSafetyContext { + const baseUrl = options.baseUrl ?? `${endpoint}/contentsafety`; + options.apiVersion = options.apiVersion ?? "2023-10-01"; + options = { + ...options, + credentials: { + scopes: options.credentials?.scopes ?? [ + "https://cognitiveservices.azure.com/.default", + ], + apiKeyHeaderName: + options.credentials?.apiKeyHeaderName ?? "Ocp-Apim-Subscription-Key", + }, + }; + + const userAgentInfo = `azsdk-js-ai-content-safety-rest/1.0.0`; + const userAgentPrefix = + options.userAgentOptions && options.userAgentOptions.userAgentPrefix + ? `${options.userAgentOptions.userAgentPrefix} ${userAgentInfo}` + : `${userAgentInfo}`; + options = { + ...options, + userAgentOptions: { + userAgentPrefix, + }, + loggingOptions: { + logger: options.loggingOptions?.logger ?? logger.info, + }, + }; + + const client = getClient( + baseUrl, + credentials, + options + ) as ContentSafetyContext; + + return client; +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/index.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/index.ts new file mode 100644 index 0000000000..611b1a6c4c --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/index.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import ContentSafetyClient from "./contentSafetyClient.js"; + +export * from "./contentSafetyClient.js"; +export * from "./parameters.js"; +export * from "./responses.js"; +export * from "./clientDefinitions.js"; +export * from "./isUnexpected.js"; +export * from "./models.js"; +export * from "./outputModels.js"; +export * from "./paginateHelper.js"; + +export default ContentSafetyClient; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/isUnexpected.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/isUnexpected.ts new file mode 100644 index 0000000000..4b0779fc2a --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/isUnexpected.ts @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AnalyzeText200Response, + AnalyzeTextDefaultResponse, + AnalyzeImage200Response, + AnalyzeImageDefaultResponse, + GetTextBlocklist200Response, + GetTextBlocklistDefaultResponse, + CreateOrUpdateTextBlocklist200Response, + CreateOrUpdateTextBlocklist201Response, + CreateOrUpdateTextBlocklistDefaultResponse, + DeleteTextBlocklist204Response, + DeleteTextBlocklistDefaultResponse, + ListTextBlocklists200Response, + ListTextBlocklistsDefaultResponse, + AddOrUpdateBlockItems200Response, + AddOrUpdateBlockItemsDefaultResponse, + RemoveBlockItems204Response, + RemoveBlockItemsDefaultResponse, + GetTextBlocklistItem200Response, + GetTextBlocklistItemDefaultResponse, + ListTextBlocklistItems200Response, + ListTextBlocklistItemsDefaultResponse, +} from "./responses.js"; + +const responseMap: Record = { + "POST /text:analyze": ["200"], + "POST /image:analyze": ["200"], + "GET /text/blocklists/{blocklistName}": ["200"], + "PATCH /text/blocklists/{blocklistName}": ["200", "201"], + "DELETE /text/blocklists/{blocklistName}": ["204"], + "GET /text/blocklists": ["200"], + "POST /text/blocklists/{blocklistName}:addOrUpdateBlockItems": ["200"], + "POST /text/blocklists/{blocklistName}:removeBlockItems": ["204"], + "GET /text/blocklists/{blocklistName}/blockItems/{blockItemId}": ["200"], + "GET /text/blocklists/{blocklistName}/blockItems": ["200"], +}; + +export function isUnexpected( + response: AnalyzeText200Response | AnalyzeTextDefaultResponse +): response is AnalyzeTextDefaultResponse; +export function isUnexpected( + response: AnalyzeImage200Response | AnalyzeImageDefaultResponse +): response is AnalyzeImageDefaultResponse; +export function isUnexpected( + response: GetTextBlocklist200Response | GetTextBlocklistDefaultResponse +): response is GetTextBlocklistDefaultResponse; +export function isUnexpected( + response: + | CreateOrUpdateTextBlocklist200Response + | CreateOrUpdateTextBlocklist201Response + | CreateOrUpdateTextBlocklistDefaultResponse +): response is CreateOrUpdateTextBlocklistDefaultResponse; +export function isUnexpected( + response: DeleteTextBlocklist204Response | DeleteTextBlocklistDefaultResponse +): response is DeleteTextBlocklistDefaultResponse; +export function isUnexpected( + response: ListTextBlocklists200Response | ListTextBlocklistsDefaultResponse +): response is ListTextBlocklistsDefaultResponse; +export function isUnexpected( + response: + | AddOrUpdateBlockItems200Response + | AddOrUpdateBlockItemsDefaultResponse +): response is AddOrUpdateBlockItemsDefaultResponse; +export function isUnexpected( + response: RemoveBlockItems204Response | RemoveBlockItemsDefaultResponse +): response is RemoveBlockItemsDefaultResponse; +export function isUnexpected( + response: + | GetTextBlocklistItem200Response + | GetTextBlocklistItemDefaultResponse +): response is GetTextBlocklistItemDefaultResponse; +export function isUnexpected( + response: + | ListTextBlocklistItems200Response + | ListTextBlocklistItemsDefaultResponse +): response is ListTextBlocklistItemsDefaultResponse; +export function isUnexpected( + response: + | AnalyzeText200Response + | AnalyzeTextDefaultResponse + | AnalyzeImage200Response + | AnalyzeImageDefaultResponse + | GetTextBlocklist200Response + | GetTextBlocklistDefaultResponse + | CreateOrUpdateTextBlocklist200Response + | CreateOrUpdateTextBlocklist201Response + | CreateOrUpdateTextBlocklistDefaultResponse + | DeleteTextBlocklist204Response + | DeleteTextBlocklistDefaultResponse + | ListTextBlocklists200Response + | ListTextBlocklistsDefaultResponse + | AddOrUpdateBlockItems200Response + | AddOrUpdateBlockItemsDefaultResponse + | RemoveBlockItems204Response + | RemoveBlockItemsDefaultResponse + | GetTextBlocklistItem200Response + | GetTextBlocklistItemDefaultResponse + | ListTextBlocklistItems200Response + | ListTextBlocklistItemsDefaultResponse +): response is + | AnalyzeTextDefaultResponse + | AnalyzeImageDefaultResponse + | GetTextBlocklistDefaultResponse + | CreateOrUpdateTextBlocklistDefaultResponse + | DeleteTextBlocklistDefaultResponse + | ListTextBlocklistsDefaultResponse + | AddOrUpdateBlockItemsDefaultResponse + | RemoveBlockItemsDefaultResponse + | GetTextBlocklistItemDefaultResponse + | ListTextBlocklistItemsDefaultResponse { + const lroOriginal = response.headers["x-ms-original-url"]; + const url = new URL(lroOriginal ?? response.request.url); + const method = response.request.method; + let pathDetails = responseMap[`${method} ${url.pathname}`]; + if (!pathDetails) { + pathDetails = getParametrizedPathSuccess(method, url.pathname); + } + return !pathDetails.includes(response.status); +} + +function getParametrizedPathSuccess(method: string, path: string): string[] { + 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: string[] = []; + + // Iterate the responseMap to find a match + for (const [key, value] of Object.entries(responseMap)) { + // 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/contentsafety_modular/generated/typespec-ts/src/rest/models.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/models.ts new file mode 100644 index 0000000000..4fc05fdd96 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/models.ts @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** The analysis request of the text. */ +export interface AnalyzeTextOptions { + /** The text needs to be scanned. We support at most 10k Unicode characters (unicode code points) in text of one request. */ + text: string; + /** The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned. */ + categories?: string[]; + /** The names of blocklists. */ + blocklistNames?: string[]; + /** When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit. */ + breakByBlocklists?: boolean; + /** + * The type of text analysis output. If not assigned, the default value is "FourLevels". + * + * Possible values: FourLevels, EightLevels + */ + outputType?: string; +} + +/** The analysis request of the image. */ +export interface AnalyzeImageOptions { + /** The image needs to be analyzed. */ + image: ImageData; + /** The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned. */ + categories?: string[]; + /** + * The type of image analysis output. If not assigned, the default value is "FourLevels". + * + * Possible values: FourLevels + */ + outputType?: string; +} + +/** The content or blob url of image, could be base64 encoding bytes or blob url. You can choose only one of them. If both are given, the request will be refused. The maximum size of image is 2048 pixels * 2048 pixels, no larger than 4MB at the same time. The minimum size of image is 50 pixels * 50 pixels. */ +export interface ImageData { + /** Base64 encoding of image. */ + content?: string; + /** The blob url of image. */ + blobUrl?: string; +} + +/** Text Blocklist. */ +export interface TextBlocklist { + /** Text blocklist name. */ + blocklistName: string; + /** Text blocklist description. */ + description?: string; +} + +/** The request of adding blockItems to text blocklist. */ +export interface AddOrUpdateBlockItemsOptions { + /** Array of blockItemInfo to add. */ + blockItems: Array; +} + +/** Block item info in text blocklist. */ +export interface TextBlockItemInfo { + /** Block item description. */ + description?: string; + /** Block item content. */ + text: string; +} + +/** The request of removing blockItems from text blocklist. */ +export interface RemoveBlockItemsOptions { + /** Array of blockItemIds to remove. */ + blockItemIds: string[]; +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/outputModels.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/outputModels.ts new file mode 100644 index 0000000000..954d97f819 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/outputModels.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { Paged } from "@azure/core-paging"; + +/** The analysis request of the text. */ +export interface AnalyzeTextOptionsOutput { + /** The text needs to be scanned. We support at most 10k Unicode characters (unicode code points) in text of one request. */ + text: string; + /** The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned. */ + categories?: string[]; + /** The names of blocklists. */ + blocklistNames?: string[]; + /** When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit. */ + breakByBlocklists?: boolean; + /** + * The type of text analysis output. If not assigned, the default value is "FourLevels". + * + * Possible values: FourLevels, EightLevels + */ + outputType?: string; +} + +/** The analysis response of the text */ +export interface AnalyzeTextResultOutput { + /** The details of blocklist match. */ + blocklistsMatchResults?: Array; + /** Analysis result for categories. */ + analyzeResults: Array; +} + +/** The result of blocklist match. */ +export interface TextBlocklistMatchResultOutput { + /** The name of matched blocklist. */ + blocklistName: string; + /** The id of matched item. */ + blockItemId: string; + /** The content of matched item. */ + blockItemText: string; +} + +/** Text analysis result. */ +export interface TextAnalyzeSeverityResultOutput { + /** + * The text category. + * + * Possible values: Hate, SelfHarm, Sexual, Violence + */ + category: string; + /** This field is decided by outputType in request, if choose "FourLevels", the value could be 0,2,4,6. The higher the severity of input content, the larger this value is. */ + severity?: number; +} + +/** The analysis request of the image. */ +export interface AnalyzeImageOptionsOutput { + /** The image needs to be analyzed. */ + image: ImageDataOutput; + /** The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned. */ + categories?: string[]; + /** + * The type of image analysis output. If not assigned, the default value is "FourLevels". + * + * Possible values: FourLevels + */ + outputType?: string; +} + +/** The content or blob url of image, could be base64 encoding bytes or blob url. You can choose only one of them. If both are given, the request will be refused. The maximum size of image is 2048 pixels * 2048 pixels, no larger than 4MB at the same time. The minimum size of image is 50 pixels * 50 pixels. */ +export interface ImageDataOutput { + /** Base64 encoding of image. */ + content?: string; + /** The blob url of image. */ + blobUrl?: string; +} + +/** The analysis response of the image. */ +export interface AnalyzeImageResultOutput { + /** Analysis result for categories. */ + analyzeResults: Array; +} + +/** Image analysis result. */ +export interface ImageAnalyzeSeverityResultOutput { + /** + * The image category. + * + * Possible values: Hate, SelfHarm, Sexual, Violence + */ + category: string; + /** This field is decided by outputType in request, if choose "FourLevels", the value could be 0,2,4,6. The higher the severity of input content, the larger this value is. */ + severity?: number; +} + +/** Text Blocklist. */ +export interface TextBlocklistOutput { + /** Text blocklist name. */ + blocklistName: string; + /** Text blocklist description. */ + description?: string; +} + +/** The response of adding blockItems to text blocklist. */ +export interface AddOrUpdateBlockItemsResultOutput { + /** Array of blockItems added. */ + value?: Array; +} + +/** Item in TextBlocklist. */ +export interface TextBlockItemOutput { + /** Block Item Id. It will be uuid. */ + blockItemId: string; + /** Block item description. */ + description?: string; + /** Block item content. */ + text: string; +} + +/** Paged collection of TextBlocklist items */ +export type PagedTextBlocklistOutput = Paged; +/** Paged collection of TextBlockItem items */ +export type PagedTextBlockItemOutput = Paged; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/paginateHelper.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/paginateHelper.ts new file mode 100644 index 0000000000..1c9af35b1e --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/paginateHelper.ts @@ -0,0 +1,154 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + getPagedAsyncIterator, + PagedAsyncIterableIterator, + PagedResult, +} from "@azure/core-paging"; +import { + Client, + createRestError, + PathUncheckedResponse, +} from "@azure-rest/core-client"; + +/** + * Helper type to extract the type of an array + */ +export type GetArrayType = T extends Array ? TData : never; + +/** + * The type of a custom function that defines how to get a page and a link to the next one if any. + */ +export type GetPage = ( + pageLink: string, + maxPageSize?: number +) => Promise<{ + page: TPage; + nextPageLink?: string; +}>; + +/** + * Options for the paging helper + */ +export interface PagingOptions { + /** + * Custom function to extract pagination details for crating the PagedAsyncIterableIterator + */ + customGetPage?: GetPage[]>; +} + +/** + * Helper type to infer the Type of the paged elements from the response type + * This type is generated based on the swagger information for x-ms-pageable + * specifically on the itemName property which indicates the property of the response + * where the page items are found. The default value is `value`. + * This type will allow us to provide strongly typed Iterator based on the response we get as second parameter + */ +export type PaginateReturn = TResult extends { + body: { value?: infer TPage }; +} + ? GetArrayType + : Array; + +/** + * Helper to paginate results from an initial response that follows the specification of Autorest `x-ms-pageable` extension + * @param client - Client to use for sending the next page requests + * @param initialResponse - Initial response containing the nextLink and current page of elements + * @param customGetPage - Optional - Function to define how to extract the page and next link to be used to paginate the results + * @returns - PagedAsyncIterableIterator to iterate the elements + */ +export function paginate( + client: Client, + initialResponse: TResponse, + options: PagingOptions = {} +): PagedAsyncIterableIterator> { + // Extract element type from initial response + type TElement = PaginateReturn; + let firstRun = true; + const itemName = "value"; + const nextLinkName = "nextLink"; + const { customGetPage } = options; + const pagedResult: PagedResult = { + firstPageLink: "", + getPage: + typeof customGetPage === "function" + ? customGetPage + : async (pageLink: string) => { + const result = firstRun + ? initialResponse + : await client.pathUnchecked(pageLink).get(); + firstRun = false; + checkPagingRequest(result); + const nextLink = getNextLink(result.body, nextLinkName); + const values = getElements(result.body, itemName); + return { + page: values, + nextPageLink: nextLink, + }; + }, + }; + + return getPagedAsyncIterator(pagedResult); +} + +/** + * 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") { + throw new Error( + `Body Property ${nextLinkName} should be a string or 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[]; + + // value has to be an array according to the x-ms-pageable extension. + // The fact that this must be an array is used above to calculate the + // type of elements in the page in PaginateReturn + if (!Array.isArray(value)) { + throw new Error( + `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): void { + const Http2xxStatusCodes = [ + "200", + "201", + "202", + "203", + "204", + "205", + "206", + "207", + "208", + "226", + ]; + if (!Http2xxStatusCodes.includes(response.status)) { + throw createRestError( + `Pagination failed with unexpected statusCode ${response.status}`, + response + ); + } +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/parameters.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/parameters.ts new file mode 100644 index 0000000000..45bd820f12 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/parameters.ts @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { RequestParameters } from "@azure-rest/core-client"; +import { + AnalyzeTextOptions, + AnalyzeImageOptions, + TextBlocklist, + AddOrUpdateBlockItemsOptions, + RemoveBlockItemsOptions, +} from "./models.js"; + +export interface AnalyzeTextBodyParam { + /** The request of text analysis. */ + body: AnalyzeTextOptions; +} + +export type AnalyzeTextParameters = AnalyzeTextBodyParam & RequestParameters; + +export interface AnalyzeImageBodyParam { + /** The analysis request of the image. */ + body: AnalyzeImageOptions; +} + +export type AnalyzeImageParameters = AnalyzeImageBodyParam & RequestParameters; +export type GetTextBlocklistParameters = RequestParameters; +/** The resource instance. */ +export type TextBlocklistResourceMergeAndPatch = Partial; + +export interface CreateOrUpdateTextBlocklistBodyParam { + /** The resource instance. */ + body: TextBlocklistResourceMergeAndPatch; +} + +export interface CreateOrUpdateTextBlocklistMediaTypesParam { + /** This request has a JSON Merge Patch body. */ + contentType: "application/merge-patch+json"; +} + +export type CreateOrUpdateTextBlocklistParameters = + CreateOrUpdateTextBlocklistMediaTypesParam & + CreateOrUpdateTextBlocklistBodyParam & + RequestParameters; +export type DeleteTextBlocklistParameters = RequestParameters; +export type ListTextBlocklistsParameters = RequestParameters; + +export interface AddOrUpdateBlockItemsBodyParam { + body?: AddOrUpdateBlockItemsOptions; +} + +export type AddOrUpdateBlockItemsParameters = AddOrUpdateBlockItemsBodyParam & + RequestParameters; + +export interface RemoveBlockItemsBodyParam { + body?: RemoveBlockItemsOptions; +} + +export type RemoveBlockItemsParameters = RemoveBlockItemsBodyParam & + RequestParameters; +export type GetTextBlocklistItemParameters = RequestParameters; + +export interface ListTextBlocklistItemsQueryParamProperties { + /** The number of result items to return. */ + top?: number; + /** The number of result items to skip. */ + skip?: number; + /** The maximum number of result items per page. */ + maxpagesize?: number; +} + +export interface ListTextBlocklistItemsQueryParam { + queryParameters?: ListTextBlocklistItemsQueryParamProperties; +} + +export type ListTextBlocklistItemsParameters = + ListTextBlocklistItemsQueryParam & RequestParameters; diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/responses.ts b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/responses.ts new file mode 100644 index 0000000000..098534893a --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/src/rest/responses.ts @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { RawHttpHeaders } from "@azure/core-rest-pipeline"; +import { HttpResponse, ErrorResponse } from "@azure-rest/core-client"; +import { + AnalyzeTextResultOutput, + AnalyzeImageResultOutput, + TextBlocklistOutput, + PagedTextBlocklistOutput, + AddOrUpdateBlockItemsResultOutput, + TextBlockItemOutput, + PagedTextBlockItemOutput, +} from "./outputModels.js"; + +/** The request has succeeded. */ +export interface AnalyzeText200Response extends HttpResponse { + status: "200"; + body: AnalyzeTextResultOutput; +} + +export interface AnalyzeTextDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface AnalyzeTextDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & AnalyzeTextDefaultHeaders; +} + +/** The request has succeeded. */ +export interface AnalyzeImage200Response extends HttpResponse { + status: "200"; + body: AnalyzeImageResultOutput; +} + +export interface AnalyzeImageDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface AnalyzeImageDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & AnalyzeImageDefaultHeaders; +} + +/** The request has succeeded. */ +export interface GetTextBlocklist200Response extends HttpResponse { + status: "200"; + body: TextBlocklistOutput; +} + +export interface GetTextBlocklistDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface GetTextBlocklistDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & GetTextBlocklistDefaultHeaders; +} + +/** The request has succeeded. */ +export interface CreateOrUpdateTextBlocklist200Response extends HttpResponse { + status: "200"; + body: TextBlocklistOutput; +} + +/** The request has succeeded and a new resource has been created as a result. */ +export interface CreateOrUpdateTextBlocklist201Response extends HttpResponse { + status: "201"; + body: TextBlocklistOutput; +} + +export interface CreateOrUpdateTextBlocklistDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface CreateOrUpdateTextBlocklistDefaultResponse + extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & CreateOrUpdateTextBlocklistDefaultHeaders; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface DeleteTextBlocklist204Response extends HttpResponse { + status: "204"; +} + +export interface DeleteTextBlocklistDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface DeleteTextBlocklistDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & DeleteTextBlocklistDefaultHeaders; +} + +/** The request has succeeded. */ +export interface ListTextBlocklists200Response extends HttpResponse { + status: "200"; + body: PagedTextBlocklistOutput; +} + +export interface ListTextBlocklistsDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface ListTextBlocklistsDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & ListTextBlocklistsDefaultHeaders; +} + +/** The request has succeeded. */ +export interface AddOrUpdateBlockItems200Response extends HttpResponse { + status: "200"; + body: AddOrUpdateBlockItemsResultOutput; +} + +export interface AddOrUpdateBlockItemsDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface AddOrUpdateBlockItemsDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & AddOrUpdateBlockItemsDefaultHeaders; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface RemoveBlockItems204Response extends HttpResponse { + status: "204"; +} + +export interface RemoveBlockItemsDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface RemoveBlockItemsDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & RemoveBlockItemsDefaultHeaders; +} + +/** The request has succeeded. */ +export interface GetTextBlocklistItem200Response extends HttpResponse { + status: "200"; + body: TextBlockItemOutput; +} + +export interface GetTextBlocklistItemDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface GetTextBlocklistItemDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & GetTextBlocklistItemDefaultHeaders; +} + +/** The request has succeeded. */ +export interface ListTextBlocklistItems200Response extends HttpResponse { + status: "200"; + body: PagedTextBlockItemOutput; +} + +export interface ListTextBlocklistItemsDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface ListTextBlocklistItemsDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & ListTextBlocklistItemsDefaultHeaders; +} diff --git a/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/tsconfig.json b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/tsconfig.json new file mode 100644 index 0000000000..d8b77d0227 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/generated/typespec-ts/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "module": "NodeNext", + "lib": ["esnext", "dom"], + "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, + "outDir": "./dist-esm", + "declarationDir": "./types", + "rootDir": "." + }, + "ts-node": { "esm": true }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/typespec-test/test/contentsafety_modular/spec/main.tsp b/packages/typespec-test/test/contentsafety_modular/spec/main.tsp new file mode 100644 index 0000000000..375bd37b15 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/spec/main.tsp @@ -0,0 +1,44 @@ +import "@typespec/http"; +import "@typespec/rest"; +import "@typespec/versioning"; +import "@azure-tools/typespec-azure-core"; +import "./routes.tsp"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; +using Azure.Core; + +@service({ + title: "ContentSafety Service", +}) +@versioned(ContentSafety.Versions) +@useAuth( + ApiKeyAuth | OAuth2Auth<[ + { + type: OAuth2FlowType.clientCredentials, + tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token", + refreshUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token", + scopes: ["https://cognitiveservices.azure.com/.default"], + } + ]> +) +@server( + "{endpoint}/contentsafety", + "Analyze harmful content", + { + @doc(""" +Supported Cognitive Services endpoints (protocol and hostname, for example: +https://.cognitiveservices.azure.com). +""") + endpoint: string, + } +) +@doc("Analyze harmful content") +namespace ContentSafety; + +#suppress "@azure-tools/typespec-azure-core/documentation-required" "https://github.com/Azure/typespec-azure/issues/3107" +enum Versions { + @useDependency(Azure.Core.Versions.v1_0_Preview_2) + v2023_10_01: "2023-10-01", +} \ No newline at end of file diff --git a/packages/typespec-test/test/contentsafety_modular/spec/models.tsp b/packages/typespec-test/test/contentsafety_modular/spec/models.tsp new file mode 100644 index 0000000000..567cb43074 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/spec/models.tsp @@ -0,0 +1,195 @@ +import "@typespec/http"; +import "@typespec/rest"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; + +namespace ContentSafety; + +#suppress "@azure-tools/typespec-azure-core/documentation-required" "MUST fix in next update" +@doc("Text analyze category") +enum TextCategory { + Hate, + SelfHarm, + Sexual, + Violence, +} + +#suppress "@azure-tools/typespec-azure-core/documentation-required" "MUST fix in next update" +@doc("Image analyze category") +enum ImageCategory { + Hate, + SelfHarm, + Sexual, + Violence, +} + +@doc("The type of text analysis output.") +enum AnalyzeTextOutputType { + @doc("Output severities in four levels, the value could be 0,2,4,6.") + FourLevels, + + @doc("Output severities in four levels, the value could be 0,1,2,3,4,5,6,7.") + EightLevels, +} + +@doc("The type of image analysis output.") +enum AnalyzeImageOutputType { + @doc("Output severities in four levels, the value could be 0,2,4,6.") + FourLevels, +} + +@doc("The analysis request of the text.") +model AnalyzeTextOptions { + // @doc("The text needs to be scanned. We support at most 1000 characters (unicode code points) in text of one request.") + // @maxLength(1000) + @doc("The text needs to be scanned. We support at most 10k Unicode characters (unicode code points) in text of one request.") + @maxLength(10000) + text: string; + + @doc("The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned.") + categories?: TextCategory[]; + + @doc("The names of blocklists.") + blocklistNames?: string[]; + + @doc("When set to true, further analyses of harmful content will not be performed in cases where blocklists are hit. When set to false, all analyses of harmful content will be performed, whether or not blocklists are hit.") + breakByBlocklists?: boolean; + + @doc("The type of text analysis output. If not assigned, the default value is \"FourLevels\".") + outputType?: AnalyzeTextOutputType = AnalyzeTextOutputType.FourLevels; +} + +@doc("The analysis response of the text") +model AnalyzeTextResult { + @doc("The details of blocklist match.") + blocklistsMatchResults?: TextBlocklistMatchResult[]; + + @doc("Analysis result for categories.") + analyzeResults: TextAnalyzeSeverityResult[]; +} + +@doc("The result of blocklist match.") +model TextBlocklistMatchResult { + @doc("The name of matched blocklist.") + @maxLength(64) + blocklistName: string; + + @doc("The id of matched item.") + @maxLength(64) + blockItemId: string; + + @doc("The content of matched item.") + @maxLength(128) + blockItemText: string; +} + +@doc("Text analysis result.") +model TextAnalyzeSeverityResult { + @doc("The text category.") + category: TextCategory; + + @doc("This field is decided by outputType in request, if choose \"FourLevels\", the value could be 0,2,4,6. The higher the severity of input content, the larger this value is.") + severity?: int32; +} + +@doc("The analysis request of the image.") +model AnalyzeImageOptions { + @doc("The image needs to be analyzed.") + image: ImageData; + + @doc("The categories will be analyzed. If not assigned, a default set of the categories' analysis results will be returned.") + categories?: ImageCategory[]; + + @doc("The type of image analysis output. If not assigned, the default value is \"FourLevels\".") + outputType?: AnalyzeImageOutputType = AnalyzeImageOutputType.FourLevels; +} + +@doc("The content or blob url of image, could be base64 encoding bytes or blob url. You can choose only one of them. If both are given, the request will be refused. The maximum size of image is 2048 pixels * 2048 pixels, no larger than 4MB at the same time. The minimum size of image is 50 pixels * 50 pixels.") +@projectedName("csharp", "ContentSafetyImageData") +model ImageData { + @doc("Base64 encoding of image.") + content?: bytes; + + @doc("The blob url of image.") + blobUrl?: url; +} + +@doc("The analysis response of the image.") +model AnalyzeImageResult { + @doc("Analysis result for categories.") + analyzeResults: ImageAnalyzeSeverityResult[]; +} + +@doc("Image analysis result.") +model ImageAnalyzeSeverityResult { + @doc("The image category.") + category: ImageCategory; + + @doc("This field is decided by outputType in request, if choose \"FourLevels\", the value could be 0,2,4,6. The higher the severity of input content, the larger this value is.") + severity?: int32; +} + +@doc("Text Blocklist.") +@resource("text/blocklists") +model TextBlocklist { + @doc("Text blocklist name.") + @pattern("^[0-9A-Za-z._~-]+$") + @key("blocklistName") + @visibility("read", "create", "query") + @maxLength(64) + blocklistName: string; + + @doc("Text blocklist description.") + @maxLength(1024) + description?: string; +} + +@doc("Item in TextBlocklist.") +@resource("blockItems") +@parentResource(TextBlocklist) +model TextBlockItem { + @doc("Block Item Id. It will be uuid.") + @key("blockItemId") + @visibility("read", "create", "query") + @maxLength(64) + blockItemId: string; + + @doc("Block item description.") + @maxLength(1024) + description?: string; + + @doc("Block item content.") + @maxLength(128) + text: string; +} + +@doc("Block item info in text blocklist.") +model TextBlockItemInfo { + @doc("Block item description.") + @maxLength(1024) + description?: string; + + @doc("Block item content.") + @maxLength(128) + text: string; +} + +@doc("The request of adding blockItems to text blocklist.") +model AddOrUpdateBlockItemsOptions { + @doc("Array of blockItemInfo to add.") + blockItems: TextBlockItemInfo[]; +} + +@doc("The response of adding blockItems to text blocklist.") +model AddOrUpdateBlockItemsResult { + @doc("Array of blockItems added.") + value?: TextBlockItem[]; +} + +@doc("The request of removing blockItems from text blocklist.") +model RemoveBlockItemsOptions { + @doc("Array of blockItemIds to remove.") + blockItemIds: string[]; +} \ No newline at end of file diff --git a/packages/typespec-test/test/contentsafety_modular/spec/routes.tsp b/packages/typespec-test/test/contentsafety_modular/spec/routes.tsp new file mode 100644 index 0000000000..5ff76b7a0d --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/spec/routes.tsp @@ -0,0 +1,92 @@ +import "@azure-tools/typespec-azure-core"; +import "@typespec/http"; +import "@typespec/rest"; +import "./models.tsp"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; +using Azure.Core; +using Azure.Core.Traits; + +namespace ContentSafety; + +interface TextOperations { + @summary("Analyze Text") + @doc("A sync API for harmful content analysis for text. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence.") + @route("/text:analyze") + @post + analyzeText is Azure.Core.RpcOperation< + { + @body + @doc("The request of text analysis.") + body: AnalyzeTextOptions; + }, + AnalyzeTextResult + >; +} + +interface ImageOperations { + @summary("Analyze Image") + @doc("A sync API for harmful content analysis for image. Currently, we support four categories: Hate, SelfHarm, Sexual, Violence.") + @route("/image:analyze") + @post + analyzeImage is Azure.Core.RpcOperation< + { + @doc("The analysis request of the image.") + @body + body: AnalyzeImageOptions; + }, + AnalyzeImageResult + >; +} + +interface BlockOps + extends Azure.Core.ResourceOperations {} + +interface TextBlocklists { + @summary("Get Text Blocklist By blocklistName") + @doc("Returns text blocklist details.") + getTextBlocklist is BlockOps.ResourceRead; + + @summary("Create Or Update Text Blocklist") + @doc("Updates a text blocklist, if blocklistName does not exist, create a new blocklist.") + createOrUpdateTextBlocklist is BlockOps.ResourceCreateOrUpdate; + + @summary("Delete Text Blocklist By blocklistName") + @doc("Deletes a text blocklist.") + deleteTextBlocklist is BlockOps.ResourceDelete; + + @summary("Get All Text Blocklists") + @doc("Get all text blocklists details.") + listTextBlocklists is BlockOps.ResourceList; + + @summary("Add or update BlockItems To Text Blocklist") + @doc("Add or update blockItems to a text blocklist. You can add or update at most 100 BlockItems in one request.") + addOrUpdateBlockItems is BlockOps.ResourceAction< + TextBlocklist, + AddOrUpdateBlockItemsOptions, + AddOrUpdateBlockItemsResult + >; + + @summary("Remove BlockItems From Text Blocklist") + @doc("Remove blockItems from a text blocklist. You can remove at most 100 BlockItems in one request.") + removeBlockItems is BlockOps.ResourceAction< + TextBlocklist, + RemoveBlockItemsOptions, + NoContentResponse + >; + + @summary("Get BlockItem By blocklistName And blockItemId") + @doc("Get blockItem By blockItemId from a text blocklist.") + getTextBlocklistItem is BlockOps.ResourceRead; + + @summary("Get All BlockItems By blocklistName") + @doc("Get all blockItems in a text blocklist") + listTextBlocklistItems is BlockOps.ResourceList< + TextBlockItem, + ListQueryParametersTrait + >; +} \ No newline at end of file diff --git a/packages/typespec-test/test/contentsafety_modular/tspconfig.yaml b/packages/typespec-test/test/contentsafety_modular/tspconfig.yaml new file mode 100644 index 0000000000..f727765703 --- /dev/null +++ b/packages/typespec-test/test/contentsafety_modular/tspconfig.yaml @@ -0,0 +1,14 @@ +emit: + - "@azure-tools/typespec-ts" +options: + "@azure-tools/typespec-ts": + title: ContentSafety Service + generateMetadata: true + generateTest: false + azureSdkForJs: false + isModularLibrary: true + "emitter-output-dir": "{project-root}/generated/typespec-ts" + packageDetails: + name: "@azure-rest/ai-content-safety" + version: "1.0.0" + description: "ContentSafety Service" \ No newline at end of file diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md index f62ebbc0a6..082189e673 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/review/load-testing.api.md @@ -122,12 +122,6 @@ export interface FileInfo { validationStatus?: FileStatus; } -// @public -export interface FileInfoList { - nextLink?: string; - value: FileInfo[]; -} - // @public export type FileStatus = string; @@ -223,8 +217,8 @@ export class LoadTestAdministrationClient { getServerMetricsConfig(testId: string, options?: GetServerMetricsConfigOptions): Promise; getTest(testId: string, options?: GetTestOptions): Promise; getTestFile(testId: string, fileName: string, options?: GetTestFileOptions): Promise; - listTestFiles(testId: string, options?: ListTestFilesOptions): Promise; - listTests(options?: ListTestsOptions): Promise; + listTestFiles(testId: string, options?: ListTestFilesOptions): Promise; + listTests(options?: ListTestsOptions): Promise; uploadTestFile(body: Uint8Array, testId: string, fileName: string, options?: UploadTestFileOptions): Promise; } @@ -254,14 +248,14 @@ export class LoadTestRunClient { getTestRunFile(testRunId: string, fileName: string, options?: GetTestRunFileOptions): Promise; // Warning: (ae-forgotten-export) The symbol "MetricDefinitionCollection" needs to be exported by the entry point index.d.ts listMetricDefinitions(testRunId: string, options?: ListMetricDefinitionsOptions): Promise; - // Warning: (ae-forgotten-export) The symbol "CustomPage" needs to be exported by the entry point index.d.ts - listMetricDimensionValues(testRunId: string, name: string, metricNamespace: string, options?: ListMetricDimensionValuesOptions): Promise; + // Warning: (ae-forgotten-export) The symbol "PagedDimensionValueList" needs to be exported by the entry point index.d.ts + listMetricDimensionValues(testRunId: string, name: string, metricNamespace: string, options?: ListMetricDimensionValuesOptions): Promise; // Warning: (ae-forgotten-export) The symbol "MetricNamespaceCollection" needs to be exported by the entry point index.d.ts listMetricNamespaces(testRunId: string, options?: ListMetricNamespacesOptions): Promise; - // Warning: (ae-forgotten-export) The symbol "Metrics" needs to be exported by the entry point index.d.ts - listMetrics(testRunId: string, options?: ListMetricsOptions): Promise; - // Warning: (ae-forgotten-export) The symbol "TestRunsList" needs to be exported by the entry point index.d.ts - listTestRuns(options?: ListTestRunsOptions): Promise; + // Warning: (ae-forgotten-export) The symbol "PagedTimeSeriesElement" needs to be exported by the entry point index.d.ts + listMetrics(testRunId: string, options?: ListMetricsOptions): Promise; + // Warning: (ae-forgotten-export) The symbol "PagedTestRun" needs to be exported by the entry point index.d.ts + listTestRuns(options?: ListTestRunsOptions): Promise; stopTestRun(testRunId: string, options?: StopTestRunOptions): Promise; testRun(testRunId: string, options?: TestRunOptions): Promise; } @@ -629,6 +623,18 @@ export interface OptionalLoadTestConfig { virtualUsers?: number; } +// @public +export interface PagedFileInfo { + nextLink?: string; + value: FileInfo[]; +} + +// @public +export interface PagedTest { + nextLink?: string; + value: Test[]; +} + // @public export interface PassFailCriteria { passFailMetrics?: Record; @@ -836,12 +842,6 @@ export interface TestServerMetricConfig { readonly testId?: string; } -// @public -export interface TestsList { - nextLink?: string; - value: Test[]; -} - // @public export type TimeGrain = string; diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts index e0dd16e7e8..cd91d5d41b 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/index.ts @@ -27,8 +27,8 @@ export { AppComponent, TestServerMetricConfig, ResourceMetric, - FileInfoList, - TestsList, + PagedFileInfo, + PagedTest, TestRun, ErrorDetails, TestRunStatistics, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/LoadTestAdministrationClient.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/LoadTestAdministrationClient.ts index f8445ad576..4fea050495 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/LoadTestAdministrationClient.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/LoadTestAdministrationClient.ts @@ -8,8 +8,8 @@ import { TestAppComponents, AppComponent, TestServerMetricConfig, - FileInfoList, - TestsList, + PagedFileInfo, + PagedTest, } from "./models/models.js"; import { CreateOrUpdateTestOptions, @@ -123,7 +123,7 @@ export class LoadTestAdministrationClient { listTestFiles( testId: string, options: ListTestFilesOptions = { requestOptions: {} } - ): Promise { + ): Promise { return listTestFiles(this._client, testId, options); } @@ -133,7 +133,7 @@ export class LoadTestAdministrationClient { */ listTests( options: ListTestsOptions = { requestOptions: {} } - ): Promise { + ): Promise { return listTests(this._client, options); } diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/api/operations.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/api/operations.ts index 521b928756..1c1a4e0033 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/api/operations.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/api/operations.ts @@ -7,8 +7,8 @@ import { TestAppComponents, AppComponent, TestServerMetricConfig, - FileInfoList, - TestsList, + PagedFileInfo, + PagedTest, } from "../models/models.js"; import { isUnexpected, @@ -747,7 +747,7 @@ export async function _listTestFilesDeserialize( result: | LoadTestAdministrationListTestFiles200Response | LoadTestAdministrationListTestFilesDefaultResponse -): Promise { +): Promise { if (isUnexpected(result)) { throw result.body; } @@ -770,7 +770,7 @@ export async function listTestFiles( context: Client, testId: string, options: ListTestFilesOptions = { requestOptions: {} } -): Promise { +): Promise { const result = await _listTestFilesSend(context, testId, options); return _listTestFilesDeserialize(result); } @@ -800,7 +800,7 @@ export async function _listTestsDeserialize( result: | LoadTestAdministrationListTests200Response | LoadTestAdministrationListTestsDefaultResponse -): Promise { +): Promise { if (isUnexpected(result)) { throw result.body; } @@ -950,7 +950,7 @@ export async function _listTestsDeserialize( export async function listTests( context: Client, options: ListTestsOptions = { requestOptions: {} } -): Promise { +): Promise { const result = await _listTestsSend(context, options); return _listTestsDeserialize(result); } diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/index.ts index 87f2134fed..34faaccfef 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/index.ts @@ -27,8 +27,8 @@ export { AppComponent, TestServerMetricConfig, ResourceMetric, - FileInfoList, - TestsList, + PagedFileInfo, + PagedTest, TestRun, ErrorDetails, TestRunStatistics, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/index.ts index 8b50e0e216..76273f1843 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/index.ts @@ -23,8 +23,8 @@ export { AppComponent, TestServerMetricConfig, ResourceMetric, - FileInfoList, - TestsList, + PagedFileInfo, + PagedTest, TestRun, ErrorDetails, TestRunStatistics, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/models.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/models.ts index 73739a0e41..adc1c2c27a 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/models.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestAdministration/models/models.ts @@ -272,7 +272,7 @@ export interface ResourceMetric { } /** Collection of files. */ -export interface FileInfoList { +export interface PagedFileInfo { /** The FileInfo items on this page */ value: FileInfo[]; /** The link to the next page of items */ @@ -280,7 +280,7 @@ export interface FileInfoList { } /** Collection of tests */ -export interface TestsList { +export interface PagedTest { /** The Test items on this page */ value: Test[]; /** The link to the next page of items */ diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/LoadTestRunClient.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/LoadTestRunClient.ts index 5a527f05d1..5682157ae5 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/LoadTestRunClient.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/LoadTestRunClient.ts @@ -10,9 +10,9 @@ import { TestRunServerMetricConfig, MetricDefinitionCollection, MetricNamespaceCollection, - Metrics, - TestRunsList, - CustomPage, + PagedTimeSeriesElement, + PagedTestRun, + PagedDimensionValueList, } from "./models/models.js"; import { TestRunOptions, @@ -143,7 +143,7 @@ export class LoadTestRunClient { name: string, metricNamespace: string, options: ListMetricDimensionValuesOptions = { requestOptions: {} } - ): Promise { + ): Promise { return listMetricDimensionValues( this._client, testRunId, @@ -173,14 +173,14 @@ export class LoadTestRunClient { listMetrics( testRunId: string, options: ListMetricsOptions = { requestOptions: {} } - ): Promise { + ): Promise { return listMetrics(this._client, testRunId, options); } /** Get all test runs with given filters */ listTestRuns( options: ListTestRunsOptions = { requestOptions: {} } - ): Promise { + ): Promise { return listTestRuns(this._client, options); } diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/api/operations.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/api/operations.ts index ca9151a566..c6b9f45b38 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/api/operations.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/api/operations.ts @@ -9,9 +9,9 @@ import { TestRunServerMetricConfig, MetricDefinitionCollection, MetricNamespaceCollection, - Metrics, - TestRunsList, - CustomPage, + PagedTimeSeriesElement, + PagedTestRun, + PagedDimensionValueList, } from "../models/models.js"; import { isUnexpected, @@ -941,7 +941,7 @@ export async function _listMetricDimensionValuesDeserialize( result: | LoadTestRunListMetricDimensionValues200Response | LoadTestRunListMetricDimensionValuesDefaultResponse -): Promise { +): Promise { if (isUnexpected(result)) { throw result.body; } @@ -959,7 +959,7 @@ export async function listMetricDimensionValues( name: string, metricNamespace: string, options: ListMetricDimensionValuesOptions = { requestOptions: {} } -): Promise { +): Promise { const result = await _listMetricDimensionValuesSend( context, testRunId, @@ -1090,7 +1090,7 @@ export async function _listMetricsDeserialize( result: | LoadTestRunListMetrics200Response | LoadTestRunListMetricsDefaultResponse -): Promise { +): Promise { if (isUnexpected(result)) { throw result.body; } @@ -1115,7 +1115,7 @@ export async function listMetrics( context: Client, testRunId: string, options: ListMetricsOptions = { requestOptions: {} } -): Promise { +): Promise { const result = await _listMetricsSend(context, testRunId, options); return _listMetricsDeserialize(result); } @@ -1146,7 +1146,7 @@ export async function _listTestRunsDeserialize( result: | LoadTestRunListTestRuns200Response | LoadTestRunListTestRunsDefaultResponse -): Promise { +): Promise { if (isUnexpected(result)) { throw result.body; } @@ -1407,7 +1407,7 @@ export async function _listTestRunsDeserialize( export async function listTestRuns( context: Client, options: ListTestRunsOptions = { requestOptions: {} } -): Promise { +): Promise { const result = await _listTestRunsSend(context, options); return _listTestRunsDeserialize(result); } diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/index.ts index 45eca88b04..c439cfff47 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/index.ts @@ -45,13 +45,13 @@ export { MetricNamespaceCollection, MetricNamespace, DimensionFilter, - Metrics, + PagedTimeSeriesElement, TimeSeriesElement, MetricValue, DimensionValue, - TestRunsList, + PagedTestRun, Interval, - CustomPage, + PagedDimensionValueList, DimensionValueList, TestRunOptions, CreateOrUpdateAppComponentsOptions, diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/index.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/index.ts index 20717ea4af..9edc074687 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/index.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/index.ts @@ -41,13 +41,13 @@ export { MetricNamespaceCollection, MetricNamespace, DimensionFilter, - Metrics, + PagedTimeSeriesElement, TimeSeriesElement, MetricValue, DimensionValue, - TestRunsList, + PagedTestRun, Interval, - CustomPage, + PagedDimensionValueList, DimensionValueList, } from "./models.js"; export { diff --git a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/models.ts b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/models.ts index c8384eb4ba..753b02bd58 100644 --- a/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/models.ts +++ b/packages/typespec-test/test/loadtesting_modular/generated/typespec-ts/src/loadTestRun/models/models.ts @@ -481,7 +481,7 @@ export interface DimensionFilter { } /** The response to a metrics query. */ -export interface Metrics { +export interface PagedTimeSeriesElement { /** The TimeSeriesElement items on this page */ value: TimeSeriesElement[]; /** The link to the next page of items */ @@ -513,7 +513,7 @@ export interface DimensionValue { } /** Collection of test runs */ -export interface TestRunsList { +export interface PagedTestRun { /** The TestRun items on this page */ value: TestRun[]; /** The link to the next page of items */ @@ -524,7 +524,7 @@ export interface TestRunsList { export type Interval = string; /** Paged collection of DimensionValueList items */ -export interface CustomPage { +export interface PagedDimensionValueList { /** The DimensionValueList items on this page */ value: DimensionValueList[]; /** The link to the next page of items */ diff --git a/packages/typespec-ts/src/modular/buildCodeModel.ts b/packages/typespec-ts/src/modular/buildCodeModel.ts index 2747b61f7c..a702876b29 100644 --- a/packages/typespec-ts/src/modular/buildCodeModel.ts +++ b/packages/typespec-ts/src/modular/buildCodeModel.ts @@ -845,6 +845,10 @@ function emitProperty( clientDefaultValue = property.default.value; } + if (propertyDefaultKind === "EnumMember") { + clientDefaultValue = property.default.value ?? property.default.name; + } + // const [clientName, jsonName] = getPropertyNames(context, property); const clientName = property.name; const jsonName = @@ -896,9 +900,35 @@ function emitModel(context: SdkContext, type: Model): Record { baseModel = getType(context, type.baseModel); } const effectiveName = getEffectiveSchemaType(context.program, type).name; - const modelName = effectiveName - ? effectiveName - : getName(context.program, type); + const overridedModelName = + getProjectedName(context.program, type, "javascript") ?? + getProjectedName(context.program, type, "client") ?? + getFriendlyName(context.program, type); + let modelName = + overridedModelName ?? + (effectiveName ? effectiveName : getName(context.program, type)); + if ( + !overridedModelName && + type.templateMapper && + type.templateMapper.args && + type.templateMapper.args.length > 0 && + getPagedResult(context.program, type) + ) { + modelName = + type.templateMapper.args + .map((it) => { + switch (it.kind) { + case "Model": + return it.name; + case "String": + return it.value; + default: + return ""; + } + }) + .join("") + "List"; + } + return { type: "model", name: modelName, diff --git a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts index 02ede2cae0..f75ec0573b 100644 --- a/packages/typespec-ts/src/modular/helpers/operationHelpers.ts +++ b/packages/typespec-ts/src/modular/helpers/operationHelpers.ts @@ -261,9 +261,13 @@ export function getOperationFunction( } export function getOperationOptionsName(operation: Operation) { - return `${toPascalCase(operation.groupName)}${toPascalCase( + const optionName = `${toPascalCase(operation.groupName)}${toPascalCase( operation.name )}Options`; + if (operation.bodyParameter?.type.name === optionName) { + return optionName.replace(/Options$/, "RequestOptions"); + } + return optionName; } /** @@ -338,14 +342,16 @@ function buildBodyParameter( if (bodyParameter.type.type === "model") { const bodyParts: string[] = []; - let hasSerializeBody = false; for (const param of bodyParameter?.type.properties?.filter( (p) => !p.readonly ) ?? []) { if (param.type.type === "model" && isRequired(param)) { - hasSerializeBody = true; bodyParts.push( - ...getRequestModelMapping(param.type, param.clientName, importSet) + `"${param.restApiName}": {${getRequestModelMapping( + param.type, + param.clientName, + importSet + ).join(",\n")}}` ); } else { bodyParts.push(getParameterMap(param, importSet)); @@ -353,13 +359,7 @@ function buildBodyParameter( } if (bodyParameter && bodyParameter.type.properties) { - if (hasSerializeBody) { - return `\nbody: {"${bodyParameter.restApiName}": {${bodyParts.join( - ",\n" - )}}},`; - } else { - return `\nbody: {${bodyParts.join(",\n")}},`; - } + return `\nbody: {${bodyParts.join(",\n")}},`; } } @@ -395,7 +395,7 @@ function getParameterMap( return getConstantValue(param); } - // if the parameter or property is optional, we don't need to handle the + // if the parameter or property is optional, we don't need to handle the default value if (isOptional(param)) { return getOptional(param, importSet); } diff --git a/packages/typespec-ts/src/utils/modelUtils.ts b/packages/typespec-ts/src/utils/modelUtils.ts index 07f53e9d23..382686937f 100644 --- a/packages/typespec-ts/src/utils/modelUtils.ts +++ b/packages/typespec-ts/src/utils/modelUtils.ts @@ -420,9 +420,7 @@ function getSchemaForModel( ) { const program = dpgContext.program; const overridedModelName = - getProjectedName(program, model, "javascript") ?? - getProjectedName(program, model, "client") ?? - getFriendlyName(program, model); + getFriendlyName(program, model) ?? getProjectedName(program, model, "json"); let name = model.name; if ( !overridedModelName && diff --git a/packages/typespec-ts/test/unit/modelsGenerator.spec.ts b/packages/typespec-ts/test/unit/modelsGenerator.spec.ts index 6181e75644..5465a3c93b 100644 --- a/packages/typespec-ts/test/unit/modelsGenerator.spec.ts +++ b/packages/typespec-ts/test/unit/modelsGenerator.spec.ts @@ -1629,7 +1629,7 @@ describe("Input/output model type", () => { }); }); - it("should generate projected model name over friendly name", async () => { + it("should generate friendly name over projected model name", async () => { const cadlDefinition = ` @projectedName("javascript", "CustomProjectedModelTS") @projectedName("json", "CustomProjectedModel") @@ -1640,18 +1640,44 @@ describe("Input/output model type", () => { } `; const cadlType = "FooModel"; - const inputModelName = "CustomProjectedModelTS"; + const inputModelName = "CustomFriendlyModel"; await verifyPropertyType(cadlType, inputModelName, { additionalCadlDefinition: cadlDefinition, - outputType: `CustomProjectedModelTSOutput`, + outputType: `CustomFriendlyModelOutput`, additionalInputContent: ` /** This is a Foo model. */ - export interface CustomProjectedModelTS { + export interface CustomFriendlyModel { x: number; }`, additionalOutputContent: ` /** This is a Foo model. */ - export interface CustomProjectedModelTSOutput { + export interface CustomFriendlyModelOutput { + x: number; + }` + }); + }); + + it("should ignore projected javascript model name", async () => { + const cadlDefinition = ` + @projectedName("javascript", "CustomProjectedModelTS") + @doc("This is a Foo model.") + model FooModel { + x: int32; + } + `; + const cadlType = "FooModel"; + const inputModelName = "FooModel"; + await verifyPropertyType(cadlType, inputModelName, { + additionalCadlDefinition: cadlDefinition, + outputType: `FooModelOutput`, + additionalInputContent: ` + /** This is a Foo model. */ + export interface FooModel { + x: number; + }`, + additionalOutputContent: ` + /** This is a Foo model. */ + export interface FooModelOutput { x: number; }` });