diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/.eslintrc.json b/sdk/contentsafety/azure-ai-content-safety-rest/.eslintrc.json new file mode 100644 index 000000000000..619797ac39b6 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/.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/sdk/contentsafety/azure-ai-content-safety-rest/README.md b/sdk/contentsafety/azure-ai-content-safety-rest/README.md new file mode 100644 index 000000000000..5731be66256f --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/README.md @@ -0,0 +1,59 @@ +# 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: + +- [Source code](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/contentsafety/azure-ai-content-safety-rest) +- [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) +- [Samples](https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/contentsafety/azure-ai-content-safety-rest/samples) + +## 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/sdk/contentsafety/azure-ai-content-safety-rest/api-extractor.json b/sdk/contentsafety/azure-ai-content-safety-rest/api-extractor.json new file mode 100644 index 000000000000..13a4abc8baf1 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/api-extractor.json @@ -0,0 +1,31 @@ +{ + "$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" + } + } + } +} \ No newline at end of file diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/karma.conf.js b/sdk/contentsafety/azure-ai-content-safety-rest/karma.conf.js new file mode 100644 index 000000000000..a9d5f1b5fc59 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/karma.conf.js @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +// https://github.com/karma-runner/karma-chrome-launcher +process.env.CHROME_BIN = require("puppeteer").executablePath(); +require("dotenv").config(); +const { relativeRecordingsPath } = require("@azure-tools/test-recorder"); +process.env.RECORDINGS_RELATIVE_PATH = relativeRecordingsPath(); + +module.exports = function (config) { + config.set({ + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: "./", + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ["source-map-support", "mocha"], + + plugins: [ + "karma-mocha", + "karma-mocha-reporter", + "karma-chrome-launcher", + "karma-firefox-launcher", + "karma-env-preprocessor", + "karma-coverage", + "karma-sourcemap-loader", + "karma-junit-reporter", + "karma-source-map-support", + ], + + // list of files / patterns to load in the browser + files: [ + "dist-test/index.browser.js", + { + pattern: "dist-test/index.browser.js.map", + type: "html", + included: false, + served: true, + }, + ], + + // list of files / patterns to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + "**/*.js": ["sourcemap", "env"], + // IMPORTANT: COMMENT following line if you want to debug in your browsers!! + // Preprocess source file to calculate code coverage, however this will make source file unreadable + // "dist-test/index.js": ["coverage"] + }, + + envPreprocessor: [ + "TEST_MODE", + "ENDPOINT", + "AZURE_CLIENT_SECRET", + "AZURE_CLIENT_ID", + "AZURE_TENANT_ID", + "SUBSCRIPTION_ID", + "RECORDINGS_RELATIVE_PATH", + ], + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ["mocha", "coverage", "junit"], + + coverageReporter: { + // specify a common output directory + dir: "coverage-browser/", + reporters: [ + { type: "json", subdir: ".", file: "coverage.json" }, + { type: "lcovonly", subdir: ".", file: "lcov.info" }, + { type: "html", subdir: "html" }, + { type: "cobertura", subdir: ".", file: "cobertura-coverage.xml" }, + ], + }, + + junitReporter: { + outputDir: "", // results will be saved as $outputDir/$browserName.xml + outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile + suite: "", // suite will become the package name attribute in xml testsuite element + useBrowserName: false, // add browser name to report and classes names + nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element + classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element + properties: {}, // key value pair of properties to add to the section of the report + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: false, + + // --no-sandbox allows our tests to run in Linux without having to change the system. + // --disable-web-security allows us to authenticate from the browser without having to write tests using interactive auth, which would be far more complex. + browsers: ["ChromeHeadlessNoSandbox"], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: "ChromeHeadless", + flags: ["--no-sandbox", "--disable-web-security"], + }, + }, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Concurrency level + // how many browser should be started simultaneous + concurrency: 1, + + browserNoActivityTimeout: 60000000, + browserDisconnectTimeout: 10000, + browserDisconnectTolerance: 3, + + client: { + mocha: { + // change Karma's debug.html to the mocha web reporter + reporter: "html", + timeout: "600000", + }, + }, + }); +}; diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/package.json b/sdk/contentsafety/azure-ai-content-safety-rest/package.json new file mode 100644 index 000000000000..b9cce71528ff --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/package.json @@ -0,0 +1,119 @@ +{ + "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", + "main": "dist/index.js", + "module": "./dist-esm/src/index.js", + "types": "./types/ai-content-safety.d.ts", + "repository": "github:Azure/azure-sdk-for-js", + "bugs": { + "url": "https://github.com/Azure/azure-sdk-for-js/issues" + }, + "files": [ + "dist/", + "dist-esm/src/", + "types/ai-content-safety.d.ts", + "README.md", + "LICENSE", + "review/*" + ], + "engines": { + "node": ">=16.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": "tsc -p . && cross-env ONLY_BROWSER=true rollup -c 2>&1", + "build:node": "tsc -p . && cross-env ONLY_NODE=true rollup -c 2>&1", + "build:samples": "echo skipped.", + "build:test": "tsc -p . && dev-tool run bundle", + "build:debug": "tsc -p . && dev-tool run bundle && api-extractor run --local", + "check-format": "prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.ts\" \"*.{js,json}\" \"test/**/*.ts\"", + "clean": "rimraf --glob 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}\" \"test/**/*.ts\"", + "generate:client": "echo skipped", + "integration-test:browser": "dev-tool run test:browser", + "integration-test:node": "dev-tool run test:node-js-input -- --timeout 5000000 'dist-esm/test/**/*.spec.js'", + "integration-test": "npm run integration-test:node && npm run integration-test:browser", + "lint:fix": "eslint package.json api-extractor.json src test --ext .ts --fix --fix-type [problem,suggestion]", + "lint": "eslint package.json api-extractor.json src test --ext .ts", + "pack": "npm pack 2>&1", + "test:browser": "npm run clean && npm run build:test && npm run unit-test:browser", + "test:node": "npm run clean && npm run build:test && npm run unit-test:node", + "test": "npm run clean && npm run build:test && npm run unit-test", + "unit-test": "npm run unit-test:node && npm run unit-test:browser", + "unit-test:node": "dev-tool run test:node-ts-input -- --timeout 1200000 --exclude 'test/**/browser/*.spec.ts' 'test/**/*.spec.ts'", + "unit-test:browser": "dev-tool run test:browser", + "build": "npm run clean && tsc -p . && dev-tool run bundle && mkdirp ./review && api-extractor run --local" + }, + "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" + }, + "devDependencies": { + "@microsoft/api-extractor": "^7.31.1", + "autorest": "latest", + "@types/node": "^16.0.0", + "dotenv": "^16.0.0", + "eslint": "^8.0.0", + "mkdirp": "^2.1.2", + "prettier": "^2.5.1", + "rimraf": "^5.0.0", + "source-map-support": "^0.5.9", + "typescript": "~5.0.0", + "@azure/dev-tool": "^1.0.0", + "@azure/eslint-plugin-azure-sdk": "^3.0.0", + "@azure-tools/test-credential": "^1.0.0", + "@azure/identity": "^3.3.0", + "@azure-tools/test-recorder": "^3.0.0", + "mocha": "^10.0.0", + "esm": "^3.2.18", + "@types/mocha": "^10.0.0", + "mocha-junit-reporter": "^1.18.0", + "cross-env": "^7.0.2", + "@types/chai": "^4.2.8", + "chai": "^4.2.0", + "karma-chrome-launcher": "^3.0.0", + "karma-coverage": "^2.0.0", + "karma-env-preprocessor": "^0.1.1", + "karma-firefox-launcher": "^2.1.2", + "karma-junit-reporter": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-mocha": "^2.0.1", + "karma-source-map-support": "~1.4.0", + "karma-sourcemap-loader": "^0.4.0", + "karma": "^6.2.0", + "nyc": "^15.0.0", + "ts-node": "^10.0.0" + }, + "homepage": "https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/contentsafety/azure-ai-content-safety-rest/README.md", + "//metadata": { + "constantPaths": [ + { + "path": "src/contentSafetyClient.ts", + "prefix": "userAgentInfo" + } + ] + }, + "browser": { + "./dist-esm/test/public/utils/env.js": "./dist-esm/test/public/utils/env.browser.js" + } +} \ No newline at end of file diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/src/clientDefinitions.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/clientDefinitions.ts new file mode 100644 index 000000000000..60b18d6331e7 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/clientDefinitions.ts @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AnalyzeTextParameters, + AnalyzeImageParameters, + GetTextBlocklistParameters, + CreateOrUpdateTextBlocklistParameters, + DeleteTextBlocklistParameters, + ListTextBlocklistsParameters, + AddOrUpdateBlocklistItemsParameters, + RemoveBlocklistItemsParameters, + GetTextBlocklistItemParameters, + ListTextBlocklistItemsParameters, +} from "./parameters"; +import { + AnalyzeText200Response, + AnalyzeTextDefaultResponse, + AnalyzeImage200Response, + AnalyzeImageDefaultResponse, + GetTextBlocklist200Response, + GetTextBlocklistDefaultResponse, + CreateOrUpdateTextBlocklist200Response, + CreateOrUpdateTextBlocklist201Response, + CreateOrUpdateTextBlocklistDefaultResponse, + DeleteTextBlocklist204Response, + DeleteTextBlocklistDefaultResponse, + ListTextBlocklists200Response, + ListTextBlocklistsDefaultResponse, + AddOrUpdateBlocklistItems200Response, + AddOrUpdateBlocklistItemsDefaultResponse, + RemoveBlocklistItems204Response, + RemoveBlocklistItemsDefaultResponse, + GetTextBlocklistItem200Response, + GetTextBlocklistItemDefaultResponse, + ListTextBlocklistItems200Response, + ListTextBlocklistItemsDefaultResponse, +} from "./responses"; +import { Client, StreamableMethod } from "@azure-rest/core-client"; + +export interface AnalyzeText { + /** A synchronous API for the analysis of potentially harmful text content. Currently, it supports four categories: Hate, SelfHarm, Sexual, and Violence. */ + post( + options: AnalyzeTextParameters + ): StreamableMethod; +} + +export interface AnalyzeImage { + /** A synchronous API for the analysis of potentially harmful image content. Currently, it supports four categories: Hate, SelfHarm, Sexual, and Violence. */ + post( + options: AnalyzeImageParameters + ): StreamableMethod; +} + +export interface GetTextBlocklist { + /** Returns text blocklist details. */ + get( + options?: GetTextBlocklistParameters + ): StreamableMethod< + GetTextBlocklist200Response | GetTextBlocklistDefaultResponse + >; + /** Updates a text blocklist. If the blocklistName does not exist, a new blocklist will be created. */ + 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 AddOrUpdateBlocklistItems { + /** Add or update blocklistItems to a text blocklist. You can add or update at most 100 blocklistItems in one request. */ + post( + options?: AddOrUpdateBlocklistItemsParameters + ): StreamableMethod< + | AddOrUpdateBlocklistItems200Response + | AddOrUpdateBlocklistItemsDefaultResponse + >; +} + +export interface RemoveBlocklistItems { + /** Remove blocklistItems from a text blocklist. You can remove at most 100 BlocklistItems in one request. */ + post( + options?: RemoveBlocklistItemsParameters + ): StreamableMethod< + RemoveBlocklistItems204Response | RemoveBlocklistItemsDefaultResponse + >; +} + +export interface GetTextBlocklistItem { + /** Get blocklistItem by blocklistName and blocklistItemId from a text blocklist. */ + get( + options?: GetTextBlocklistItemParameters + ): StreamableMethod< + GetTextBlocklistItem200Response | GetTextBlocklistItemDefaultResponse + >; +} + +export interface ListTextBlocklistItems { + /** Get all blocklistItems 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\}:addOrUpdateBlocklistItems' has methods for the following verbs: post */ + ( + path: "/text/blocklists/{blocklistName}:addOrUpdateBlocklistItems", + blocklistName: string + ): AddOrUpdateBlocklistItems; + /** Resource for '/text/blocklists/\{blocklistName\}:removeBlocklistItems' has methods for the following verbs: post */ + ( + path: "/text/blocklists/{blocklistName}:removeBlocklistItems", + blocklistName: string + ): RemoveBlocklistItems; + /** Resource for '/text/blocklists/\{blocklistName\}/blocklistItems/\{blocklistItemId\}' has methods for the following verbs: get */ + ( + path: "/text/blocklists/{blocklistName}/blocklistItems/{blocklistItemId}", + blocklistName: string, + blocklistItemId: string + ): GetTextBlocklistItem; + /** Resource for '/text/blocklists/\{blocklistName\}/blocklistItems' has methods for the following verbs: get */ + ( + path: "/text/blocklists/{blocklistName}/blocklistItems", + blocklistName: string + ): ListTextBlocklistItems; +} + +export type ContentSafetyClient = Client & { + path: Routes; +}; diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/src/contentSafetyClient.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/contentSafetyClient.ts new file mode 100644 index 000000000000..c236b3829d7a --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/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"; +import { TokenCredential, KeyCredential } from "@azure/core-auth"; +import { ContentSafetyClient } from "./clientDefinitions"; + +/** + * Initialize a new instance of `ContentSafetyClient` + * @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 = {} +): ContentSafetyClient { + 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 ContentSafetyClient; + + return client; +} diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/src/index.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/index.ts new file mode 100644 index 000000000000..566927563535 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/index.ts @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import ContentSafetyClient from "./contentSafetyClient"; + +export * from "./contentSafetyClient"; +export * from "./parameters"; +export * from "./responses"; +export * from "./clientDefinitions"; +export * from "./isUnexpected"; +export * from "./models"; +export * from "./outputModels"; +export * from "./paginateHelper"; + +export default ContentSafetyClient; diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/src/isUnexpected.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/isUnexpected.ts new file mode 100644 index 000000000000..9cf7564754bc --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/isUnexpected.ts @@ -0,0 +1,199 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { + AnalyzeText200Response, + AnalyzeTextDefaultResponse, + AnalyzeImage200Response, + AnalyzeImageDefaultResponse, + GetTextBlocklist200Response, + GetTextBlocklistDefaultResponse, + CreateOrUpdateTextBlocklist200Response, + CreateOrUpdateTextBlocklist201Response, + CreateOrUpdateTextBlocklistDefaultResponse, + DeleteTextBlocklist204Response, + DeleteTextBlocklistDefaultResponse, + ListTextBlocklists200Response, + ListTextBlocklistsDefaultResponse, + AddOrUpdateBlocklistItems200Response, + AddOrUpdateBlocklistItemsDefaultResponse, + RemoveBlocklistItems204Response, + RemoveBlocklistItemsDefaultResponse, + GetTextBlocklistItem200Response, + GetTextBlocklistItemDefaultResponse, + ListTextBlocklistItems200Response, + ListTextBlocklistItemsDefaultResponse, +} from "./responses"; + +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}:addOrUpdateBlocklistItems": ["200"], + "POST /text/blocklists/{blocklistName}:removeBlocklistItems": ["204"], + "GET /text/blocklists/{blocklistName}/blocklistItems/{blocklistItemId}": [ + "200", + ], + "GET /text/blocklists/{blocklistName}/blocklistItems": ["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: + | AddOrUpdateBlocklistItems200Response + | AddOrUpdateBlocklistItemsDefaultResponse +): response is AddOrUpdateBlocklistItemsDefaultResponse; +export function isUnexpected( + response: + | RemoveBlocklistItems204Response + | RemoveBlocklistItemsDefaultResponse +): response is RemoveBlocklistItemsDefaultResponse; +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 + | AddOrUpdateBlocklistItems200Response + | AddOrUpdateBlocklistItemsDefaultResponse + | RemoveBlocklistItems204Response + | RemoveBlocklistItemsDefaultResponse + | GetTextBlocklistItem200Response + | GetTextBlocklistItemDefaultResponse + | ListTextBlocklistItems200Response + | ListTextBlocklistItemsDefaultResponse +): response is + | AnalyzeTextDefaultResponse + | AnalyzeImageDefaultResponse + | GetTextBlocklistDefaultResponse + | CreateOrUpdateTextBlocklistDefaultResponse + | DeleteTextBlocklistDefaultResponse + | ListTextBlocklistsDefaultResponse + | AddOrUpdateBlocklistItemsDefaultResponse + | RemoveBlocklistItemsDefaultResponse + | 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/sdk/contentsafety/azure-ai-content-safety-rest/src/logger.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/logger.ts new file mode 100644 index 000000000000..8236c0444b12 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/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/sdk/contentsafety/azure-ai-content-safety-rest/src/models.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/models.ts new file mode 100644 index 000000000000..f3368faf30b4 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/models.ts @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/** The text analysis request. */ +export interface AnalyzeTextOptions { + /** The text needs to be analyzed. We support a maximum of 10k Unicode characters (Unicode code points) in the text of one request. */ + text: string; + /** The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories 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. */ + haltOnBlocklistHit?: boolean; + /** + * This refers to the type of text analysis output. If no value is assigned, the default value will be "FourSeverityLevels". + * + * Possible values: FourSeverityLevels, EightSeverityLevels + */ + outputType?: string; +} + +/** The image analysis request. */ +export interface AnalyzeImageOptions { + /** The image needs to be analyzed. */ + image: ImageData; + /** The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories will be returned. */ + categories?: string[]; + /** + * This refers to the type of image analysis output. If no value is assigned, the default value will be "FourSeverityLevels". + * + * Possible values: FourSeverityLevels + */ + outputType?: string; +} + +/** The image can be either base64 encoded bytes or a blob URL. You can choose only one of these options. If both are provided, the request will be refused. The maximum image size is 2048 x 2048 pixels and should not exceed 4 MB, while the minimum image size is 50 x 50 pixels. */ +export interface ImageData { + /** The Base64 encoding of the image. */ + content?: string; + /** The blob url of the image. */ + blobUrl?: string; +} + +/** Text Blocklist. */ +export interface TextBlocklist { + /** Text blocklist name. */ + blocklistName: string; + /** Text blocklist description. */ + description?: string; +} + +/** The request to add blocklistItems to a text blocklist. */ +export interface AddOrUpdateTextBlocklistItemsOptions { + /** Array of blocklistItems to add. */ + blocklistItems: Array; +} + +/** Item in a TextBlocklist. */ +export interface TextBlocklistItem { + /** BlocklistItem description. */ + description?: string; + /** BlocklistItem content. */ + text: string; +} + +/** The request to remove blocklistItems from a text blocklist. */ +export interface RemoveTextBlocklistItemsOptions { + /** Array of blocklistItemIds to remove. */ + blocklistItemIds: string[]; +} diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/src/outputModels.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/outputModels.ts new file mode 100644 index 000000000000..ac215a7b18fa --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/outputModels.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { Paged } from "@azure/core-paging"; + +/** The text analysis request. */ +export interface AnalyzeTextOptionsOutput { + /** The text needs to be analyzed. We support a maximum of 10k Unicode characters (Unicode code points) in the text of one request. */ + text: string; + /** The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories 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. */ + haltOnBlocklistHit?: boolean; + /** + * This refers to the type of text analysis output. If no value is assigned, the default value will be "FourSeverityLevels". + * + * Possible values: FourSeverityLevels, EightSeverityLevels + */ + outputType?: string; +} + +/** The text analysis response. */ +export interface AnalyzeTextResultOutput { + /** The blocklist match details. */ + blocklistsMatch?: Array; + /** Analysis result for categories. */ + categoriesAnalysis: Array; +} + +/** The result of blocklist match. */ +export interface TextBlocklistMatchOutput { + /** The name of the matched blocklist. */ + blocklistName: string; + /** The ID of the matched item. */ + blocklistItemId: string; + /** The content of the matched item. */ + blocklistItemText: string; +} + +/** Text analysis result. */ +export interface TextCategoriesAnalysisOutput { + /** + * The text analysis category. + * + * Possible values: Hate, SelfHarm, Sexual, Violence + */ + category: string; + /** The value increases with the severity of the input content. The value of this field is determined by the output type specified in the request. The output type could be ‘FourSeverityLevels’ or ‘EightSeverity Levels’, and the output value can be 0, 2, 4, 6 or 0, 1, 2, 3, 4, 5, 6, or 7. */ + severity?: number; +} + +/** The image analysis request. */ +export interface AnalyzeImageOptionsOutput { + /** The image needs to be analyzed. */ + image: ImageDataOutput; + /** The categories will be analyzed. If they are not assigned, a default set of analysis results for the categories will be returned. */ + categories?: string[]; + /** + * This refers to the type of image analysis output. If no value is assigned, the default value will be "FourSeverityLevels". + * + * Possible values: FourSeverityLevels + */ + outputType?: string; +} + +/** The image can be either base64 encoded bytes or a blob URL. You can choose only one of these options. If both are provided, the request will be refused. The maximum image size is 2048 x 2048 pixels and should not exceed 4 MB, while the minimum image size is 50 x 50 pixels. */ +export interface ImageDataOutput { + /** The Base64 encoding of the image. */ + content?: string; + /** The blob url of the image. */ + blobUrl?: string; +} + +/** The image analysis response. */ +export interface AnalyzeImageResultOutput { + /** Analysis result for categories. */ + categoriesAnalysis: Array; +} + +/** Image analysis result. */ +export interface ImageCategoriesAnalysisOutput { + /** + * The image analysis category. + * + * Possible values: Hate, SelfHarm, Sexual, Violence + */ + category: string; + /** The value increases with the severity of the input content. The value of this field is determined by the output type specified in the request. The output type could be ‘FourSeverityLevels’, and the output value can be 0, 2, 4, 6. */ + severity?: number; +} + +/** Text Blocklist. */ +export interface TextBlocklistOutput { + /** Text blocklist name. */ + blocklistName: string; + /** Text blocklist description. */ + description?: string; +} + +/** Item in a TextBlocklist. */ +export interface TextBlocklistItemOutput { + /** The service will generate a BlocklistItemId, which will be a UUID. */ + readonly blocklistItemId: string; + /** BlocklistItem description. */ + description?: string; + /** BlocklistItem content. */ + text: string; +} + +/** The response of adding blocklistItems to the text blocklist. */ +export interface AddOrUpdateTextBlocklistItemsResultOutput { + /** Array of blocklistItems have been added. */ + blocklistItems: Array; +} + +/** Paged collection of TextBlocklist items */ +export type PagedTextBlocklistOutput = Paged; +/** Paged collection of TextBlocklistItem items */ +export type PagedTextBlocklistItemOutput = Paged; diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/src/paginateHelper.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/paginateHelper.ts new file mode 100644 index 000000000000..1c9af35b1efd --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/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/sdk/contentsafety/azure-ai-content-safety-rest/src/parameters.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/parameters.ts new file mode 100644 index 000000000000..d4c750389ada --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/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, + AddOrUpdateTextBlocklistItemsOptions, + RemoveTextBlocklistItemsOptions, +} from "./models"; + +export interface AnalyzeTextBodyParam { + /** The text analysis request. */ + body: AnalyzeTextOptions; +} + +export type AnalyzeTextParameters = AnalyzeTextBodyParam & RequestParameters; + +export interface AnalyzeImageBodyParam { + /** The image analysis request. */ + 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 AddOrUpdateBlocklistItemsBodyParam { + body?: AddOrUpdateTextBlocklistItemsOptions; +} + +export type AddOrUpdateBlocklistItemsParameters = + AddOrUpdateBlocklistItemsBodyParam & RequestParameters; + +export interface RemoveBlocklistItemsBodyParam { + body?: RemoveTextBlocklistItemsOptions; +} + +export type RemoveBlocklistItemsParameters = RemoveBlocklistItemsBodyParam & + 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/sdk/contentsafety/azure-ai-content-safety-rest/src/responses.ts b/sdk/contentsafety/azure-ai-content-safety-rest/src/responses.ts new file mode 100644 index 000000000000..d0de87a205c8 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/src/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, + AddOrUpdateTextBlocklistItemsResultOutput, + TextBlocklistItemOutput, + PagedTextBlocklistItemOutput, +} from "./outputModels"; + +/** 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 AddOrUpdateBlocklistItems200Response extends HttpResponse { + status: "200"; + body: AddOrUpdateTextBlocklistItemsResultOutput; +} + +export interface AddOrUpdateBlocklistItemsDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface AddOrUpdateBlocklistItemsDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & AddOrUpdateBlocklistItemsDefaultHeaders; +} + +/** There is no content to send for this request, but the headers may be useful. */ +export interface RemoveBlocklistItems204Response extends HttpResponse { + status: "204"; +} + +export interface RemoveBlocklistItemsDefaultHeaders { + /** String error code indicating what went wrong. */ + "x-ms-error-code"?: string; +} + +export interface RemoveBlocklistItemsDefaultResponse extends HttpResponse { + status: string; + body: ErrorResponse; + headers: RawHttpHeaders & RemoveBlocklistItemsDefaultHeaders; +} + +/** The request has succeeded. */ +export interface GetTextBlocklistItem200Response extends HttpResponse { + status: "200"; + body: TextBlocklistItemOutput; +} + +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: PagedTextBlocklistItemOutput; +} + +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/sdk/contentsafety/azure-ai-content-safety-rest/test/public/sampleTest.spec.ts b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/sampleTest.spec.ts new file mode 100644 index 000000000000..bce68e428645 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/sampleTest.spec.ts @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { Recorder } from "@azure-tools/test-recorder"; +import { assert } from "chai"; +import { createRecorder } from "./utils/recordedClient"; +import { Context } from "mocha"; + +describe("My test", () => { + let recorder: Recorder; + + beforeEach(async function (this: Context) { + recorder = await createRecorder(this); + }); + + afterEach(async function () { + await recorder.stop(); + }); + + it("sample test", async function () { + assert.equal(1, 1); + }); +}); diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/env.browser.ts b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/env.browser.ts new file mode 100644 index 000000000000..fd2aca680c7b --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/env.browser.ts @@ -0,0 +1,2 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/env.ts b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/env.ts new file mode 100644 index 000000000000..0e06855b73ae --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/env.ts @@ -0,0 +1,6 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import * as dotenv from "dotenv"; + +dotenv.config(); diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/recordedClient.ts b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/recordedClient.ts new file mode 100644 index 000000000000..6cc58bc15e11 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/test/public/utils/recordedClient.ts @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import { Context } from "mocha"; +import { Recorder, RecorderStartOptions } from "@azure-tools/test-recorder"; +import "./env"; + +const envSetupForPlayback: Record = { + ENDPOINT: "https://endpoint", + AZURE_CLIENT_ID: "azure_client_id", + AZURE_CLIENT_SECRET: "azure_client_secret", + AZURE_TENANT_ID: "88888888-8888-8888-8888-888888888888", + SUBSCRIPTION_ID: "azure_subscription_id", +}; + +const recorderEnvSetup: RecorderStartOptions = { + envSetupForPlayback, +}; + +/** + * creates the recorder and reads the environment variables from the `.env` file. + * Should be called first in the test suite to make sure environment variables are + * read before they are being used. + */ +export async function createRecorder(context: Context): Promise { + const recorder = new Recorder(context.currentTest); + await recorder.start(recorderEnvSetup); + return recorder; +} diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/tsconfig.json b/sdk/contentsafety/azure-ai-content-safety-rest/tsconfig.json new file mode 100644 index 000000000000..d5bf593423c9 --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../../tsconfig.package", + "compilerOptions": { + "outDir": "./dist-esm", + "declarationDir": "./types" + }, + "include": [ + "src/**/*.ts", + "./test/**/*.ts" + ] +} \ No newline at end of file diff --git a/sdk/contentsafety/azure-ai-content-safety-rest/tsp-location.yaml b/sdk/contentsafety/azure-ai-content-safety-rest/tsp-location.yaml new file mode 100644 index 000000000000..3b934b80d73a --- /dev/null +++ b/sdk/contentsafety/azure-ai-content-safety-rest/tsp-location.yaml @@ -0,0 +1,5 @@ +directory: specification/cognitiveservices/ContentSafety +repo: Azure/azure-rest-api-specs +commit: 004df4b91067d268ec870b2c7c759048b9914869 +additionalDirectories: [] +