Initial move of csharp client emitter (#3098)
Fixes https://github.com/microsoft/typespec/issues/3095 --------- Co-authored-by: Wes Haggard <weshaggard@users.noreply.github.com> Co-authored-by: Brian Terlson <brian.terlson@microsoft.com> Co-authored-by: Timothee Guerin <tiguerin@microsoft.com> Co-authored-by: Patrick Hallisey <hallipr@gmail.com>
This commit is contained in:
Родитель
f490d448a7
Коммит
0889ba248c
|
@ -54,3 +54,6 @@ versionPolicies:
|
|||
- "@typespec/library-linter"
|
||||
|
||||
changelog: ["@chronus/github/changelog", { repo: "microsoft/typespec" }]
|
||||
|
||||
ignore:
|
||||
- "@typespec/http-client-csharp"
|
||||
|
|
|
@ -1 +1,8 @@
|
|||
* @bterlson @markcowl @allenjzhang @timotheeguerin
|
||||
######################
|
||||
# CSharp
|
||||
######################
|
||||
/packages/http-client-csharp @m-nash
|
||||
/packages/http-client-csharp-generator @m-nash
|
||||
|
||||
# Catch all
|
||||
* @bterlson @markcowl @allenjzhang @timotheeguerin
|
||||
|
|
|
@ -190,3 +190,6 @@ obj/
|
|||
|
||||
|
||||
docs/**/js-api/
|
||||
|
||||
# csharp emitter
|
||||
!packages/http-client-csharp/package-lock.json
|
||||
|
|
|
@ -112,6 +112,8 @@ words:
|
|||
- WINDOWSVMIMAGE
|
||||
- xlarge
|
||||
- xplat
|
||||
- isequal # only needed until https://github.com/microsoft/typespec/issues/3108 is resolved
|
||||
- TCGC
|
||||
ignorePaths:
|
||||
- "**/node_modules/**"
|
||||
- "**/dist/**"
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
# Continuous Integration
|
||||
|
||||
trigger:
|
||||
- main
|
||||
# For patch releases
|
||||
- release/*
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
# For patch releases
|
||||
- release/*
|
||||
paths:
|
||||
exclude:
|
||||
- packages/http-client-csharp
|
||||
|
||||
pr: none
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
name: PR Tools
|
||||
trigger: none
|
||||
pr:
|
||||
- main
|
||||
- release/*
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- release/*
|
||||
paths:
|
||||
exclude:
|
||||
- packages/http-client-csharp
|
||||
|
||||
extends:
|
||||
template: /eng/pipelines/templates/1es-redirect.yml
|
||||
|
|
|
@ -3,10 +3,18 @@ trigger:
|
|||
include:
|
||||
- main
|
||||
- gh-readonly-queue/* # Used to trigger for GitHub merge queues
|
||||
paths:
|
||||
exclude:
|
||||
- packages/http-client-csharp
|
||||
|
||||
pr:
|
||||
- main
|
||||
- release/*
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- release/*
|
||||
paths:
|
||||
exclude:
|
||||
- packages/http-client-csharp
|
||||
|
||||
extends:
|
||||
template: /eng/pipelines/templates/1es-redirect.yml
|
||||
|
|
|
@ -6,21 +6,22 @@
|
|||
"scripts": {
|
||||
"build": "pnpm build:all && pnpm gen-compiler-extern-signature",
|
||||
"build:all": "pnpm -r --workspace-concurrency=Infinity --aggregate-output --reporter=append-only build && pnpm gen-compiler-extern-signature",
|
||||
"setup:min": "pnpm install && pnpm --filter \"@typespec/prettier-plugin-typespec...\" run build",
|
||||
"check-version-mismatch": "syncpack list-mismatches",
|
||||
"change": "chronus",
|
||||
"clean": "pnpm -r run clean",
|
||||
"cspell": "cspell --no-progress .",
|
||||
"dogfood": "pnpm install && pnpm build && pnpm -r dogfood",
|
||||
"fix-version-mismatch": "syncpack fix-mismatches",
|
||||
"format": "pnpm run prettier --write",
|
||||
"format:check": "pnpm run prettier --check",
|
||||
"format": "prettier . --write",
|
||||
"format:check": "prettier . --check",
|
||||
"format:dir": "prettier --write",
|
||||
"gen-compiler-extern-signature": "pnpm run -r --filter \"@typespec/compiler\" gen-extern-signature",
|
||||
"lint": "pnpm -r --parallel --aggregate-output --reporter=append-only run lint",
|
||||
"merge-coverage": "c8 -- report --reporter=cobertura --reporter=text",
|
||||
"pack:all": "chronus pack --pack-destination ./temp/artifacts",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"prepare-publish": "pnpm chronus version",
|
||||
"prettier": "prettier .",
|
||||
"purge": "rimraf --glob \"packages/*/node_modules/\"",
|
||||
"regen-docs": "pnpm -r --parallel --aggregate-output --reporter=append-only run regen-docs",
|
||||
"regen-samples": "pnpm -r run regen-samples",
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
require("@typespec/eslint-config-typespec/patch/modern-module-resolution");
|
||||
|
||||
module.exports = {
|
||||
plugins: ["@typespec/eslint-plugin-typespec"],
|
||||
extends: [
|
||||
"@typespec/eslint-config-typespec",
|
||||
"plugin:@typespec/eslint-plugin-typespec/recommended",
|
||||
],
|
||||
parserOptions: { tsconfigRootDir: __dirname },
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
trigger: none
|
||||
pr: none
|
||||
|
||||
extends:
|
||||
template: /eng/pipelines/templates/1es-redirect.yml
|
||||
parameters:
|
||||
variables:
|
||||
BlobFeedUrl: https://azuresdkartifacts.blob.core.windows.net/azure-sdk-tools/index.json
|
||||
OfficialBuildId: $(Build.BuildNumber)
|
||||
nugetMultiFeedWarnLevel: "none"
|
||||
|
||||
stages:
|
||||
- stage: "Build_and_Test"
|
||||
pool:
|
||||
name: $(WINDOWSPOOL)
|
||||
image: $(WINDOWSVMIMAGE)
|
||||
os: windows
|
||||
jobs:
|
||||
- job: Build
|
||||
timeoutInMinutes: 120
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: 1
|
||||
- task: NodeTool@0
|
||||
displayName: "Install Node 18.x"
|
||||
inputs:
|
||||
versionSpec: "18.x"
|
||||
- script: |
|
||||
npm ci
|
||||
displayName: "Install packages"
|
||||
workingDirectory: $(Build.SourcesDirectory)/packages/http-client-csharp
|
||||
- script: |
|
||||
npm ls -a
|
||||
displayName: "List packages"
|
||||
workingDirectory: $(Build.SourcesDirectory)/packages/http-client-csharp
|
||||
- task: Npm@1
|
||||
displayName: "Build TypeSpec csharp emitter"
|
||||
inputs:
|
||||
command: custom
|
||||
customCommand: run build
|
||||
workingDir: $(Build.SourcesDirectory)/packages/http-client-csharp
|
||||
- script: |
|
||||
npm run test
|
||||
displayName: "Unit Test"
|
||||
workingDirectory: $(Build.SourcesDirectory)/packages/http-client-csharp
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,70 @@
|
|||
{
|
||||
"name": "@typespec/http-client-csharp",
|
||||
"version": "0.2.0",
|
||||
"author": "Microsoft Corporation",
|
||||
"description": "The typespec library that can be used to generate C# models from a TypeSpec REST protocol binding",
|
||||
"homepage": "https://github.com/Microsoft/typespec",
|
||||
"readme": "https://github.com/Microsoft/typespec/blob/main/packages/http-client-csharp/readme.md",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Microsoft/typespec.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/Microsoft/typespec/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"typespec"
|
||||
],
|
||||
"type": "module",
|
||||
"main": "dist/src/index.js",
|
||||
"scripts": {
|
||||
"clean": "rimraf ./dist ./temp",
|
||||
"build": "tsc -p .",
|
||||
"watch": "tsc -p . --watch",
|
||||
"lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit",
|
||||
"test": "mocha dist/test/**/*.js",
|
||||
"test-official": "c8 mocha --forbid-only dist/test/**/*.js",
|
||||
"lint": "eslint . --ext .ts --max-warnings=0",
|
||||
"lint:fix": "eslint . --fix --ext .ts",
|
||||
"format": "pnpm -w format:dir packages/http-client-csharp"
|
||||
},
|
||||
"files": [
|
||||
"dist/src/**"
|
||||
],
|
||||
"dependencies": {
|
||||
"json-serialize-refs": "0.1.0-0",
|
||||
"winston": "^3.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@azure-tools/typespec-azure-core": ">=0.36.0 <1.0.0",
|
||||
"@azure-tools/typespec-client-generator-core": ">=0.36.0 <1.0.0",
|
||||
"@typespec/compiler": ">=0.50.0 <1.0.0",
|
||||
"@typespec/http": ">=0.50.0 <1.0.0",
|
||||
"@typespec/rest": ">=0.50.0 <1.0.0",
|
||||
"@typespec/versioning": ">=0.50.0 <1.0.0",
|
||||
"@typespec/openapi": ">=0.50.0 <1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@azure-tools/typespec-azure-core": "0.40.0",
|
||||
"@azure-tools/typespec-client-generator-core": "0.40.0",
|
||||
"@typespec/compiler": "0.54.0",
|
||||
"@typespec/eslint-config-typespec": "0.54.0",
|
||||
"@typespec/eslint-plugin": "0.54.0",
|
||||
"@typespec/http": "0.54.0",
|
||||
"@typespec/library-linter": "0.54.0",
|
||||
"@typespec/rest": "0.54.0",
|
||||
"@typespec/versioning": "0.54.0",
|
||||
"@typespec/openapi": "0.54.0",
|
||||
"@typespec/json-schema": "0.54.0",
|
||||
"@types/lodash.isequal": "^4.5.6",
|
||||
"@types/mocha": "~9.1.0",
|
||||
"@types/node": "~18.13.0",
|
||||
"c8": "~7.11.0",
|
||||
"eslint": "^8.57.0",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"mocha": "~9.2.0",
|
||||
"rimraf": "~5.0.5",
|
||||
"typescript": "~5.4.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
# TypeSpec csharp emitter library
|
||||
|
||||
This is a TypeSpec library that will emit a .NET SDK from TypeSpec.
|
||||
|
||||
## Prerequisite
|
||||
|
||||
Install [Node.js](https://nodejs.org/download/) 16 or above. (Verify by `node --version`)
|
||||
Install [**.NET 8.0 SDK**](https://dotnet.microsoft.com/download/dotnet/8.0) for your specific platform. (or a higher version)
|
||||
|
||||
## Getting started
|
||||
|
||||
### Initialize TypeSpec Project
|
||||
|
||||
Follow [TypeSpec Getting Started](https://github.com/microsoft/typespec/#using-node--npm) to initialize your TypeSpec project.
|
||||
|
||||
Make sure `npx tsp compile .` runs correctly.
|
||||
|
||||
### Add typespec csharp
|
||||
|
||||
Run `npm install @typespec/http-client-csharp`.
|
||||
|
||||
### Generate .NET client
|
||||
|
||||
Run command `npx tsp compile --emit @typespec/http-client-csharp <path-to-typespec-file>`
|
||||
|
||||
e.g.
|
||||
|
||||
```cmd
|
||||
npx tsp compile main.tsp --emit @typespec/http-client-csharp
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can further configure the SDK generated, using the emitter options on @typespec/http-client-csharp.
|
||||
|
||||
You can set options in the command line directly via `--option @typespec/http-client-csharp.<optionName>=XXX`, e.g. `--option @typespec/http-client-csharp.namespace=MyService.Namespace`
|
||||
|
||||
or
|
||||
|
||||
Modify `tspconfig.yaml` in typespec project, add emitter options under options/@typespec/http-client-csharp.
|
||||
|
||||
```diff
|
||||
emit:
|
||||
- "@typespec/http-client-csharp"
|
||||
options:
|
||||
"@typespec/http-client-csharp":
|
||||
+ namespace: MyService.Namespace
|
||||
```
|
||||
|
||||
**Supported Emitter options**:
|
||||
|
||||
- `namespace` define the client library namespace. e.g. MyService.Namespace.
|
||||
- `emitter-output-dir` define the output dire path which will store the generated code.
|
||||
- `generate-protocol-methods` indicate if you want to generate **protocol method** for every operation or not. The default value is true.
|
||||
- `generate-convenience-methods` indicate if you want to generate **convenience method** for every operation or not. The default value is true.
|
||||
- `unreferenced-types-handling` define the strategy how to handle the unreferenced types. It can be `removeOrInternalize`, `internalize` or `keepAll`
|
||||
- `model-namespace` indicate if we want to put the models in their own namespace which is a sub namespace of the client library namespace plus ".Models". if it is set `false`, the models will be put in the same namespace of the client. The default value is `true`.
|
||||
- `clear-output-folder` indicate if you want to clear up the output folder.
|
||||
- `package-name` define the package name.
|
||||
|
||||
## Convenience API
|
||||
|
||||
By default, TypeSpec csharp generates all protocol APIs and convenience APIs.
|
||||
A few exceptions are API of JSON Merge Patch, and API of long-running operation with ambiguous response type.
|
||||
|
||||
You can configure whether generate convenience API or not via `convenienceAPI` decorator.
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export const projectedNameJsonKey = "json";
|
||||
export const projectedNameCSharpKey = "csharp";
|
||||
export const projectedNameClientKey = "client";
|
||||
export const mockApiVersion = "0000-00-00";
|
||||
export const tspOutputFileName = "tspCodeModel.json";
|
||||
export const configurationFileName = "Configuration.json";
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { createSdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
import {
|
||||
EmitContext,
|
||||
Program,
|
||||
createTypeSpecLibrary,
|
||||
logDiagnostics,
|
||||
paramMessage,
|
||||
resolvePath,
|
||||
} from "@typespec/compiler";
|
||||
|
||||
import fs, { existsSync } from "fs";
|
||||
import { PreserveType, stringifyRefs } from "json-serialize-refs";
|
||||
import { configurationFileName, tspOutputFileName } from "./constants.js";
|
||||
import { createModel } from "./lib/clientModelBuilder.js";
|
||||
import { LoggerLevel, logger } from "./lib/logger.js";
|
||||
import {
|
||||
NetEmitterOptions,
|
||||
NetEmitterOptionsSchema,
|
||||
resolveOptions,
|
||||
resolveOutputFolder,
|
||||
} from "./options.js";
|
||||
import { Configuration } from "./type/configuration.js";
|
||||
|
||||
export const $lib = createTypeSpecLibrary({
|
||||
name: "@typespec/http-client-csharp",
|
||||
diagnostics: {
|
||||
"No-APIVersion": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
default: paramMessage`No APIVersion Provider for service ${"service"}`,
|
||||
},
|
||||
},
|
||||
"No-Route": {
|
||||
severity: "error",
|
||||
messages: {
|
||||
default: paramMessage`No Route for service for service ${"service"}`,
|
||||
},
|
||||
},
|
||||
"Invalid-Name": {
|
||||
severity: "warning",
|
||||
messages: {
|
||||
default: paramMessage`Invalid interface or operation group name ${"name"} when configuration "model-namespace" is on`,
|
||||
},
|
||||
},
|
||||
},
|
||||
emitter: {
|
||||
options: NetEmitterOptionsSchema,
|
||||
},
|
||||
});
|
||||
|
||||
export async function $onEmit(context: EmitContext<NetEmitterOptions>) {
|
||||
const program: Program = context.program;
|
||||
const options = resolveOptions(context);
|
||||
const outputFolder = resolveOutputFolder(context);
|
||||
|
||||
/* set the loglevel. */
|
||||
for (const transport of logger.transports) {
|
||||
transport.level = options.logLevel ?? LoggerLevel.INFO;
|
||||
}
|
||||
|
||||
if (!program.compilerOptions.noEmit && !program.hasError()) {
|
||||
// Write out the dotnet model to the output path
|
||||
const sdkContext = createSdkContext(context, "@typespec/http-client-csharp");
|
||||
const root = createModel(sdkContext);
|
||||
if (
|
||||
context.program.diagnostics.length > 0 &&
|
||||
context.program.diagnostics.filter((digs) => digs.severity === "error").length > 0
|
||||
) {
|
||||
logDiagnostics(context.program.diagnostics, context.program.host.logSink);
|
||||
process.exit(1);
|
||||
}
|
||||
const tspNamespace = root.Name; // this is the top-level namespace defined in the typespec file, which is actually always different from the namespace of the SDK
|
||||
// await program.host.writeFile(outPath, prettierOutput(JSON.stringify(root, null, 2)));
|
||||
if (root) {
|
||||
const generatedFolder = outputFolder.endsWith("src")
|
||||
? resolvePath(outputFolder, "Generated")
|
||||
: resolvePath(outputFolder, "src", "Generated");
|
||||
|
||||
if (!fs.existsSync(generatedFolder)) {
|
||||
fs.mkdirSync(generatedFolder, { recursive: true });
|
||||
}
|
||||
|
||||
await program.host.writeFile(
|
||||
resolvePath(generatedFolder, tspOutputFileName),
|
||||
prettierOutput(stringifyRefs(root, null, 1, PreserveType.Objects))
|
||||
);
|
||||
|
||||
//emit configuration.json
|
||||
const namespace = options.namespace ?? tspNamespace;
|
||||
const configurations: Configuration = {
|
||||
"output-folder": ".",
|
||||
namespace: namespace,
|
||||
"library-name": options["library-name"] ?? namespace,
|
||||
"single-top-level-client": options["single-top-level-client"],
|
||||
"unreferenced-types-handling": options["unreferenced-types-handling"],
|
||||
"keep-non-overloadable-protocol-signature":
|
||||
options["keep-non-overloadable-protocol-signature"],
|
||||
"model-namespace": options["model-namespace"],
|
||||
"models-to-treat-empty-string-as-null": options["models-to-treat-empty-string-as-null"],
|
||||
"intrinsic-types-to-treat-empty-string-as-null": options[
|
||||
"models-to-treat-empty-string-as-null"
|
||||
]
|
||||
? options["additional-intrinsic-types-to-treat-empty-string-as-null"].concat(
|
||||
["Uri", "Guid", "ResourceIdentifier", "DateTimeOffset"].filter(
|
||||
(item) =>
|
||||
options["additional-intrinsic-types-to-treat-empty-string-as-null"].indexOf(
|
||||
item
|
||||
) < 0
|
||||
)
|
||||
)
|
||||
: undefined,
|
||||
"methods-to-keep-client-default-value": options["methods-to-keep-client-default-value"],
|
||||
"head-as-boolean": options["head-as-boolean"],
|
||||
"deserialize-null-collection-as-null-value":
|
||||
options["deserialize-null-collection-as-null-value"],
|
||||
flavor: options["flavor"],
|
||||
//only emit these if they are not the default values
|
||||
"generate-sample-project":
|
||||
options["generate-sample-project"] === true
|
||||
? undefined
|
||||
: options["generate-sample-project"],
|
||||
"generate-test-project":
|
||||
options["generate-test-project"] === false ? undefined : options["generate-test-project"],
|
||||
"use-model-reader-writer": options["use-model-reader-writer"] ?? true,
|
||||
};
|
||||
|
||||
await program.host.writeFile(
|
||||
resolvePath(generatedFolder, configurationFileName),
|
||||
prettierOutput(JSON.stringify(configurations, null, 2))
|
||||
);
|
||||
|
||||
if (options.skipSDKGeneration !== true) {
|
||||
const csProjFile = resolvePath(outputFolder, `${configurations["library-name"]}.csproj`);
|
||||
logger.info(`Checking if ${csProjFile} exists`);
|
||||
const newProjectOption =
|
||||
options["new-project"] || !existsSync(csProjFile) ? "--new-project" : "";
|
||||
const existingProjectOption = options["existing-project-folder"]
|
||||
? `--existing-project-folder ${options["existing-project-folder"]}`
|
||||
: "";
|
||||
const debugFlag = options.debug ?? false ? " --debug" : "";
|
||||
|
||||
logger.info("TODO connect the dotnet generator");
|
||||
//const command = `dotnet --roll-forward Major ${resolvePath(
|
||||
// options.csharpGeneratorPath
|
||||
//)} --project-path ${outputFolder} ${newProjectOption} ${existingProjectOption} --clear-output-folder ${
|
||||
// options["clear-output-folder"]
|
||||
//}${debugFlag}`;
|
||||
//logger.info(command);
|
||||
//
|
||||
//try {
|
||||
// execSync(command, { stdio: "inherit" });
|
||||
//} catch (error: any) {
|
||||
// if (error.message) logger.info(error.message);
|
||||
// if (error.stderr) logger.error(error.stderr);
|
||||
// if (error.stdout) logger.verbose(error.stdout);
|
||||
// throw error;
|
||||
//}
|
||||
}
|
||||
|
||||
if (!options["save-inputs"]) {
|
||||
// delete
|
||||
deleteFile(resolvePath(generatedFolder, tspOutputFileName));
|
||||
deleteFile(resolvePath(generatedFolder, configurationFileName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function deleteFile(filePath: string) {
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
logger.error(`stderr: ${err}`);
|
||||
} else {
|
||||
logger.info(`File ${filePath} is deleted.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function prettierOutput(output: string) {
|
||||
return output + "\n";
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export * from "./emitter.js";
|
|
@ -0,0 +1,295 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import {
|
||||
SdkClient,
|
||||
SdkContext,
|
||||
SdkOperationGroup,
|
||||
getLibraryName,
|
||||
listClients,
|
||||
listOperationGroups,
|
||||
listOperationsInOperationGroup,
|
||||
} from "@azure-tools/typespec-client-generator-core";
|
||||
import {
|
||||
EmitContext,
|
||||
NoTarget,
|
||||
Service,
|
||||
getDoc,
|
||||
getNamespaceFullName,
|
||||
ignoreDiagnostics,
|
||||
listServices,
|
||||
} from "@typespec/compiler";
|
||||
import {
|
||||
HttpOperation,
|
||||
getAllHttpServices,
|
||||
getAuthentication,
|
||||
getHttpOperation,
|
||||
getServers,
|
||||
} from "@typespec/http";
|
||||
import { getVersions } from "@typespec/versioning";
|
||||
import { $lib } from "../emitter.js";
|
||||
import { NetEmitterOptions, resolveOptions } from "../options.js";
|
||||
import { ClientKind } from "../type/clientKind.js";
|
||||
import { CodeModel } from "../type/codeModel.js";
|
||||
import { InputClient } from "../type/inputClient.js";
|
||||
import { InputConstant } from "../type/inputConstant.js";
|
||||
import { InputOperation } from "../type/inputOperation.js";
|
||||
import { InputOperationParameterKind } from "../type/inputOperationParameterKind.js";
|
||||
import { InputParameter } from "../type/inputParameter.js";
|
||||
import { InputPrimitiveTypeKind } from "../type/inputPrimitiveTypeKind.js";
|
||||
import { InputEnumType, InputModelType, InputPrimitiveType } from "../type/inputType.js";
|
||||
import { InputTypeKind } from "../type/inputTypeKind.js";
|
||||
import { RequestLocation } from "../type/requestLocation.js";
|
||||
import { Usage } from "../type/usage.js";
|
||||
import { getExternalDocs } from "./decorators.js";
|
||||
import { logger } from "./logger.js";
|
||||
import { getUsages, navigateModels } from "./model.js";
|
||||
import { loadOperation } from "./operation.js";
|
||||
import { processServiceAuthentication } from "./serviceAuthentication.js";
|
||||
import { resolveServers } from "./typespecServer.js";
|
||||
import { createContentTypeOrAcceptParameter } from "./utils.js";
|
||||
|
||||
export function createModel(sdkContext: SdkContext<NetEmitterOptions>): CodeModel {
|
||||
const services = listServices(sdkContext.emitContext.program);
|
||||
if (services.length === 0) {
|
||||
services.push({
|
||||
type: sdkContext.emitContext.program.getGlobalNamespaceType(),
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: support multiple service. Current only chose the first service.
|
||||
const service = services[0];
|
||||
const serviceNamespaceType = service.type;
|
||||
if (serviceNamespaceType === undefined) {
|
||||
throw Error("Can not emit yaml for a namespace that doesn't exist.");
|
||||
}
|
||||
|
||||
return createModelForService(sdkContext, service);
|
||||
}
|
||||
|
||||
export function createModelForService(
|
||||
sdkContext: SdkContext<NetEmitterOptions>,
|
||||
service: Service
|
||||
): CodeModel {
|
||||
const emitterOptions = resolveOptions(sdkContext.emitContext);
|
||||
const program = sdkContext.emitContext.program;
|
||||
const serviceNamespaceType = service.type;
|
||||
|
||||
const apiVersions: Set<string> | undefined = new Set<string>();
|
||||
let defaultApiVersion: string | undefined = undefined;
|
||||
const versions = getVersions(program, service.type)[1]?.getVersions();
|
||||
if (versions && versions.length > 0) {
|
||||
for (const ver of versions) {
|
||||
apiVersions.add(ver.value);
|
||||
}
|
||||
defaultApiVersion = versions[versions.length - 1].value;
|
||||
}
|
||||
const defaultApiVersionConstant: InputConstant | undefined = defaultApiVersion
|
||||
? {
|
||||
Type: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
Value: defaultApiVersion,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const description = getDoc(program, serviceNamespaceType);
|
||||
const externalDocs = getExternalDocs(sdkContext, serviceNamespaceType);
|
||||
|
||||
const servers = getServers(program, serviceNamespaceType);
|
||||
const namespace = getNamespaceFullName(serviceNamespaceType) || "client";
|
||||
const authentication = getAuthentication(program, serviceNamespaceType);
|
||||
let auth = undefined;
|
||||
if (authentication) {
|
||||
auth = processServiceAuthentication(authentication);
|
||||
}
|
||||
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
let urlParameters: InputParameter[] | undefined = undefined;
|
||||
let url: string = "";
|
||||
const convenienceOperations: HttpOperation[] = [];
|
||||
|
||||
//create endpoint parameter from servers
|
||||
if (servers !== undefined) {
|
||||
const typespecServers = resolveServers(sdkContext, servers, modelMap, enumMap);
|
||||
if (typespecServers.length > 0) {
|
||||
/* choose the first server as endpoint. */
|
||||
url = typespecServers[0].url;
|
||||
urlParameters = typespecServers[0].parameters;
|
||||
}
|
||||
}
|
||||
const [services] = getAllHttpServices(program);
|
||||
const routes = services[0].operations;
|
||||
if (routes.length === 0) {
|
||||
$lib.reportDiagnostic(program, {
|
||||
code: "No-Route",
|
||||
format: { service: services[0].namespace.name },
|
||||
target: NoTarget,
|
||||
});
|
||||
}
|
||||
logger.info("routes:" + routes.length);
|
||||
|
||||
const clients: InputClient[] = [];
|
||||
const dpgClients = listClients(sdkContext);
|
||||
for (const client of dpgClients) {
|
||||
clients.push(emitClient(client));
|
||||
addChildClients(sdkContext.emitContext, client, clients);
|
||||
}
|
||||
|
||||
for (const client of clients) {
|
||||
for (const op of client.Operations) {
|
||||
const apiVersionIndex = op.Parameters.findIndex(
|
||||
(value: InputParameter) => value.IsApiVersion
|
||||
);
|
||||
if (apiVersionIndex === -1) {
|
||||
continue;
|
||||
}
|
||||
const apiVersionInOperation = op.Parameters[apiVersionIndex];
|
||||
if (defaultApiVersionConstant !== undefined) {
|
||||
if (!apiVersionInOperation.DefaultValue?.Value) {
|
||||
apiVersionInOperation.DefaultValue = defaultApiVersionConstant;
|
||||
}
|
||||
} else {
|
||||
apiVersionInOperation.Kind = InputOperationParameterKind.Method;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
navigateModels(sdkContext, serviceNamespaceType, modelMap, enumMap);
|
||||
|
||||
const usages = getUsages(sdkContext, convenienceOperations, modelMap);
|
||||
setUsage(usages, modelMap);
|
||||
setUsage(usages, enumMap);
|
||||
|
||||
const clientModel = {
|
||||
Name: namespace,
|
||||
Description: description,
|
||||
ApiVersions: Array.from(apiVersions.values()),
|
||||
Enums: Array.from(enumMap.values()),
|
||||
Models: Array.from(modelMap.values()),
|
||||
Clients: clients,
|
||||
Auth: auth,
|
||||
} as CodeModel;
|
||||
return clientModel;
|
||||
|
||||
function addChildClients(
|
||||
context: EmitContext<NetEmitterOptions>,
|
||||
client: SdkClient | SdkOperationGroup,
|
||||
clients: InputClient[]
|
||||
) {
|
||||
const dpgOperationGroups = listOperationGroups(sdkContext, client as SdkClient);
|
||||
for (const dpgGroup of dpgOperationGroups) {
|
||||
var subClient = emitClient(dpgGroup, client);
|
||||
clients.push(subClient);
|
||||
addChildClients(context, dpgGroup, clients);
|
||||
}
|
||||
}
|
||||
|
||||
function getClientName(client: SdkClient | SdkOperationGroup): string {
|
||||
if (client.kind === ClientKind.SdkClient) {
|
||||
return client.name;
|
||||
}
|
||||
|
||||
var pathParts = client.groupPath.split(".");
|
||||
if (pathParts?.length >= 3) {
|
||||
return pathParts.slice(pathParts.length - 2).join("");
|
||||
}
|
||||
|
||||
var clientName = getLibraryName(sdkContext, client.type);
|
||||
if (
|
||||
clientName === "Models" &&
|
||||
resolveOptions(sdkContext.emitContext)["model-namespace"] !== false
|
||||
) {
|
||||
$lib.reportDiagnostic(program, {
|
||||
code: "Invalid-Name",
|
||||
format: { name: clientName },
|
||||
target: client.type,
|
||||
});
|
||||
return "ModelsOps";
|
||||
}
|
||||
return clientName;
|
||||
}
|
||||
|
||||
function emitClient(
|
||||
client: SdkClient | SdkOperationGroup,
|
||||
parent?: SdkClient | SdkOperationGroup
|
||||
): InputClient {
|
||||
const operations = listOperationsInOperationGroup(sdkContext, client);
|
||||
let clientDesc = "";
|
||||
if (operations.length > 0) {
|
||||
const container = ignoreDiagnostics(getHttpOperation(program, operations[0])).container;
|
||||
clientDesc = getDoc(program, container) ?? "";
|
||||
}
|
||||
|
||||
const inputClient = {
|
||||
Name: getClientName(client),
|
||||
Description: clientDesc,
|
||||
Operations: [],
|
||||
Protocol: {},
|
||||
Creatable: client.kind === ClientKind.SdkClient,
|
||||
Parent: parent === undefined ? undefined : getClientName(parent),
|
||||
} as InputClient;
|
||||
for (const op of operations) {
|
||||
const httpOperation = ignoreDiagnostics(getHttpOperation(program, op));
|
||||
const inputOperation: InputOperation = loadOperation(
|
||||
sdkContext,
|
||||
httpOperation,
|
||||
url,
|
||||
urlParameters,
|
||||
serviceNamespaceType,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
|
||||
applyDefaultContentTypeAndAcceptParameter(inputOperation);
|
||||
inputClient.Operations.push(inputOperation);
|
||||
if (inputOperation.GenerateConvenienceMethod) convenienceOperations.push(httpOperation);
|
||||
}
|
||||
return inputClient;
|
||||
}
|
||||
}
|
||||
|
||||
function setUsage(
|
||||
usages: { inputs: string[]; outputs: string[]; roundTrips: string[] },
|
||||
models: Map<string, InputModelType | InputEnumType>
|
||||
) {
|
||||
for (const [name, m] of models) {
|
||||
if (m.Usage !== undefined && m.Usage !== Usage.None) continue;
|
||||
if (usages.inputs.includes(name)) {
|
||||
m.Usage = Usage.Input;
|
||||
} else if (usages.outputs.includes(name)) {
|
||||
m.Usage = Usage.Output;
|
||||
} else if (usages.roundTrips.includes(name)) {
|
||||
m.Usage = Usage.RoundTrip;
|
||||
} else {
|
||||
m.Usage = Usage.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyDefaultContentTypeAndAcceptParameter(operation: InputOperation): void {
|
||||
const defaultValue: string = "application/json";
|
||||
if (
|
||||
operation.Parameters.some((value) => value.Location === RequestLocation.Body) &&
|
||||
!operation.Parameters.some((value) => value.IsContentType === true)
|
||||
) {
|
||||
operation.Parameters.push(
|
||||
createContentTypeOrAcceptParameter([defaultValue], "contentType", "Content-Type")
|
||||
);
|
||||
operation.RequestMediaTypes = [defaultValue];
|
||||
}
|
||||
|
||||
if (
|
||||
!operation.Parameters.some(
|
||||
(value) =>
|
||||
value.Location === RequestLocation.Header && value.NameInRequest.toLowerCase() === "accept"
|
||||
)
|
||||
) {
|
||||
operation.Parameters.push(
|
||||
createContentTypeOrAcceptParameter([defaultValue], "accept", "Accept")
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { SdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
import { DecoratedType, Operation, Type } from "@typespec/compiler";
|
||||
import { ExternalDocs } from "../type/externalDocs.js";
|
||||
|
||||
const externalDocsKey = Symbol("externalDocs");
|
||||
export function getExternalDocs(context: SdkContext, entity: Type): ExternalDocs | undefined {
|
||||
return context.program.stateMap(externalDocsKey).get(entity);
|
||||
}
|
||||
|
||||
const operationIdsKey = Symbol("operationIds");
|
||||
/**
|
||||
* @returns operationId set via the @operationId decorator or `undefined`
|
||||
*/
|
||||
export function getOperationId(context: SdkContext, entity: Operation): string | undefined {
|
||||
return context.program.stateMap(operationIdsKey).get(entity);
|
||||
}
|
||||
|
||||
export function hasDecorator(type: DecoratedType, name: string): boolean {
|
||||
return type.decorators.find((it) => it.decorator.name === name) !== undefined;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import * as winston from "winston";
|
||||
|
||||
export enum LoggerLevel {
|
||||
ERROR = "error",
|
||||
WARN = "warn",
|
||||
INFO = "info",
|
||||
DEBUG = "debug",
|
||||
VERBOSE = "verbose",
|
||||
}
|
||||
|
||||
export var logger: winston.Logger = winston.createLogger({
|
||||
transports: [
|
||||
new winston.transports.Console({
|
||||
level: LoggerLevel.INFO,
|
||||
}),
|
||||
],
|
||||
});
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,386 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { getLroMetadata } from "@azure-tools/typespec-azure-core";
|
||||
import {
|
||||
SdkContext,
|
||||
getAccess,
|
||||
isApiVersion,
|
||||
isInternal,
|
||||
shouldGenerateConvenient,
|
||||
shouldGenerateProtocol,
|
||||
} from "@azure-tools/typespec-client-generator-core";
|
||||
import {
|
||||
Model,
|
||||
ModelProperty,
|
||||
Namespace,
|
||||
Operation,
|
||||
getDeprecated,
|
||||
getDoc,
|
||||
getSummary,
|
||||
isErrorModel,
|
||||
} from "@typespec/compiler";
|
||||
import { HttpOperation, HttpOperationParameter, HttpOperationResponse } from "@typespec/http";
|
||||
import { getResourceOperation } from "@typespec/rest";
|
||||
import { NetEmitterOptions } from "../options.js";
|
||||
import { BodyMediaType, typeToBodyMediaType } from "../type/bodyMediaType.js";
|
||||
import { collectionFormatToDelimMap } from "../type/collectionFormat.js";
|
||||
import { HttpResponseHeader } from "../type/httpResponseHeader.js";
|
||||
import { InputConstant } from "../type/inputConstant.js";
|
||||
import { InputOperation } from "../type/inputOperation.js";
|
||||
import { InputOperationParameterKind } from "../type/inputOperationParameterKind.js";
|
||||
import { InputParameter } from "../type/inputParameter.js";
|
||||
import {
|
||||
InputEnumType,
|
||||
InputListType,
|
||||
InputModelType,
|
||||
InputType,
|
||||
isInputLiteralType,
|
||||
isInputModelType,
|
||||
isInputUnionType,
|
||||
} from "../type/inputType.js";
|
||||
import { InputTypeKind } from "../type/inputTypeKind.js";
|
||||
import { convertLroFinalStateVia } from "../type/operationFinalStateVia.js";
|
||||
import { OperationLongRunning } from "../type/operationLongRunning.js";
|
||||
import { OperationPaging } from "../type/operationPaging.js";
|
||||
import { OperationResponse } from "../type/operationResponse.js";
|
||||
import { RequestLocation, requestLocationMap } from "../type/requestLocation.js";
|
||||
import { RequestMethod, parseHttpRequestMethod } from "../type/requestMethod.js";
|
||||
import { Usage } from "../type/usage.js";
|
||||
import { getExternalDocs, getOperationId, hasDecorator } from "./decorators.js";
|
||||
import { logger } from "./logger.js";
|
||||
import {
|
||||
getDefaultValue,
|
||||
getEffectiveSchemaType,
|
||||
getFormattedType,
|
||||
getInputType,
|
||||
} from "./model.js";
|
||||
import { capitalize, createContentTypeOrAcceptParameter, getTypeName } from "./utils.js";
|
||||
|
||||
export function loadOperation(
|
||||
sdkContext: SdkContext<NetEmitterOptions>,
|
||||
operation: HttpOperation,
|
||||
uri: string,
|
||||
urlParameters: InputParameter[] | undefined = undefined,
|
||||
serviceNamespaceType: Namespace,
|
||||
models: Map<string, InputModelType>,
|
||||
enums: Map<string, InputEnumType>
|
||||
): InputOperation {
|
||||
const { path: fullPath, operation: op, verb, parameters: typespecParameters } = operation;
|
||||
const program = sdkContext.program;
|
||||
logger.info(`load operation: ${op.name}, path:${fullPath} `);
|
||||
const resourceOperation = getResourceOperation(program, op);
|
||||
const desc = getDoc(program, op);
|
||||
const summary = getSummary(program, op);
|
||||
const externalDocs = getExternalDocs(sdkContext, op);
|
||||
|
||||
const parameters: InputParameter[] = [];
|
||||
if (urlParameters) {
|
||||
for (const param of urlParameters) {
|
||||
parameters.push(param);
|
||||
}
|
||||
}
|
||||
for (const p of typespecParameters.parameters) {
|
||||
parameters.push(loadOperationParameter(sdkContext, p));
|
||||
}
|
||||
|
||||
if (typespecParameters.body?.parameter) {
|
||||
parameters.push(loadBodyParameter(sdkContext, typespecParameters.body?.parameter));
|
||||
} else if (typespecParameters.body?.type) {
|
||||
const effectiveBodyType = getEffectiveSchemaType(sdkContext, typespecParameters.body.type);
|
||||
if (effectiveBodyType.kind === "Model") {
|
||||
let bodyParameter = loadBodyParameter(sdkContext, effectiveBodyType);
|
||||
if (effectiveBodyType.name === "") {
|
||||
bodyParameter.Kind = InputOperationParameterKind.Spread;
|
||||
}
|
||||
// TODO: remove this after https://github.com/Azure/typespec-azure/issues/69 is resolved
|
||||
// workaround for alias model
|
||||
if (isInputModelType(bodyParameter.Type) && bodyParameter.Type.Name === "") {
|
||||
// give body type a name
|
||||
bodyParameter.Type.Name = `${capitalize(op.name)}Request`;
|
||||
var bodyModelType = bodyParameter.Type as InputModelType;
|
||||
bodyModelType.Usage = Usage.Input;
|
||||
// update models cache
|
||||
models.delete("");
|
||||
models.set(bodyModelType.Name, bodyModelType);
|
||||
|
||||
// give body parameter a name
|
||||
bodyParameter.Name = `${capitalize(op.name)}Request`;
|
||||
}
|
||||
parameters.push(bodyParameter);
|
||||
}
|
||||
}
|
||||
|
||||
const responses: OperationResponse[] = [];
|
||||
for (const res of operation.responses) {
|
||||
const operationResponse = loadOperationResponse(sdkContext, res);
|
||||
if (operationResponse) {
|
||||
responses.push(operationResponse);
|
||||
}
|
||||
if (operationResponse?.ContentTypes && operationResponse.ContentTypes.length > 0) {
|
||||
const acceptParameter = createContentTypeOrAcceptParameter(
|
||||
[operationResponse.ContentTypes[0]], // We currently only support one content type per response
|
||||
"accept",
|
||||
"Accept"
|
||||
);
|
||||
const acceptIndex = parameters.findIndex((p) => p.NameInRequest.toLowerCase() === "accept");
|
||||
if (acceptIndex > -1) {
|
||||
parameters.splice(acceptIndex, 1, acceptParameter);
|
||||
} else {
|
||||
parameters.push(acceptParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mediaTypes: string[] = [];
|
||||
const contentTypeParameter = parameters.find((value) => value.IsContentType);
|
||||
if (contentTypeParameter) {
|
||||
if (isInputLiteralType(contentTypeParameter.Type)) {
|
||||
mediaTypes.push(contentTypeParameter.DefaultValue?.Value);
|
||||
} else if (isInputUnionType(contentTypeParameter.Type)) {
|
||||
const mediaTypeValues = contentTypeParameter.Type.UnionItemTypes.map((item) =>
|
||||
isInputLiteralType(item) ? item.Value : undefined
|
||||
);
|
||||
if (mediaTypeValues.some((item) => item === undefined)) {
|
||||
throw "Media type of content type should be string.";
|
||||
}
|
||||
mediaTypes.push(...mediaTypeValues);
|
||||
}
|
||||
}
|
||||
const requestMethod = parseHttpRequestMethod(verb);
|
||||
const generateProtocol: boolean = shouldGenerateProtocol(sdkContext, op);
|
||||
const generateConvenience: boolean =
|
||||
requestMethod !== RequestMethod.PATCH && shouldGenerateConvenient(sdkContext, op);
|
||||
|
||||
/* handle lro */
|
||||
/* handle paging. */
|
||||
let paging: OperationPaging | undefined = undefined;
|
||||
for (const res of operation.responses) {
|
||||
const body = res.responses[0]?.body;
|
||||
if (body?.type) {
|
||||
const bodyType = getEffectiveSchemaType(sdkContext, body.type);
|
||||
if (bodyType.kind === "Model" && hasDecorator(bodyType, "$pagedResult")) {
|
||||
const itemsProperty = Array.from(bodyType.properties.values()).find((it) =>
|
||||
hasDecorator(it, "$items")
|
||||
);
|
||||
const nextLinkProperty = Array.from(bodyType.properties.values()).find((it) =>
|
||||
hasDecorator(it, "$nextLink")
|
||||
);
|
||||
paging = {
|
||||
NextLinkName: nextLinkProperty?.name,
|
||||
ItemName: itemsProperty?.name,
|
||||
} as OperationPaging;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* TODO: handle lro */
|
||||
|
||||
return {
|
||||
Name: getTypeName(sdkContext, op),
|
||||
ResourceName:
|
||||
resourceOperation?.resourceType.name ??
|
||||
getOperationGroupName(sdkContext, op, serviceNamespaceType),
|
||||
Summary: summary,
|
||||
Deprecated: getDeprecated(program, op),
|
||||
Description: desc,
|
||||
Accessibility: isInternal(sdkContext, op) ? "internal" : getAccess(sdkContext, op),
|
||||
Parameters: parameters,
|
||||
Responses: responses,
|
||||
HttpMethod: requestMethod,
|
||||
RequestBodyMediaType: typeToBodyMediaType(typespecParameters.body?.type),
|
||||
Uri: uri,
|
||||
Path: fullPath,
|
||||
ExternalDocsUrl: externalDocs?.url,
|
||||
RequestMediaTypes: mediaTypes.length > 0 ? mediaTypes : undefined,
|
||||
BufferResponse: true,
|
||||
LongRunning: loadLongRunningOperation(sdkContext, operation),
|
||||
Paging: paging,
|
||||
GenerateProtocolMethod: generateProtocol,
|
||||
GenerateConvenienceMethod: generateConvenience,
|
||||
} as InputOperation;
|
||||
|
||||
function loadOperationParameter(
|
||||
context: SdkContext<NetEmitterOptions>,
|
||||
parameter: HttpOperationParameter
|
||||
): InputParameter {
|
||||
const { type: location, name, param } = parameter;
|
||||
const format = parameter.type === "path" ? undefined : parameter.format;
|
||||
const typespecType = param.type;
|
||||
const inputType: InputType = getInputType(
|
||||
context,
|
||||
getFormattedType(program, param),
|
||||
models,
|
||||
enums
|
||||
);
|
||||
let defaultValue = undefined;
|
||||
const value = getDefaultValue(typespecType);
|
||||
if (value) {
|
||||
defaultValue = {
|
||||
Type: inputType,
|
||||
Value: value,
|
||||
} as InputConstant;
|
||||
}
|
||||
const requestLocation = requestLocationMap[location];
|
||||
const isApiVer: boolean = isApiVersion(sdkContext, parameter);
|
||||
const isContentType: boolean =
|
||||
requestLocation === RequestLocation.Header && name.toLowerCase() === "content-type";
|
||||
const kind: InputOperationParameterKind =
|
||||
isContentType || inputType.Kind === InputTypeKind.Literal
|
||||
? InputOperationParameterKind.Constant
|
||||
: isApiVer
|
||||
? defaultValue
|
||||
? InputOperationParameterKind.Constant
|
||||
: InputOperationParameterKind.Client
|
||||
: InputOperationParameterKind.Method;
|
||||
return {
|
||||
Name: getTypeName(sdkContext, param),
|
||||
NameInRequest: name,
|
||||
Description: getDoc(program, param),
|
||||
Type: inputType,
|
||||
Location: requestLocation,
|
||||
DefaultValue: defaultValue,
|
||||
IsRequired: !param.optional,
|
||||
IsApiVersion: isApiVer,
|
||||
IsResourceParameter: false,
|
||||
IsContentType: isContentType,
|
||||
IsEndpoint: false,
|
||||
SkipUrlEncoding: false, //TODO: retrieve out value from extension
|
||||
Explode: (inputType as InputListType).ElementType && format === "multi" ? true : false,
|
||||
Kind: kind,
|
||||
ArraySerializationDelimiter: format ? collectionFormatToDelimMap[format] : undefined,
|
||||
} as InputParameter;
|
||||
}
|
||||
|
||||
function loadBodyParameter(
|
||||
context: SdkContext<NetEmitterOptions>,
|
||||
body: ModelProperty | Model
|
||||
): InputParameter {
|
||||
const inputType: InputType = getInputType(
|
||||
context,
|
||||
getFormattedType(program, body),
|
||||
models,
|
||||
enums
|
||||
);
|
||||
const requestLocation = RequestLocation.Body;
|
||||
const kind: InputOperationParameterKind = InputOperationParameterKind.Method;
|
||||
return {
|
||||
Name: getTypeName(context, body),
|
||||
NameInRequest: body.name,
|
||||
Description: getDoc(program, body),
|
||||
Type: inputType,
|
||||
Location: requestLocation,
|
||||
IsRequired: body.kind === "Model" ? true : !body.optional,
|
||||
IsApiVersion: false,
|
||||
IsResourceParameter: false,
|
||||
IsContentType: false,
|
||||
IsEndpoint: false,
|
||||
SkipUrlEncoding: false,
|
||||
Explode: false,
|
||||
Kind: kind,
|
||||
} as InputParameter;
|
||||
}
|
||||
|
||||
function loadOperationResponse(
|
||||
context: SdkContext<NetEmitterOptions>,
|
||||
response: HttpOperationResponse
|
||||
): OperationResponse | undefined {
|
||||
if (!response.statusCode || response.statusCode === "*") {
|
||||
return undefined;
|
||||
}
|
||||
const status: number[] = [];
|
||||
status.push(Number(response.statusCode));
|
||||
//TODO: what to do if more than 1 response?
|
||||
const body = response.responses[0]?.body;
|
||||
|
||||
let type: InputType | undefined = undefined;
|
||||
if (body?.type) {
|
||||
const typespecType = getEffectiveSchemaType(context, body.type);
|
||||
const inputType: InputType = getInputType(
|
||||
context,
|
||||
getFormattedType(program, typespecType),
|
||||
models,
|
||||
enums
|
||||
);
|
||||
type = inputType;
|
||||
}
|
||||
|
||||
const headers = response.responses[0]?.headers;
|
||||
const responseHeaders: HttpResponseHeader[] = [];
|
||||
if (headers) {
|
||||
for (const key of Object.keys(headers)) {
|
||||
responseHeaders.push({
|
||||
Name: key,
|
||||
NameInResponse: headers[key].name,
|
||||
Description: getDoc(program, headers[key]) ?? "",
|
||||
Type: getInputType(context, getFormattedType(program, headers[key].type), models, enums),
|
||||
} as HttpResponseHeader);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
StatusCodes: status,
|
||||
BodyType: type,
|
||||
BodyMediaType: BodyMediaType.Json,
|
||||
Headers: responseHeaders,
|
||||
IsErrorResponse: isErrorModel(program, response.type),
|
||||
ContentTypes: body?.contentTypes,
|
||||
} as OperationResponse;
|
||||
}
|
||||
|
||||
function loadLongRunningOperation(
|
||||
context: SdkContext<NetEmitterOptions>,
|
||||
op: HttpOperation
|
||||
): OperationLongRunning | undefined {
|
||||
const metadata = getLroMetadata(program, op.operation);
|
||||
if (metadata === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
var bodyType = undefined;
|
||||
if (
|
||||
op.verb !== "delete" &&
|
||||
metadata.finalResult !== undefined &&
|
||||
metadata.finalResult !== "void"
|
||||
) {
|
||||
const formattedType = getFormattedType(program, metadata.finalEnvelopeResult as Model);
|
||||
bodyType = getInputType(context, formattedType, models, enums);
|
||||
}
|
||||
|
||||
return {
|
||||
FinalStateVia: convertLroFinalStateVia(metadata.finalStateVia),
|
||||
FinalResponse: {
|
||||
// in swagger, we allow delete to return some meaningful body content
|
||||
// for now, let assume we don't allow return type
|
||||
StatusCodes: op.verb === "delete" ? [204] : [200],
|
||||
BodyType: bodyType,
|
||||
BodyMediaType: BodyMediaType.Json,
|
||||
} as OperationResponse,
|
||||
ResultPath: metadata.finalResultPath,
|
||||
} as OperationLongRunning;
|
||||
}
|
||||
}
|
||||
|
||||
function getOperationGroupName(
|
||||
context: SdkContext,
|
||||
operation: Operation,
|
||||
serviceNamespaceType: Namespace
|
||||
): string {
|
||||
const explicitOperationId = getOperationId(context, operation);
|
||||
if (explicitOperationId) {
|
||||
const ids: string[] = explicitOperationId.split("_");
|
||||
if (ids.length > 1) {
|
||||
return ids.slice(0, -2).join("_");
|
||||
}
|
||||
}
|
||||
|
||||
if (operation.interface) {
|
||||
return operation.interface.name;
|
||||
}
|
||||
let namespace = operation.namespace;
|
||||
if (!namespace) {
|
||||
namespace = context.program.checker.getGlobalNamespaceType() ?? serviceNamespaceType;
|
||||
}
|
||||
|
||||
if (namespace) return namespace.name;
|
||||
else return "";
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { ServiceAuthentication } from "@typespec/http";
|
||||
import { InputApiKeyAuth } from "../type/inputApiKeyAuth.js";
|
||||
import { InputAuth } from "../type/inputAuth.js";
|
||||
import { InputOAuth2Auth } from "../type/inputOAuth2Auth.js";
|
||||
import { logger } from "./logger.js";
|
||||
|
||||
export function processServiceAuthentication(authentication: ServiceAuthentication): InputAuth {
|
||||
const auth = {} as InputAuth;
|
||||
let scopes: Set<string> | undefined;
|
||||
|
||||
for (const option of authentication.options) {
|
||||
for (const scheme of option.schemes) {
|
||||
switch (scheme.type) {
|
||||
case "apiKey":
|
||||
auth.ApiKey = { Name: scheme.name } as InputApiKeyAuth;
|
||||
break;
|
||||
case "oauth2":
|
||||
for (const flow of scheme.flows) {
|
||||
if (flow.scopes) {
|
||||
scopes ??= new Set<string>();
|
||||
for (const scope of flow.scopes) {
|
||||
scopes.add(scope.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "http":
|
||||
const schemeOrApiKeyPrefix = scheme.scheme;
|
||||
if (schemeOrApiKeyPrefix === "basic") {
|
||||
logger.warn(`{schemeOrApiKeyPrefix} auth method is currently not supported.`);
|
||||
} else if (schemeOrApiKeyPrefix === "bearer") {
|
||||
auth.ApiKey = {
|
||||
Name: "Authorization",
|
||||
Prefix: "Bearer",
|
||||
} as InputApiKeyAuth;
|
||||
} else {
|
||||
auth.ApiKey = {
|
||||
Name: "Authorization",
|
||||
Prefix: schemeOrApiKeyPrefix,
|
||||
} as InputApiKeyAuth;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error("Not supported authentication.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (scopes) {
|
||||
auth.OAuth2 = {
|
||||
Scopes: Array.from(scopes.values()),
|
||||
} as InputOAuth2Auth;
|
||||
}
|
||||
|
||||
return auth;
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { SdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
import { getDoc, Type } from "@typespec/compiler";
|
||||
import { HttpServer } from "@typespec/http";
|
||||
import { NetEmitterOptions } from "../options.js";
|
||||
import { InputConstant } from "../type/inputConstant.js";
|
||||
import { InputOperationParameterKind } from "../type/inputOperationParameterKind.js";
|
||||
import { InputParameter } from "../type/inputParameter.js";
|
||||
import { InputPrimitiveTypeKind } from "../type/inputPrimitiveTypeKind.js";
|
||||
import { InputEnumType, InputModelType, InputPrimitiveType, InputType } from "../type/inputType.js";
|
||||
import { InputTypeKind } from "../type/inputTypeKind.js";
|
||||
import { RequestLocation } from "../type/requestLocation.js";
|
||||
import { getFormattedType, getInputType } from "./model.js";
|
||||
|
||||
export interface TypeSpecServer {
|
||||
url: string;
|
||||
description?: string;
|
||||
parameters: InputParameter[];
|
||||
}
|
||||
|
||||
function getDefaultValue(type: Type): any {
|
||||
switch (type.kind) {
|
||||
case "String":
|
||||
return type.value;
|
||||
case "Number":
|
||||
return type.value;
|
||||
case "Boolean":
|
||||
return type.value;
|
||||
case "Tuple":
|
||||
return type.values.map(getDefaultValue);
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveServers(
|
||||
context: SdkContext<NetEmitterOptions>,
|
||||
servers: HttpServer[],
|
||||
models: Map<string, InputModelType>,
|
||||
enums: Map<string, InputEnumType>
|
||||
): TypeSpecServer[] {
|
||||
return servers.map((server) => {
|
||||
const parameters: InputParameter[] = [];
|
||||
let url: string = server.url;
|
||||
const endpoint: string = url.replace("http://", "").replace("https://", "").split("/")[0];
|
||||
for (const [name, prop] of server.parameters) {
|
||||
const isEndpoint: boolean = endpoint === `{${name}}`;
|
||||
let defaultValue = undefined;
|
||||
const value = prop.default ? getDefaultValue(prop.default) : "";
|
||||
const inputType: InputType = isEndpoint
|
||||
? ({
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Uri,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType)
|
||||
: getInputType(context, getFormattedType(context.program, prop), models, enums);
|
||||
|
||||
if (value) {
|
||||
defaultValue = {
|
||||
Type: inputType,
|
||||
Value: value,
|
||||
} as InputConstant;
|
||||
}
|
||||
const variable: InputParameter = {
|
||||
Name: name,
|
||||
NameInRequest: name,
|
||||
Description: getDoc(context.program, prop),
|
||||
Type: inputType,
|
||||
Location: RequestLocation.Uri,
|
||||
IsApiVersion: name.toLowerCase() === "apiversion" || name.toLowerCase() === "api-version",
|
||||
IsResourceParameter: false,
|
||||
IsContentType: false,
|
||||
IsRequired: true,
|
||||
IsEndpoint: isEndpoint,
|
||||
SkipUrlEncoding: false,
|
||||
Explode: false,
|
||||
Kind: InputOperationParameterKind.Client,
|
||||
DefaultValue: defaultValue,
|
||||
};
|
||||
|
||||
parameters.push(variable);
|
||||
}
|
||||
/* add default server. */
|
||||
if (server.url && parameters.length === 0) {
|
||||
const variable: InputParameter = {
|
||||
Name: "host",
|
||||
NameInRequest: "host",
|
||||
Description: server.description,
|
||||
Type: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
Location: RequestLocation.Uri,
|
||||
IsApiVersion: false,
|
||||
IsResourceParameter: false,
|
||||
IsContentType: false,
|
||||
IsRequired: true,
|
||||
IsEndpoint: true,
|
||||
SkipUrlEncoding: false,
|
||||
Explode: false,
|
||||
Kind: InputOperationParameterKind.Client,
|
||||
DefaultValue: {
|
||||
Type: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
Value: server.url,
|
||||
} as InputConstant,
|
||||
};
|
||||
url = `{host}`;
|
||||
parameters.push(variable);
|
||||
}
|
||||
return {
|
||||
url: url,
|
||||
description: server.description,
|
||||
parameters,
|
||||
};
|
||||
});
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
import {
|
||||
SdkContext,
|
||||
getLibraryName,
|
||||
getSdkModel,
|
||||
} from "@azure-tools/typespec-client-generator-core";
|
||||
import {
|
||||
Enum,
|
||||
EnumMember,
|
||||
Model,
|
||||
ModelProperty,
|
||||
Namespace,
|
||||
Operation,
|
||||
Scalar,
|
||||
} from "@typespec/compiler";
|
||||
import { InputConstant } from "../type/inputConstant.js";
|
||||
import { InputOperationParameterKind } from "../type/inputOperationParameterKind.js";
|
||||
import { InputParameter } from "../type/inputParameter.js";
|
||||
import { InputPrimitiveTypeKind } from "../type/inputPrimitiveTypeKind.js";
|
||||
import { InputPrimitiveType, InputType } from "../type/inputType.js";
|
||||
import { InputTypeKind } from "../type/inputTypeKind.js";
|
||||
import { RequestLocation } from "../type/requestLocation.js";
|
||||
|
||||
export function capitalize(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export function getNameForTemplate(model: Model): string {
|
||||
if (model.name !== "" && model.templateMapper && model.templateMapper.args) {
|
||||
return model.name + model.templateMapper.args.map((it) => (it as Model).name).join("");
|
||||
}
|
||||
|
||||
return model.name;
|
||||
}
|
||||
|
||||
export function getTypeName(
|
||||
context: SdkContext,
|
||||
type: Model | Enum | EnumMember | ModelProperty | Scalar | Operation
|
||||
): string {
|
||||
var name = getLibraryName(context, type);
|
||||
if (type.kind !== "Model") return name;
|
||||
if (type.name === name) {
|
||||
var templateName = getNameForTemplate(type);
|
||||
if (templateName === "") {
|
||||
const sdkModel = getSdkModel(context, type as Model);
|
||||
return sdkModel.generatedName || sdkModel.name;
|
||||
}
|
||||
return templateName;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
export function getFullNamespaceString(namespace: Namespace | undefined): string {
|
||||
if (!namespace || !namespace.name) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let namespaceString: string = namespace.name;
|
||||
let current: Namespace | undefined = namespace.namespace;
|
||||
while (current && current.name) {
|
||||
namespaceString = `${current.name}.${namespaceString}`;
|
||||
current = current.namespace;
|
||||
}
|
||||
return namespaceString;
|
||||
}
|
||||
|
||||
export function createContentTypeOrAcceptParameter(
|
||||
mediaTypes: string[],
|
||||
name: string,
|
||||
nameInRequest: string
|
||||
): InputParameter {
|
||||
const isContentType: boolean = nameInRequest.toLowerCase() === "content-type";
|
||||
const inputType: InputType = {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType;
|
||||
return {
|
||||
Name: name,
|
||||
NameInRequest: nameInRequest,
|
||||
Type: inputType,
|
||||
Location: RequestLocation.Header,
|
||||
IsApiVersion: false,
|
||||
IsResourceParameter: false,
|
||||
IsContentType: isContentType,
|
||||
IsRequired: true,
|
||||
IsEndpoint: false,
|
||||
SkipUrlEncoding: false,
|
||||
Explode: false,
|
||||
Kind: InputOperationParameterKind.Constant,
|
||||
DefaultValue:
|
||||
mediaTypes.length === 1
|
||||
? ({
|
||||
Type: inputType,
|
||||
Value: mediaTypes[0],
|
||||
} as InputConstant)
|
||||
: undefined,
|
||||
} as InputParameter;
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
import { SdkEmitterOptions } from "@azure-tools/typespec-client-generator-core";
|
||||
import { EmitContext, JSONSchemaType, resolvePath } from "@typespec/compiler";
|
||||
import { tspOutputFileName } from "./constants.js";
|
||||
import { LoggerLevel } from "./lib/logger.js";
|
||||
|
||||
export type NetEmitterOptions = {
|
||||
outputFile?: string;
|
||||
logFile?: string;
|
||||
namespace: string;
|
||||
"library-name": string;
|
||||
"single-top-level-client"?: boolean;
|
||||
skipSDKGeneration?: boolean;
|
||||
"unreferenced-types-handling"?: "removeOrInternalize" | "internalize" | "keepAll";
|
||||
"new-project"?: boolean;
|
||||
csharpGeneratorPath?: string;
|
||||
"clear-output-folder"?: boolean;
|
||||
"save-inputs"?: boolean;
|
||||
"model-namespace"?: boolean;
|
||||
"existing-project-folder"?: string;
|
||||
"keep-non-overloadable-protocol-signature"?: boolean;
|
||||
debug?: boolean;
|
||||
"models-to-treat-empty-string-as-null"?: string[];
|
||||
"additional-intrinsic-types-to-treat-empty-string-as-null"?: string[];
|
||||
"methods-to-keep-client-default-value"?: string[];
|
||||
"deserialize-null-collection-as-null-value"?: boolean;
|
||||
logLevel?: string;
|
||||
"package-dir"?: string;
|
||||
"head-as-boolean"?: boolean;
|
||||
flavor?: string;
|
||||
"generate-sample-project"?: boolean;
|
||||
"generate-test-project"?: boolean;
|
||||
"use-model-reader-writer"?: boolean;
|
||||
} & SdkEmitterOptions;
|
||||
|
||||
export const NetEmitterOptionsSchema: JSONSchemaType<NetEmitterOptions> = {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
outputFile: { type: "string", nullable: true },
|
||||
logFile: { type: "string", nullable: true },
|
||||
namespace: { type: "string" },
|
||||
"library-name": { type: "string" },
|
||||
"single-top-level-client": { type: "boolean", nullable: true },
|
||||
skipSDKGeneration: { type: "boolean", default: false, nullable: true },
|
||||
"unreferenced-types-handling": {
|
||||
type: "string",
|
||||
enum: ["removeOrInternalize", "internalize", "keepAll"],
|
||||
nullable: true,
|
||||
},
|
||||
"new-project": { type: "boolean", nullable: true },
|
||||
csharpGeneratorPath: {
|
||||
type: "string",
|
||||
default: "fake-location",
|
||||
nullable: true,
|
||||
},
|
||||
"clear-output-folder": { type: "boolean", nullable: true },
|
||||
"save-inputs": { type: "boolean", nullable: true },
|
||||
"model-namespace": { type: "boolean", nullable: true },
|
||||
"generate-protocol-methods": { type: "boolean", nullable: true },
|
||||
"generate-convenience-methods": { type: "boolean", nullable: true },
|
||||
"filter-out-core-models": { type: "boolean", nullable: true },
|
||||
"package-name": { type: "string", nullable: true },
|
||||
"existing-project-folder": { type: "string", nullable: true },
|
||||
"keep-non-overloadable-protocol-signature": {
|
||||
type: "boolean",
|
||||
nullable: true,
|
||||
},
|
||||
debug: { type: "boolean", nullable: true },
|
||||
"models-to-treat-empty-string-as-null": {
|
||||
type: "array",
|
||||
nullable: true,
|
||||
items: { type: "string" },
|
||||
},
|
||||
"additional-intrinsic-types-to-treat-empty-string-as-null": {
|
||||
type: "array",
|
||||
nullable: true,
|
||||
items: { type: "string" },
|
||||
},
|
||||
"methods-to-keep-client-default-value": {
|
||||
type: "array",
|
||||
nullable: true,
|
||||
items: { type: "string" },
|
||||
},
|
||||
"deserialize-null-collection-as-null-value": {
|
||||
type: "boolean",
|
||||
nullable: true,
|
||||
},
|
||||
logLevel: {
|
||||
type: "string",
|
||||
enum: [
|
||||
LoggerLevel.ERROR,
|
||||
LoggerLevel.WARN,
|
||||
LoggerLevel.INFO,
|
||||
LoggerLevel.DEBUG,
|
||||
LoggerLevel.VERBOSE,
|
||||
],
|
||||
nullable: true,
|
||||
},
|
||||
"package-dir": { type: "string", nullable: true },
|
||||
"head-as-boolean": { type: "boolean", nullable: true },
|
||||
flavor: { type: "string", nullable: true },
|
||||
"generate-sample-project": {
|
||||
type: "boolean",
|
||||
nullable: true,
|
||||
default: true,
|
||||
},
|
||||
"generate-test-project": {
|
||||
type: "boolean",
|
||||
nullable: true,
|
||||
default: false,
|
||||
},
|
||||
"use-model-reader-writer": { type: "boolean", nullable: true },
|
||||
},
|
||||
required: [],
|
||||
};
|
||||
|
||||
const defaultOptions = {
|
||||
outputFile: tspOutputFileName,
|
||||
logFile: "log.json",
|
||||
skipSDKGeneration: false,
|
||||
"new-project": false,
|
||||
csharpGeneratorPath: "fake-location",
|
||||
"clear-output-folder": false,
|
||||
"save-inputs": false,
|
||||
"generate-protocol-methods": true,
|
||||
"generate-convenience-methods": true,
|
||||
"package-name": undefined,
|
||||
debug: undefined,
|
||||
"models-to-treat-empty-string-as-null": undefined,
|
||||
"additional-intrinsic-types-to-treat-empty-string-as-null": [],
|
||||
"methods-to-keep-client-default-value": undefined,
|
||||
"deserialize-null-collection-as-null-value": undefined,
|
||||
logLevel: LoggerLevel.INFO,
|
||||
flavor: undefined,
|
||||
"generate-test-project": false,
|
||||
};
|
||||
|
||||
export function resolveOptions(context: EmitContext<NetEmitterOptions>) {
|
||||
const emitterOptions = context.options;
|
||||
const emitterOutputDir = context.emitterOutputDir;
|
||||
const resolvedOptions = { ...defaultOptions, ...emitterOptions };
|
||||
|
||||
const outputFolder = resolveOutputFolder(context);
|
||||
return {
|
||||
...resolvedOptions,
|
||||
outputFile: resolvePath(outputFolder, resolvedOptions.outputFile),
|
||||
logFile: resolvePath(emitterOutputDir ?? "./tsp-output", resolvedOptions.logFile),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveOutputFolder(context: EmitContext<NetEmitterOptions>): string {
|
||||
return resolvePath(context.emitterOutputDir ?? "./tsp-output");
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
import { Type } from "@typespec/compiler";
|
||||
|
||||
export enum BodyMediaType {
|
||||
None = "None",
|
||||
Binary = "Binary",
|
||||
Form = "Form",
|
||||
Json = "Json",
|
||||
Multipart = "Multipart",
|
||||
Text = "Text",
|
||||
Xml = "Xml",
|
||||
}
|
||||
|
||||
export function typeToBodyMediaType(type: Type | undefined) {
|
||||
if (type === undefined) {
|
||||
return BodyMediaType.None;
|
||||
}
|
||||
|
||||
if (type.kind === "Model") {
|
||||
return BodyMediaType.Json;
|
||||
} else if (type.kind === "String") {
|
||||
return BodyMediaType.Text;
|
||||
} else if (type.kind === "Scalar" && type.name === "bytes") {
|
||||
return BodyMediaType.Binary;
|
||||
}
|
||||
return BodyMediaType.None;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum ClientKind {
|
||||
SdkClient = "SdkClient",
|
||||
SdkOperationGroup = "SdkOperationGroup",
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputAuth } from "./inputAuth.js";
|
||||
import { InputClient } from "./inputClient.js";
|
||||
import { InputEnumType, InputModelType } from "./inputType.js";
|
||||
|
||||
export interface CodeModel {
|
||||
Name: string;
|
||||
Description?: string;
|
||||
ApiVersions: string[];
|
||||
Enums: InputEnumType[];
|
||||
Models: InputModelType[];
|
||||
Clients: InputClient[];
|
||||
Auth?: InputAuth;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum CollectionFormat {
|
||||
CSV = "csv",
|
||||
Simple = "simple",
|
||||
SSV = "ssv",
|
||||
TSV = "tsv",
|
||||
Pipes = "pipes",
|
||||
Multi = "multi",
|
||||
Form = "form",
|
||||
}
|
||||
|
||||
export const collectionFormatToDelimMap: {
|
||||
[key: string]: string | undefined;
|
||||
} = {
|
||||
[CollectionFormat.CSV.toString()]: ",",
|
||||
[CollectionFormat.Simple.toString()]: ",", // csv and simple are used interchangeably
|
||||
[CollectionFormat.SSV.toString()]: " ",
|
||||
[CollectionFormat.TSV.toString()]: "\t",
|
||||
[CollectionFormat.Pipes.toString()]: "|",
|
||||
[CollectionFormat.Multi.toString()]: undefined,
|
||||
[CollectionFormat.Form.toString()]: undefined, // multi and form are used interchangeably
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface Configuration {
|
||||
"output-folder": string;
|
||||
namespace: string;
|
||||
"library-name": string | null;
|
||||
flavor?: string;
|
||||
"single-top-level-client"?: boolean;
|
||||
"unreferenced-types-handling"?: "removeOrInternalize" | "internalize" | "keepAll";
|
||||
"model-namespace"?: boolean;
|
||||
"models-to-treat-empty-string-as-null"?: string[];
|
||||
"additional-intrinsic-types-to-treat-empty-string-as-null"?: string[];
|
||||
"methods-to-keep-client-default-value"?: string[];
|
||||
"keep-non-overloadable-protocol-signature"?: boolean;
|
||||
"intrinsic-types-to-treat-empty-string-as-null"?: string[];
|
||||
"head-as-boolean"?: boolean;
|
||||
"deserialize-null-collection-as-null-value"?: boolean;
|
||||
"generate-sample-project"?: boolean;
|
||||
"generate-test-project"?: boolean;
|
||||
"use-model-reader-writer"?: boolean;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import {
|
||||
SdkContext,
|
||||
SdkEnumType,
|
||||
SdkEnumValueType,
|
||||
} from "@azure-tools/typespec-client-generator-core";
|
||||
import { Enum, UsageFlags } from "@typespec/compiler";
|
||||
import { setUsage } from "../lib/model.js";
|
||||
import { getFullNamespaceString } from "../lib/utils.js";
|
||||
import { InputEnumTypeValue } from "./inputEnumTypeValue.js";
|
||||
import { InputEnumType } from "./inputType.js";
|
||||
import { InputTypeKind } from "./inputTypeKind.js";
|
||||
import { Usage } from "./usage.js";
|
||||
|
||||
export function fromSdkEnumType(
|
||||
enumType: SdkEnumType,
|
||||
context: SdkContext,
|
||||
enums: Map<string, InputEnumType>,
|
||||
addToCollection: boolean = true
|
||||
): InputEnumType {
|
||||
let enumName = enumType.generatedName || enumType.name;
|
||||
let inputEnumType = enums.get(enumName);
|
||||
if (inputEnumType === undefined) {
|
||||
const newInputEnumType: InputEnumType = {
|
||||
Kind: InputTypeKind.Enum,
|
||||
Name: enumName,
|
||||
EnumValueType: enumType.valueType.kind,
|
||||
AllowedValues: enumType.values.map((v) => fromSdkEnumValueType(v)),
|
||||
Namespace: getFullNamespaceString((enumType.__raw! as Enum).namespace),
|
||||
Accessibility: enumType.access,
|
||||
Deprecated: enumType.deprecation,
|
||||
Description: enumType.description,
|
||||
IsExtensible: enumType.isFixed ? false : true,
|
||||
IsNullable: enumType.nullable,
|
||||
Usage: fromUsageFlags(enumType.usage),
|
||||
};
|
||||
setUsage(context, enumType.__raw! as Enum, newInputEnumType);
|
||||
if (addToCollection) enums.set(enumName, newInputEnumType);
|
||||
inputEnumType = newInputEnumType;
|
||||
}
|
||||
inputEnumType.IsNullable = enumType.nullable; // TO-DO: https://github.com/Azure/autorest.csharp/issues/4314
|
||||
return inputEnumType;
|
||||
}
|
||||
|
||||
export function fromSdkEnumValueType(enumValueType: SdkEnumValueType): InputEnumTypeValue {
|
||||
return {
|
||||
Name: enumValueType.name,
|
||||
Value: enumValueType.value,
|
||||
Description: enumValueType.description,
|
||||
} as InputEnumTypeValue;
|
||||
}
|
||||
|
||||
export function fromUsageFlags(usage: UsageFlags): Usage {
|
||||
if (usage === UsageFlags.Input) return Usage.Input;
|
||||
else if (usage === UsageFlags.Output) return Usage.Output;
|
||||
else if (usage === (UsageFlags.Input | UsageFlags.Output)) return Usage.RoundTrip;
|
||||
else return Usage.None;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface ExternalDocs {
|
||||
url: string;
|
||||
description?: string;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { EncodeData, Type } from "@typespec/compiler";
|
||||
export interface FormattedType {
|
||||
type: Type;
|
||||
format?: string;
|
||||
encode?: EncodeData;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputType } from "./inputType.js";
|
||||
|
||||
export interface HttpResponseHeader {
|
||||
Name: string;
|
||||
NameInResponse: string;
|
||||
Description: string;
|
||||
Type: InputType;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface InputApiKeyAuth {
|
||||
Name: string;
|
||||
Prefix?: string;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputApiKeyAuth } from "./inputApiKeyAuth.js";
|
||||
import { InputOAuth2Auth } from "./inputOAuth2Auth.js";
|
||||
|
||||
export interface InputAuth {
|
||||
ApiKey?: InputApiKeyAuth;
|
||||
OAuth2?: InputOAuth2Auth;
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputOperation } from "./inputOperation.js";
|
||||
import { InputParameter } from "./inputParameter.js";
|
||||
import { Protocols } from "./protocols.js";
|
||||
|
||||
export interface InputClient {
|
||||
Name: string;
|
||||
Description?: string;
|
||||
Operations: InputOperation[];
|
||||
Protocol?: Protocols;
|
||||
Parent?: string;
|
||||
Creatable: boolean;
|
||||
Parameters?: InputParameter[];
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputType } from "./inputType.js";
|
||||
|
||||
export interface InputConstant {
|
||||
Value?: any;
|
||||
Type: InputType;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface InputEnumTypeValue {
|
||||
Name: string;
|
||||
Value: any;
|
||||
Description?: string;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum InputIntrinsicTypeKind {
|
||||
Error = "ErrorType",
|
||||
Void = "void",
|
||||
Never = "never",
|
||||
Unknown = "unknown",
|
||||
Null = "null",
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputType } from "./inputType.js";
|
||||
|
||||
export interface InputModelProperty {
|
||||
Name: string;
|
||||
SerializedName: string;
|
||||
Description: string;
|
||||
Type: InputType;
|
||||
IsRequired: boolean;
|
||||
IsReadOnly: boolean;
|
||||
IsDiscriminator?: boolean;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface InputOAuth2Auth {
|
||||
Scopes?: string[];
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { BodyMediaType } from "./bodyMediaType.js";
|
||||
import { InputParameter } from "./inputParameter.js";
|
||||
import { OperationLongRunning } from "./operationLongRunning.js";
|
||||
import { OperationPaging } from "./operationPaging.js";
|
||||
import { OperationResponse } from "./operationResponse.js";
|
||||
import { RequestMethod } from "./requestMethod.js";
|
||||
|
||||
export interface Paging {
|
||||
NextLinkName?: string;
|
||||
ItemName: string;
|
||||
NextPageMethod?: string;
|
||||
}
|
||||
|
||||
export interface InputOperation {
|
||||
Name: string;
|
||||
ResourceName?: string;
|
||||
Summary?: string;
|
||||
Deprecated?: string;
|
||||
Description?: string;
|
||||
Accessibility?: string;
|
||||
Parameters: InputParameter[];
|
||||
Responses: OperationResponse[];
|
||||
HttpMethod: RequestMethod;
|
||||
RequestBodyMediaType: BodyMediaType;
|
||||
Uri: string;
|
||||
Path: string;
|
||||
ExternalDocsUrl?: string;
|
||||
RequestMediaTypes?: string[];
|
||||
BufferResponse: boolean;
|
||||
LongRunning?: OperationLongRunning;
|
||||
Paging?: OperationPaging;
|
||||
GenerateProtocolMethod: boolean;
|
||||
GenerateConvenienceMethod: boolean;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum InputOperationParameterKind {
|
||||
Method = "Method",
|
||||
Client = "Client",
|
||||
Constant = "Constant",
|
||||
Flattened = "Flattened",
|
||||
Spread = "Spread",
|
||||
Grouped = "Grouped",
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputConstant } from "./inputConstant.js";
|
||||
import { InputOperationParameterKind } from "./inputOperationParameterKind.js";
|
||||
import { InputType } from "./inputType.js";
|
||||
import { RequestLocation } from "./requestLocation.js";
|
||||
|
||||
//TODO: Define VirtualParameter for HLC
|
||||
export interface VirtualParameter {}
|
||||
export interface InputParameter {
|
||||
Name: string;
|
||||
NameInRequest: string;
|
||||
Description?: string;
|
||||
Type: InputType;
|
||||
Location: RequestLocation;
|
||||
DefaultValue?: InputConstant;
|
||||
VirtualParameter?: VirtualParameter; //for HLC, set null for typespec
|
||||
GroupedBy?: InputParameter;
|
||||
Kind: InputOperationParameterKind;
|
||||
IsRequired: boolean;
|
||||
IsApiVersion: boolean;
|
||||
IsResourceParameter: boolean;
|
||||
IsContentType: boolean;
|
||||
IsEndpoint: boolean;
|
||||
SkipUrlEncoding: boolean;
|
||||
Explode: boolean;
|
||||
ArraySerializationDelimiter?: string;
|
||||
HeaderCollectionPrefix?: string;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum InputPrimitiveTypeKind {
|
||||
AzureLocation = "AzureLocation",
|
||||
Boolean = "Boolean",
|
||||
BinaryData = "BinaryData",
|
||||
Bytes = "Bytes",
|
||||
BytesBase64Url = "BytesBase64Url",
|
||||
ContentType = "ContentType",
|
||||
Date = "Date",
|
||||
DateTime = "DateTime",
|
||||
DateTimeISO8601 = "DateTimeISO8601",
|
||||
DateTimeRFC1123 = "DateTimeRFC1123",
|
||||
DateTimeRFC3339 = "DateTimeRFC3339",
|
||||
DateTimeRFC7231 = "DateTimeRFC7231",
|
||||
DateTimeUnix = "DateTimeUnix",
|
||||
Decimal = "Decimal",
|
||||
Decimal128 = "Decimal128",
|
||||
DurationISO8601 = "DurationISO8601",
|
||||
DurationConstant = "DurationConstant",
|
||||
DurationSeconds = "DurationSeconds",
|
||||
DurationSecondsFloat = "DurationSecondsFloat",
|
||||
ETag = "Etag",
|
||||
Float32 = "Float32",
|
||||
Float64 = "Float64",
|
||||
Float128 = "Float128",
|
||||
Guid = "Guid",
|
||||
Int32 = "Int32",
|
||||
Int64 = "Int64",
|
||||
SafeInt = "SafeInt",
|
||||
IPAddress = "IPAddress",
|
||||
Object = "Object",
|
||||
RequestMethod = "RequestMethod",
|
||||
ResourceIdentifier = "ResourceIdentifier",
|
||||
ResourceType = "ResourceType",
|
||||
Stream = "Stream",
|
||||
String = "String",
|
||||
Time = "Time",
|
||||
Uri = "Uri",
|
||||
Enum = "Enum",
|
||||
SByte = "SByte", //int8
|
||||
Byte = "Byte", //uint8
|
||||
UnKnownKind = "UnknownKind",
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputEnumTypeValue } from "./inputEnumTypeValue.js";
|
||||
import { InputIntrinsicTypeKind } from "./inputIntrinsicTypeKind.js";
|
||||
import { InputModelProperty } from "./inputModelProperty.js";
|
||||
import { InputPrimitiveTypeKind } from "./inputPrimitiveTypeKind.js";
|
||||
import { InputTypeKind } from "./inputTypeKind.js";
|
||||
|
||||
export interface InputType {
|
||||
Name: string;
|
||||
Kind: InputTypeKind;
|
||||
IsNullable: boolean;
|
||||
}
|
||||
|
||||
export interface InputPrimitiveType extends InputType {
|
||||
Name: InputPrimitiveTypeKind;
|
||||
}
|
||||
|
||||
export interface InputLiteralType extends InputType {
|
||||
Kind: InputTypeKind.Literal;
|
||||
Name: InputTypeKind.Literal; // literal type does not really have a name right now, we just use its kind
|
||||
LiteralValueType: InputType;
|
||||
Value: any;
|
||||
}
|
||||
|
||||
export function isInputLiteralType(type: InputType): type is InputLiteralType {
|
||||
return type.Kind === InputTypeKind.Literal;
|
||||
}
|
||||
|
||||
export interface InputUnionType extends InputType {
|
||||
Kind: InputTypeKind.Union;
|
||||
Name: InputTypeKind.Union; // union type does not really have a name right now, we just use its kind
|
||||
UnionItemTypes: InputType[];
|
||||
}
|
||||
|
||||
export function isInputUnionType(type: InputType): type is InputUnionType {
|
||||
return type.Kind === InputTypeKind.Union;
|
||||
}
|
||||
|
||||
export interface InputModelType extends InputType {
|
||||
Kind: InputTypeKind.Model;
|
||||
Name: string;
|
||||
Namespace?: string;
|
||||
Accessibility?: string;
|
||||
Deprecated?: string;
|
||||
Description?: string;
|
||||
Usage: string;
|
||||
Properties: InputModelProperty[];
|
||||
BaseModel?: InputModelType;
|
||||
DiscriminatorPropertyName?: string;
|
||||
DiscriminatorValue?: string;
|
||||
DerivedModels?: InputModelType[];
|
||||
InheritedDictionaryType?: InputDictionaryType;
|
||||
}
|
||||
|
||||
export function isInputModelType(type: InputType): type is InputModelType {
|
||||
return type.Kind === InputTypeKind.Model;
|
||||
}
|
||||
|
||||
export interface InputEnumType extends InputType {
|
||||
Kind: InputTypeKind.Enum;
|
||||
Name: string;
|
||||
EnumValueType: string;
|
||||
AllowedValues: InputEnumTypeValue[];
|
||||
Namespace?: string;
|
||||
Accessibility?: string;
|
||||
Deprecated?: string;
|
||||
Description?: string;
|
||||
IsExtensible: boolean;
|
||||
Usage: string;
|
||||
}
|
||||
|
||||
export function isInputEnumType(type: InputType): type is InputEnumType {
|
||||
return type.Kind === InputTypeKind.Enum;
|
||||
}
|
||||
|
||||
export interface InputListType extends InputType {
|
||||
Kind: InputTypeKind.Array;
|
||||
Name: InputTypeKind.Array; // array type does not really have a name right now, we just use its kind
|
||||
ElementType: InputType;
|
||||
}
|
||||
|
||||
export function isInputListType(type: InputType): type is InputListType {
|
||||
return type.Kind === InputTypeKind.Array;
|
||||
}
|
||||
|
||||
export interface InputDictionaryType extends InputType {
|
||||
Kind: InputTypeKind.Dictionary;
|
||||
Name: InputTypeKind.Dictionary; // dictionary type does not really have a name right now, we just use its kind
|
||||
KeyType: InputType;
|
||||
ValueType: InputType;
|
||||
}
|
||||
|
||||
export function isInputDictionaryType(type: InputType): type is InputDictionaryType {
|
||||
return type.Kind === InputTypeKind.Dictionary;
|
||||
}
|
||||
|
||||
export interface InputIntrinsicType extends InputType {
|
||||
Kind: InputTypeKind.Intrinsic;
|
||||
Name: InputIntrinsicTypeKind;
|
||||
IsNullable: false;
|
||||
}
|
||||
|
||||
export function isInputIntrinsicType(type: InputType): type is InputIntrinsicType {
|
||||
return type.Kind === InputTypeKind.Intrinsic;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum InputTypeKind {
|
||||
Primitive = "Primitive",
|
||||
Literal = "Literal",
|
||||
Union = "Union",
|
||||
Model = "Model",
|
||||
Enum = "Enum",
|
||||
Array = "Array",
|
||||
Dictionary = "Dictionary",
|
||||
Intrinsic = "Intrinsic",
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum InputTypeSerializationFormat {
|
||||
Default,
|
||||
Base64Url,
|
||||
Byte,
|
||||
Date,
|
||||
Time,
|
||||
DateTime,
|
||||
DateTimeUnix,
|
||||
DateTimeRFC1123,
|
||||
Duration,
|
||||
DurationConstant,
|
||||
Json,
|
||||
Xml,
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface InputTypeValue {
|
||||
Name: string;
|
||||
Value: string;
|
||||
Description?: string;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface LiteralTypeContext {
|
||||
ModelName: string;
|
||||
PropertyName: string;
|
||||
Namespace?: string;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
import { FinalStateValue } from "@azure-tools/typespec-azure-core";
|
||||
|
||||
export enum OperationFinalStateVia {
|
||||
AzureAsyncOperation,
|
||||
Location,
|
||||
OriginalUri,
|
||||
OperationLocation,
|
||||
CustomLink,
|
||||
CustomOperationReference,
|
||||
NoResult,
|
||||
}
|
||||
|
||||
export function convertLroFinalStateVia(finalStateValue: FinalStateValue): OperationFinalStateVia {
|
||||
switch (finalStateValue) {
|
||||
case FinalStateValue.azureAsyncOperation:
|
||||
return OperationFinalStateVia.AzureAsyncOperation;
|
||||
// TODO: we don't have implementation of custom-link and custom-operation-reference yet
|
||||
// case FinalStateValue.customLink:
|
||||
// return OperationFinalStateVia.CustomLink;
|
||||
|
||||
// And right now some existing API specs are not correctly defined so that they are parsed
|
||||
// into `custom-operation-reference` which should be `operation-location`.
|
||||
// so let's fallback `custom-operation-reference` into `operation-location` as a work-around
|
||||
case FinalStateValue.customOperationReference:
|
||||
return OperationFinalStateVia.OperationLocation;
|
||||
case FinalStateValue.location:
|
||||
return OperationFinalStateVia.Location;
|
||||
case FinalStateValue.originalUri:
|
||||
return OperationFinalStateVia.OriginalUri;
|
||||
case FinalStateValue.operationLocation:
|
||||
return OperationFinalStateVia.OperationLocation;
|
||||
default:
|
||||
throw `Unsupported LRO final state value: ${finalStateValue}`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { OperationFinalStateVia } from "./operationFinalStateVia.js";
|
||||
import { OperationResponse } from "./operationResponse.js";
|
||||
|
||||
export interface OperationLongRunning {
|
||||
FinalStateVia: OperationFinalStateVia;
|
||||
FinalResponse: OperationResponse;
|
||||
ResultPath?: string;
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { InputOperation } from "./inputOperation.js";
|
||||
|
||||
export interface OperationPaging {
|
||||
NextLinkName?: string;
|
||||
ItemName?: string;
|
||||
NextLinkOperation?: InputOperation;
|
||||
NextLinkOperationRef?: (p: any) => void;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
import { BodyMediaType } from "./bodyMediaType.js";
|
||||
import { HttpResponseHeader } from "./httpResponseHeader.js";
|
||||
import { InputType } from "./inputType.js";
|
||||
|
||||
export interface OperationResponse {
|
||||
StatusCodes: number[];
|
||||
BodyType?: InputType;
|
||||
BodyMediaType: BodyMediaType;
|
||||
Headers: HttpResponseHeader[];
|
||||
ContentTypes?: string[];
|
||||
IsErrorResponse: boolean;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export interface Protocols {}
|
|
@ -0,0 +1,19 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum RequestLocation {
|
||||
None = "",
|
||||
Uri = "Uri",
|
||||
Path = "Path",
|
||||
Query = "Query",
|
||||
Header = "Header",
|
||||
Body = "Body",
|
||||
}
|
||||
|
||||
export const requestLocationMap: { [key: string]: RequestLocation } = {
|
||||
path: RequestLocation.Path,
|
||||
query: RequestLocation.Query,
|
||||
header: RequestLocation.Header,
|
||||
body: RequestLocation.Body,
|
||||
uri: RequestLocation.Uri,
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum RequestMethod {
|
||||
GET = "GET",
|
||||
POST = "POST",
|
||||
PUT = "PUT",
|
||||
PATCH = "PATCH",
|
||||
DELETE = "DELETE",
|
||||
HEAD = "HEAD",
|
||||
OPTIONS = "OPTIONS",
|
||||
TRACE = "TRACE",
|
||||
NONE = "",
|
||||
}
|
||||
export function parseHttpRequestMethod(method: string): RequestMethod {
|
||||
if (method.length === 3) {
|
||||
if (method.toLowerCase() === "get") return RequestMethod.GET;
|
||||
if (method.toLowerCase() === "put") return RequestMethod.PUT;
|
||||
} else if (method.length === 4) {
|
||||
if (method.toLowerCase() === "post") return RequestMethod.POST;
|
||||
if (method.toLowerCase() === "head") return RequestMethod.HEAD;
|
||||
} else {
|
||||
if (method.toLowerCase() === "patch") return RequestMethod.PATCH;
|
||||
if (method.toLowerCase() === "delete") return RequestMethod.DELETE;
|
||||
if (method.toLowerCase() === "options") return RequestMethod.OPTIONS;
|
||||
if (method.toLowerCase() === "trace") return RequestMethod.TRACE;
|
||||
}
|
||||
|
||||
return RequestMethod.NONE;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum Usage {
|
||||
None = "None",
|
||||
Input = "Input",
|
||||
Output = "Output",
|
||||
RoundTrip = "RoundTrip",
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
|
||||
export enum ValidationType {
|
||||
None,
|
||||
AssertNotNull,
|
||||
AssertNotNullOrEmpty,
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
import { TestHost } from "@typespec/compiler/testing";
|
||||
import { getAllHttpServices } from "@typespec/http";
|
||||
import assert from "assert";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { loadOperation } from "../../src/lib/operation.js";
|
||||
import { InputPrimitiveTypeKind } from "../../src/type/inputPrimitiveTypeKind.js";
|
||||
import { InputEnumType, InputModelType, InputPrimitiveType } from "../../src/type/inputType.js";
|
||||
import { InputTypeKind } from "../../src/type/inputTypeKind.js";
|
||||
import {
|
||||
createEmitterContext,
|
||||
createEmitterTestHost,
|
||||
createNetSdkContext,
|
||||
navigateModels,
|
||||
typeSpecCompile,
|
||||
} from "./utils/TestUtil.js";
|
||||
|
||||
describe("Test encode duration", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("encode iso8601 for duration query parameter ", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(
|
||||
@query
|
||||
@encode(DurationKnownEncoding.ISO8601)
|
||||
input: duration
|
||||
): NoContentResponse;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
const operation = loadOperation(
|
||||
sdkContext,
|
||||
services[0].operations[0],
|
||||
"",
|
||||
[],
|
||||
services[0].namespace,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationISO8601,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
operation.Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("encode seconds-int32 for duration query parameter ", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(
|
||||
@query
|
||||
@encode(DurationKnownEncoding.seconds, int32)
|
||||
input: duration
|
||||
): NoContentResponse;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
const operation = loadOperation(
|
||||
sdkContext,
|
||||
services[0].operations[0],
|
||||
"",
|
||||
[],
|
||||
services[0].namespace,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationSeconds,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
operation.Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("encode seconds-float for duration query parameter ", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(
|
||||
@query
|
||||
@encode(DurationKnownEncoding.seconds, float)
|
||||
input: duration
|
||||
): NoContentResponse;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
const operation = loadOperation(
|
||||
sdkContext,
|
||||
services[0].operations[0],
|
||||
"",
|
||||
[],
|
||||
services[0].namespace,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationSecondsFloat,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
operation.Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("encode iso8601 on duration model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model ISO8601DurationProperty {
|
||||
@encode(DurationKnownEncoding.ISO8601)
|
||||
value: duration;
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const durationProperty = modelMap.get("ISO8601DurationProperty");
|
||||
assert(durationProperty !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationISO8601,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
durationProperty.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("encode iso8601 on duration model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model ISO8601DurationProperty {
|
||||
@encode(DurationKnownEncoding.ISO8601)
|
||||
value: duration;
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const durationProperty = modelMap.get("ISO8601DurationProperty");
|
||||
assert(durationProperty !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationISO8601,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
durationProperty.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("encode seconds-int32 on duration model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Int32SecondsDurationProperty {
|
||||
@encode(DurationKnownEncoding.seconds, int32)
|
||||
value: duration;
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const durationProperty = modelMap.get("Int32SecondsDurationProperty");
|
||||
assert(durationProperty !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationSeconds,
|
||||
IsNullable: false,
|
||||
},
|
||||
durationProperty.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("encode seconds-int32 on duration model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model FloatSecondsDurationProperty {
|
||||
@encode(DurationKnownEncoding.seconds, float)
|
||||
value: duration;
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const durationProperty = modelMap.get("FloatSecondsDurationProperty");
|
||||
assert(durationProperty !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.DurationSecondsFloat,
|
||||
IsNullable: false,
|
||||
},
|
||||
durationProperty.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,771 @@
|
|||
import { TestHost } from "@typespec/compiler/testing";
|
||||
import { getAllHttpServices } from "@typespec/http";
|
||||
import assert from "assert";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { createModel } from "../../src/lib/clientModelBuilder.js";
|
||||
import { InputIntrinsicTypeKind } from "../../src/type/inputIntrinsicTypeKind.js";
|
||||
import { InputModelProperty } from "../../src/type/inputModelProperty.js";
|
||||
import { InputPrimitiveTypeKind } from "../../src/type/inputPrimitiveTypeKind.js";
|
||||
import { InputDictionaryType, InputEnumType, InputModelType } from "../../src/type/inputType.js";
|
||||
import { InputTypeKind } from "../../src/type/inputTypeKind.js";
|
||||
import {
|
||||
createEmitterContext,
|
||||
createEmitterTestHost,
|
||||
createNetSdkContext,
|
||||
navigateModels,
|
||||
typeSpecCompile,
|
||||
} from "./utils/TestUtil.js";
|
||||
|
||||
describe("Discriminator property", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("Base model has discriminator property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("The base Pet model")
|
||||
@discriminator("kind")
|
||||
model Pet {
|
||||
@doc("The name of the pet")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("The cat")
|
||||
model Cat extends Pet {
|
||||
kind: "cat";
|
||||
|
||||
@doc("Meow")
|
||||
meow: string;
|
||||
}
|
||||
|
||||
@doc("The dog")
|
||||
model Dog extends Pet {
|
||||
kind: "dog";
|
||||
|
||||
@doc("Woof")
|
||||
woof: string;
|
||||
}
|
||||
|
||||
op test(@body input: Pet): Pet;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
runner.compileAndDiagnose;
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
const models = root.Models;
|
||||
const petModel = models.find((m) => m.Name === "Pet");
|
||||
const catModel = models.find((m) => m.Name === "Cat");
|
||||
const dogModel = models.find((m) => m.Name === "Dog");
|
||||
// assert the discriminator property name
|
||||
assert(
|
||||
isEqual("kind", petModel?.DiscriminatorPropertyName),
|
||||
`Discriminator property name is not correct, got ${petModel?.DiscriminatorPropertyName}`
|
||||
);
|
||||
// assert we have a property corresponding to the discriminator property above on the base model
|
||||
const discriminatorProperty = petModel?.Properties.find(
|
||||
(p) => p.Name === petModel?.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Name: "kind",
|
||||
SerializedName: "kind",
|
||||
Type: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
IsRequired: true,
|
||||
IsReadOnly: false,
|
||||
IsDiscriminator: true,
|
||||
Description: "Discriminator",
|
||||
} as InputModelProperty,
|
||||
discriminatorProperty
|
||||
),
|
||||
`Discriminator property is not correct, got ${JSON.stringify(discriminatorProperty)}`
|
||||
);
|
||||
// assert we will NOT have a DiscriminatorPropertyName on the derived models
|
||||
assert(
|
||||
catModel?.DiscriminatorPropertyName === undefined,
|
||||
"Cat model should not have the discriminator property name"
|
||||
);
|
||||
assert(
|
||||
dogModel?.DiscriminatorPropertyName === undefined,
|
||||
"Dog model should not have the discriminator property name"
|
||||
);
|
||||
// assert we will NOT have a property corresponding to the discriminator property on the derived models
|
||||
const catDiscriminatorProperty = catModel?.Properties.find(
|
||||
(p) => p.Name === petModel?.DiscriminatorPropertyName
|
||||
);
|
||||
const dogDiscriminatorProperty = dogModel?.Properties.find(
|
||||
(p) => p.Name === petModel?.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
catDiscriminatorProperty === undefined,
|
||||
"Cat model should not have the discriminator property"
|
||||
);
|
||||
assert(
|
||||
dogDiscriminatorProperty === undefined,
|
||||
"Dog model should not have the discriminator property"
|
||||
);
|
||||
});
|
||||
|
||||
it("Discriminator property is enum with no enum value defined", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("The pet kind")
|
||||
enum PetKind {
|
||||
Cat,
|
||||
Dog,
|
||||
}
|
||||
@doc("The base Pet model")
|
||||
@discriminator("kind")
|
||||
model Pet {
|
||||
@doc("The kind of the pet")
|
||||
kind: PetKind;
|
||||
@doc("The name of the pet")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("The cat")
|
||||
model Cat extends Pet{
|
||||
kind: PetKind.Cat;
|
||||
|
||||
@doc("Meow")
|
||||
meow: string;
|
||||
}
|
||||
|
||||
@doc("The dog")
|
||||
model Dog extends Pet{
|
||||
kind: PetKind.Dog;
|
||||
|
||||
@doc("Woof")
|
||||
woof: string;
|
||||
}
|
||||
|
||||
op test(@body input: Pet): Pet;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const pet = modelMap.get("Pet");
|
||||
assert(pet !== undefined);
|
||||
// assert the discriminator property name
|
||||
assert(
|
||||
isEqual("kind", pet?.DiscriminatorPropertyName),
|
||||
`Discriminator property name is not correct, got ${pet?.DiscriminatorPropertyName}`
|
||||
);
|
||||
// assert we have a property corresponding to the discriminator property above on the base model
|
||||
const discriminatorProperty = pet?.Properties.find(
|
||||
(p) => p.Name === pet?.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Name: "kind",
|
||||
SerializedName: "kind",
|
||||
Description: "The kind of the pet",
|
||||
Type: {
|
||||
Kind: InputTypeKind.Enum,
|
||||
Name: "PetKind",
|
||||
Namespace: "Azure.Csharp.Testing",
|
||||
Description: "The pet kind",
|
||||
Accessibility: undefined,
|
||||
Deprecated: undefined,
|
||||
EnumValueType: "String",
|
||||
AllowedValues: [
|
||||
{
|
||||
Name: "Cat",
|
||||
Value: "Cat",
|
||||
Description: undefined,
|
||||
},
|
||||
{
|
||||
Name: "Dog",
|
||||
Value: "Dog",
|
||||
Description: undefined,
|
||||
},
|
||||
],
|
||||
IsExtensible: false,
|
||||
IsNullable: false,
|
||||
Usage: "None",
|
||||
},
|
||||
IsRequired: true,
|
||||
IsReadOnly: false,
|
||||
IsDiscriminator: true,
|
||||
} as InputModelProperty,
|
||||
discriminatorProperty
|
||||
),
|
||||
`Discriminator property is not correct, got ${JSON.stringify(discriminatorProperty)}`
|
||||
);
|
||||
|
||||
// verify derived model Cat
|
||||
const cat = modelMap.get("Cat");
|
||||
assert(cat !== undefined);
|
||||
assert(cat.DiscriminatorValue === "Cat");
|
||||
assert(cat.BaseModel === pet);
|
||||
// assert we will NOT have a DiscriminatorPropertyName on the derived models
|
||||
assert(
|
||||
cat.DiscriminatorPropertyName === undefined,
|
||||
"Cat model should not have the discriminator property name"
|
||||
);
|
||||
// assert we will NOT have a property corresponding to the discriminator property on the derived models
|
||||
const catDiscriminatorProperty = cat.Properties.find(
|
||||
(p) => p.Name === pet.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
catDiscriminatorProperty === undefined,
|
||||
"Cat model should not have the discriminator property"
|
||||
);
|
||||
|
||||
// verify derived model Dog
|
||||
const dog = modelMap.get("Dog");
|
||||
assert(dog !== undefined);
|
||||
assert(dog.DiscriminatorValue === "Dog");
|
||||
assert(dog.BaseModel === pet);
|
||||
// assert we will NOT have a DiscriminatorPropertyName on the derived models
|
||||
assert(
|
||||
dog.DiscriminatorPropertyName === undefined,
|
||||
"Dog model should not have the discriminator property name"
|
||||
);
|
||||
// assert we will NOT have a property corresponding to the discriminator property on the derived models
|
||||
const dogDiscriminatorProperty = dog.Properties.find(
|
||||
(p) => p.Name === pet.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
dogDiscriminatorProperty === undefined,
|
||||
"Dog model should not have the discriminator property"
|
||||
);
|
||||
});
|
||||
|
||||
it("Discriminator property is enum with enum value defined", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("The pet kind")
|
||||
enum PetKind {
|
||||
Cat : "cat",
|
||||
Dog : "dog",
|
||||
}
|
||||
@doc("The base Pet model")
|
||||
@discriminator("kind")
|
||||
model Pet {
|
||||
@doc("The kind of the pet")
|
||||
kind: PetKind;
|
||||
@doc("The name of the pet")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("The cat")
|
||||
model Cat extends Pet{
|
||||
kind: PetKind.Cat;
|
||||
|
||||
@doc("Meow")
|
||||
meow: string;
|
||||
}
|
||||
|
||||
@doc("The dog")
|
||||
model Dog extends Pet{
|
||||
kind: PetKind.Dog;
|
||||
|
||||
@doc("Woof")
|
||||
woof: string;
|
||||
}
|
||||
|
||||
op test(@body input: Pet): Pet;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const pet = modelMap.get("Pet");
|
||||
assert(pet !== undefined);
|
||||
// assert the discriminator property name
|
||||
assert(
|
||||
isEqual("kind", pet?.DiscriminatorPropertyName),
|
||||
`Discriminator property name is not correct, got ${pet?.DiscriminatorPropertyName}`
|
||||
);
|
||||
// assert we have a property corresponding to the discriminator property above on the base model
|
||||
const discriminatorProperty = pet?.Properties.find(
|
||||
(p) => p.Name === pet?.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Name: "kind",
|
||||
SerializedName: "kind",
|
||||
Description: "The kind of the pet",
|
||||
Type: {
|
||||
Kind: InputTypeKind.Enum,
|
||||
Name: "PetKind",
|
||||
Namespace: "Azure.Csharp.Testing",
|
||||
Accessibility: undefined,
|
||||
Deprecated: undefined,
|
||||
Description: "The pet kind",
|
||||
EnumValueType: "String",
|
||||
AllowedValues: [
|
||||
{
|
||||
Name: "Cat",
|
||||
Value: "cat",
|
||||
Description: undefined,
|
||||
},
|
||||
{
|
||||
Name: "Dog",
|
||||
Value: "dog",
|
||||
Description: undefined,
|
||||
},
|
||||
],
|
||||
IsExtensible: false,
|
||||
IsNullable: false,
|
||||
Usage: "None",
|
||||
},
|
||||
IsRequired: true,
|
||||
IsReadOnly: false,
|
||||
IsDiscriminator: true,
|
||||
} as InputModelProperty,
|
||||
discriminatorProperty
|
||||
),
|
||||
`Discriminator property is not correct, got ${JSON.stringify(discriminatorProperty)}`
|
||||
);
|
||||
|
||||
// verify derived model Cat
|
||||
const cat = modelMap.get("Cat");
|
||||
assert(cat !== undefined);
|
||||
assert(cat.DiscriminatorValue === "cat");
|
||||
assert(cat.BaseModel === pet);
|
||||
// assert we will NOT have a DiscriminatorPropertyName on the derived models
|
||||
assert(
|
||||
cat.DiscriminatorPropertyName === undefined,
|
||||
"Cat model should not have the discriminator property name"
|
||||
);
|
||||
// assert we will NOT have a property corresponding to the discriminator property on the derived models
|
||||
const catDiscriminatorProperty = cat.Properties.find(
|
||||
(p) => p.Name === pet.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
catDiscriminatorProperty === undefined,
|
||||
"Cat model should not have the discriminator property"
|
||||
);
|
||||
|
||||
// verify derived model Dog
|
||||
const dog = modelMap.get("Dog");
|
||||
assert(dog !== undefined);
|
||||
assert(dog.DiscriminatorValue === "dog");
|
||||
assert(dog.BaseModel === pet);
|
||||
// assert we will NOT have a DiscriminatorPropertyName on the derived models
|
||||
assert(
|
||||
dog.DiscriminatorPropertyName === undefined,
|
||||
"Dog model should not have the discriminator property name"
|
||||
);
|
||||
// assert we will NOT have a property corresponding to the discriminator property on the derived models
|
||||
const dogDiscriminatorProperty = dog.Properties.find(
|
||||
(p) => p.Name === pet.DiscriminatorPropertyName
|
||||
);
|
||||
assert(
|
||||
dogDiscriminatorProperty === undefined,
|
||||
"Dog model should not have the discriminator property"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Additional Properties property should work with extends syntax", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("Model extends Record should have additional properties property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("Extends Record<unknown>")
|
||||
model ExtendsUnknown extends Record<unknown> {
|
||||
@doc("The name.")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("Extends Record<string>")
|
||||
model ExtendsString extends Record<string> {
|
||||
@doc("The name.")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("Extends Record<int32>")
|
||||
model ExtendsInt32 extends Record<int32> {
|
||||
@doc("The name.")
|
||||
name: int32;
|
||||
}
|
||||
|
||||
@doc("Extends Record<Foo>")
|
||||
model ExtendsFoo extends Record<Foo> {
|
||||
@doc("The name.")
|
||||
name: Foo;
|
||||
}
|
||||
|
||||
@doc("Extends Record<Foo[]>")
|
||||
model ExtendsFooArray extends Record<Foo[]> {
|
||||
@doc("The name.")
|
||||
name: Foo[];
|
||||
}
|
||||
|
||||
@doc("The Foo")
|
||||
model Foo {
|
||||
@doc("The name.")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@route("/op1")
|
||||
op op1(@body body: ExtendsUnknown): ExtendsUnknown;
|
||||
|
||||
@route("/op2")
|
||||
op op2(@body body: ExtendsString): ExtendsString;
|
||||
|
||||
@route("/op3")
|
||||
op op3(@body body: ExtendsInt32): ExtendsInt32;
|
||||
|
||||
@route("/op4")
|
||||
op op4(@body body: ExtendsFoo): ExtendsFoo;
|
||||
|
||||
@route("/op5")
|
||||
op op5(@body body: ExtendsFooArray): ExtendsFooArray;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
runner.compileAndDiagnose;
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
const models = root.Models;
|
||||
const extendsUnknownModel = models.find((m) => m.Name === "ExtendsUnknown");
|
||||
const extendsStringModel = models.find((m) => m.Name === "ExtendsString");
|
||||
const extendsInt32Model = models.find((m) => m.Name === "ExtendsInt32");
|
||||
const extendsFooModel = models.find((m) => m.Name === "ExtendsFoo");
|
||||
const extendsFooArrayModel = models.find((m) => m.Name === "ExtendsFooArray");
|
||||
const fooModel = models.find((m) => m.Name === "Foo");
|
||||
assert(extendsUnknownModel !== undefined);
|
||||
assert(extendsStringModel !== undefined);
|
||||
assert(extendsInt32Model !== undefined);
|
||||
assert(extendsFooModel !== undefined);
|
||||
assert(extendsFooArrayModel !== undefined);
|
||||
// assert the inherited dictionary type is expected
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Intrinsic,
|
||||
Name: InputIntrinsicTypeKind.Unknown,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
extendsUnknownModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
extendsUnknownModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
extendsStringModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
extendsStringModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Int32,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
extendsInt32Model.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
extendsInt32Model.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: fooModel,
|
||||
} as InputDictionaryType,
|
||||
extendsFooModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
extendsFooModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Array,
|
||||
Name: InputTypeKind.Array,
|
||||
ElementType: fooModel,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
extendsFooArrayModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
extendsFooArrayModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Additional Properties property should work with is syntax", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("Model is Record should have additional properties property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("Is Record<unknown>")
|
||||
model IsUnknown is Record<unknown> {
|
||||
@doc("The name.")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("Is Record<string>")
|
||||
model IsString is Record<string> {
|
||||
@doc("The name.")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("Is Record<int32>")
|
||||
model IsInt32 is Record<int32> {
|
||||
@doc("The name.")
|
||||
name: int32;
|
||||
}
|
||||
|
||||
@doc("Is Record<Foo>")
|
||||
model IsFoo is Record<Foo> {
|
||||
@doc("The name.")
|
||||
name: Foo;
|
||||
}
|
||||
|
||||
@doc("Is Record<Foo[]>")
|
||||
model IsFooArray is Record<Foo[]> {
|
||||
@doc("The name.")
|
||||
name: Foo[];
|
||||
}
|
||||
|
||||
@doc("The Foo")
|
||||
model Foo {
|
||||
@doc("The name.")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@route("/op1")
|
||||
op op1(@body body: IsUnknown): IsUnknown;
|
||||
|
||||
@route("/op2")
|
||||
op op2(@body body: IsString): IsString;
|
||||
|
||||
@route("/op3")
|
||||
op op3(@body body: IsInt32): IsInt32;
|
||||
|
||||
@route("/op4")
|
||||
op op4(@body body: IsFoo): IsFoo;
|
||||
|
||||
@route("/op5")
|
||||
op op5(@body body: IsFooArray): IsFooArray;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
runner.compileAndDiagnose;
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
const models = root.Models;
|
||||
const isUnknownModel = models.find((m) => m.Name === "IsUnknown");
|
||||
const isStringModel = models.find((m) => m.Name === "IsString");
|
||||
const isInt32Model = models.find((m) => m.Name === "IsInt32");
|
||||
const isFooModel = models.find((m) => m.Name === "IsFoo");
|
||||
const isFooArrayModel = models.find((m) => m.Name === "IsFooArray");
|
||||
const fooModel = models.find((m) => m.Name === "Foo");
|
||||
assert(isUnknownModel !== undefined);
|
||||
assert(isStringModel !== undefined);
|
||||
assert(isInt32Model !== undefined);
|
||||
assert(isFooModel !== undefined);
|
||||
assert(isFooArrayModel !== undefined);
|
||||
// assert the inherited dictionary type is expected
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Intrinsic,
|
||||
Name: InputIntrinsicTypeKind.Unknown,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
isUnknownModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
isUnknownModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
isStringModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
isStringModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Int32,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
isInt32Model.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
isInt32Model.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: fooModel,
|
||||
} as InputDictionaryType,
|
||||
isFooModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
isFooModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Dictionary,
|
||||
Name: InputTypeKind.Dictionary,
|
||||
IsNullable: false,
|
||||
KeyType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
ValueType: {
|
||||
Kind: InputTypeKind.Array,
|
||||
Name: InputTypeKind.Array,
|
||||
ElementType: fooModel,
|
||||
IsNullable: false,
|
||||
},
|
||||
} as InputDictionaryType,
|
||||
isFooArrayModel.InheritedDictionaryType
|
||||
),
|
||||
`Inherited dictionary type is not correct, got ${JSON.stringify(
|
||||
isFooArrayModel.InheritedDictionaryType
|
||||
)}`
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
import { FinalStateValue } from "@azure-tools/typespec-azure-core";
|
||||
import assert from "assert";
|
||||
import { describe } from "mocha";
|
||||
import {
|
||||
OperationFinalStateVia,
|
||||
convertLroFinalStateVia,
|
||||
} from "../../src/type/operationFinalStateVia.js";
|
||||
|
||||
describe("convertLroFinalStateVia()", () => {
|
||||
describe("normal inputs", () => {
|
||||
const mappings: [FinalStateValue, OperationFinalStateVia][] = [
|
||||
[FinalStateValue.azureAsyncOperation, OperationFinalStateVia.AzureAsyncOperation],
|
||||
[FinalStateValue.location, OperationFinalStateVia.Location],
|
||||
[FinalStateValue.originalUri, OperationFinalStateVia.OriginalUri],
|
||||
[FinalStateValue.operationLocation, OperationFinalStateVia.OperationLocation],
|
||||
];
|
||||
for (const [input, output] of mappings) {
|
||||
it(`should return '${output}' for '${input}'`, function () {
|
||||
assert.equal(convertLroFinalStateVia(input), output);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("unsupported inputs", () => {
|
||||
const unsupportedInputs = [FinalStateValue.customLink];
|
||||
for (const input of unsupportedInputs) {
|
||||
it(`should throw exception for unsupported input '${input}'`, function () {
|
||||
assert.throws(
|
||||
() => convertLroFinalStateVia(input),
|
||||
new RegExp(`Unsupported LRO final state value: ${input}`)
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,257 @@
|
|||
import { TestHost } from "@typespec/compiler/testing";
|
||||
import assert, { deepStrictEqual } from "assert";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { createModel } from "../../src/lib/clientModelBuilder.js";
|
||||
import { InputPrimitiveTypeKind } from "../../src/type/inputPrimitiveTypeKind.js";
|
||||
import { InputEnumType, InputListType } from "../../src/type/inputType.js";
|
||||
import { InputTypeKind } from "../../src/type/inputTypeKind.js";
|
||||
import {
|
||||
createEmitterContext,
|
||||
createEmitterTestHost,
|
||||
createNetSdkContext,
|
||||
typeSpecCompile,
|
||||
} from "./utils/TestUtil.js";
|
||||
|
||||
describe("Test GetInputType for array", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("array as request", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(@body input: string[]): string[];
|
||||
`,
|
||||
runner
|
||||
);
|
||||
runner.compileAndDiagnose;
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
deepStrictEqual(root.Clients[0].Operations[0].Parameters[0].Type.Kind, InputTypeKind.Array);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Array,
|
||||
Name: InputTypeKind.Array,
|
||||
ElementType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
IsNullable: false,
|
||||
} as InputListType,
|
||||
root.Clients[0].Operations[0].Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("array as response", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(): string[];
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
deepStrictEqual(root.Clients[0].Operations[0].Responses[0].BodyType?.Kind, InputTypeKind.Array);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Array,
|
||||
Name: InputTypeKind.Array,
|
||||
ElementType: {
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.String,
|
||||
IsNullable: false,
|
||||
},
|
||||
IsNullable: false,
|
||||
} as InputListType,
|
||||
root.Clients[0].Operations[0].Responses[0].BodyType
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Test GetInputType for enum", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("Fixed string enum", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
#suppress "@azure-tools/typespec-azure-core/use-extensible-enum" "Enums should be defined without the @fixed decorator."
|
||||
@doc("fixed string enum")
|
||||
@fixed
|
||||
enum SimpleEnum {
|
||||
@doc("Enum value one")
|
||||
One: "1",
|
||||
@doc("Enum value two")
|
||||
Two: "2",
|
||||
@doc("Enum value four")
|
||||
Four: "4"
|
||||
}
|
||||
#suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Operation 'test' should be defined using a signature from the Azure.Core namespace."
|
||||
@doc("test fixed enum.")
|
||||
op test(@doc("fixed enum as input.")@body input: SimpleEnum): string[];
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Enum,
|
||||
Name: "SimpleEnum",
|
||||
Namespace: "Azure.Csharp.Testing",
|
||||
Accessibility: undefined,
|
||||
Deprecated: undefined,
|
||||
Description: "fixed string enum",
|
||||
EnumValueType: "String",
|
||||
AllowedValues: [
|
||||
{
|
||||
Name: "One",
|
||||
Value: "1",
|
||||
Description: "Enum value one",
|
||||
},
|
||||
{
|
||||
Name: "Two",
|
||||
Value: "2",
|
||||
Description: "Enum value two",
|
||||
},
|
||||
{
|
||||
Name: "Four",
|
||||
Value: "4",
|
||||
Description: "Enum value four",
|
||||
},
|
||||
],
|
||||
IsExtensible: false,
|
||||
IsNullable: false,
|
||||
Usage: "Input",
|
||||
} as InputEnumType,
|
||||
root.Clients[0].Operations[0].Parameters[0].Type
|
||||
)
|
||||
);
|
||||
const type = root.Clients[0].Operations[0].Parameters[0].Type as InputEnumType;
|
||||
assert(type.EnumValueType !== undefined);
|
||||
deepStrictEqual(type.Name, "SimpleEnum");
|
||||
deepStrictEqual(type.IsExtensible, false);
|
||||
});
|
||||
it("Fixed int enum", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
#suppress "@azure-tools/typespec-azure-core/use-extensible-enum" "Enums should be defined without the @fixed decorator."
|
||||
@doc("Fixed int enum")
|
||||
@fixed
|
||||
enum FixedIntEnum {
|
||||
@doc("Enum value one")
|
||||
One: 1,
|
||||
@doc("Enum value two")
|
||||
Two: 2,
|
||||
@doc("Enum value four")
|
||||
Four: 4
|
||||
}
|
||||
#suppress "@azure-tools/typespec-azure-core/use-standard-operations" "Operation 'test' should be defined using a signature from the Azure.Core namespace."
|
||||
@doc("test fixed enum.")
|
||||
op test(@doc("fixed enum as input.")@body input: FixedIntEnum): string[];
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Enum,
|
||||
Name: "FixedIntEnum",
|
||||
Namespace: "Azure.Csharp.Testing",
|
||||
Accessibility: undefined,
|
||||
Deprecated: undefined,
|
||||
Description: "Fixed int enum",
|
||||
EnumValueType: "Float32",
|
||||
AllowedValues: [
|
||||
{
|
||||
Name: "One",
|
||||
Value: 1,
|
||||
Description: "Enum value one",
|
||||
},
|
||||
{
|
||||
Name: "Two",
|
||||
Value: 2,
|
||||
Description: "Enum value two",
|
||||
},
|
||||
{
|
||||
Name: "Four",
|
||||
Value: 4,
|
||||
Description: "Enum value four",
|
||||
},
|
||||
],
|
||||
IsExtensible: false,
|
||||
IsNullable: false,
|
||||
Usage: "Input",
|
||||
} as InputEnumType,
|
||||
root.Clients[0].Operations[0].Parameters[0].Type
|
||||
)
|
||||
);
|
||||
const type = root.Clients[0].Operations[0].Parameters[0].Type as InputEnumType;
|
||||
assert(type.EnumValueType !== undefined);
|
||||
deepStrictEqual(type.Name, "FixedIntEnum");
|
||||
deepStrictEqual(type.IsExtensible, false);
|
||||
});
|
||||
|
||||
it("fixed enum", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("Fixed enum")
|
||||
enum FixedEnum {
|
||||
One: "1",
|
||||
Two: "2",
|
||||
Four: "4"
|
||||
}
|
||||
op test(@body input: FixedEnum): string[];
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Enum,
|
||||
Name: "FixedEnum",
|
||||
Namespace: "Azure.Csharp.Testing",
|
||||
Accessibility: undefined,
|
||||
Deprecated: undefined,
|
||||
Description: "Fixed enum",
|
||||
EnumValueType: "String",
|
||||
AllowedValues: [
|
||||
{ Name: "One", Value: "1", Description: undefined },
|
||||
{ Name: "Two", Value: "2", Description: undefined },
|
||||
{ Name: "Four", Value: "4", Description: undefined },
|
||||
],
|
||||
IsExtensible: false,
|
||||
IsNullable: false,
|
||||
Usage: "Input",
|
||||
} as InputEnumType,
|
||||
root.Clients[0].Operations[0].Parameters[0].Type
|
||||
)
|
||||
);
|
||||
const type = root.Clients[0].Operations[0].Parameters[0].Type as InputEnumType;
|
||||
assert(type.EnumValueType !== undefined);
|
||||
deepStrictEqual(type.Name, "FixedEnum");
|
||||
deepStrictEqual((type as InputEnumType).IsExtensible, false);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,49 @@
|
|||
import { TestHost } from "@typespec/compiler/testing";
|
||||
import assert, { deepStrictEqual } from "assert";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { createModel } from "../../src/lib/clientModelBuilder.js";
|
||||
import { InputPrimitiveTypeKind } from "../../src/type/inputPrimitiveTypeKind.js";
|
||||
import { InputTypeKind } from "../../src/type/inputTypeKind.js";
|
||||
import {
|
||||
createEmitterContext,
|
||||
createEmitterTestHost,
|
||||
createNetSdkContext,
|
||||
typeSpecCompile,
|
||||
} from "./utils/TestUtil.js";
|
||||
|
||||
describe("Test GetInputType for scalar", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("azureLocation scalar", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(@query location: azureLocation): void;
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
runner.compileAndDiagnose;
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const root = createModel(sdkContext);
|
||||
deepStrictEqual(root.Clients[0].Operations[0].Parameters[0].Type.Kind, InputTypeKind.Primitive);
|
||||
deepStrictEqual(
|
||||
root.Clients[0].Operations[0].Parameters[0].Type.Name,
|
||||
InputPrimitiveTypeKind.AzureLocation
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.AzureLocation,
|
||||
IsNullable: false,
|
||||
},
|
||||
root.Clients[0].Operations[0].Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,217 @@
|
|||
import { TestHost } from "@typespec/compiler/testing";
|
||||
import { getAllHttpServices } from "@typespec/http";
|
||||
import assert from "assert";
|
||||
import isEqual from "lodash.isequal";
|
||||
import { loadOperation } from "../../src/lib/operation.js";
|
||||
import { InputPrimitiveTypeKind } from "../../src/type/inputPrimitiveTypeKind.js";
|
||||
import { InputEnumType, InputModelType, InputPrimitiveType } from "../../src/type/inputType.js";
|
||||
import { InputTypeKind } from "../../src/type/inputTypeKind.js";
|
||||
import {
|
||||
createEmitterContext,
|
||||
createEmitterTestHost,
|
||||
createNetSdkContext,
|
||||
navigateModels,
|
||||
typeSpecCompile,
|
||||
} from "./utils/TestUtil.js";
|
||||
|
||||
describe("Test string format", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("scalar url as parameter", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(@path sourceUrl: url): void;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
const operation = loadOperation(
|
||||
sdkContext,
|
||||
services[0].operations[0],
|
||||
"",
|
||||
[],
|
||||
services[0].namespace,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Uri,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
operation.Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("scalar url as model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("The source url.")
|
||||
source: url;
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const foo = modelMap.get("Foo");
|
||||
assert(foo !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Uri,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
foo.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("format uri on operation parameter", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(@path @format("Uri")sourceUrl: string): void;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
const operation = loadOperation(
|
||||
sdkContext,
|
||||
services[0].operations[0],
|
||||
"",
|
||||
[],
|
||||
services[0].namespace,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Uri,
|
||||
IsNullable: false,
|
||||
},
|
||||
operation.Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("format uri on model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("The source url.")
|
||||
@format("Uri")
|
||||
source: string
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const foo = modelMap.get("Foo");
|
||||
assert(foo !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Uri,
|
||||
IsNullable: false,
|
||||
},
|
||||
foo.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("format uuid on operation parameter", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
op test(@path @format("uuid")subscriptionId: string): void;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
const operation = loadOperation(
|
||||
sdkContext,
|
||||
services[0].operations[0],
|
||||
"",
|
||||
[],
|
||||
services[0].namespace,
|
||||
modelMap,
|
||||
enumMap
|
||||
);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Guid,
|
||||
IsNullable: false,
|
||||
},
|
||||
operation.Parameters[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it("format on model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("The subscription id.")
|
||||
@format("uuid")
|
||||
subscriptionId: string
|
||||
}
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const modelMap = new Map<string, InputModelType>();
|
||||
const enumMap = new Map<string, InputEnumType>();
|
||||
navigateModels(sdkContext, services[0].namespace, modelMap, enumMap);
|
||||
const foo = modelMap.get("Foo");
|
||||
assert(foo !== undefined);
|
||||
assert(
|
||||
isEqual(
|
||||
{
|
||||
Kind: InputTypeKind.Primitive,
|
||||
Name: InputPrimitiveTypeKind.Guid,
|
||||
IsNullable: false,
|
||||
} as InputPrimitiveType,
|
||||
foo.Properties[0].Type
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,629 @@
|
|||
import { TestHost } from "@typespec/compiler/testing";
|
||||
import { getAllHttpServices } from "@typespec/http";
|
||||
import assert from "assert";
|
||||
import { getUsages } from "../../src/lib/model.js";
|
||||
import {
|
||||
createEmitterContext,
|
||||
createEmitterTestHost,
|
||||
createNetSdkContext,
|
||||
typeSpecCompile,
|
||||
} from "./utils/TestUtil.js";
|
||||
|
||||
describe("Test getUsages", () => {
|
||||
let runner: TestHost;
|
||||
|
||||
beforeEach(async () => {
|
||||
runner = await createEmitterTestHost();
|
||||
});
|
||||
|
||||
it("Get usage for body parameter type", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: Foo): void;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Get usage for response body", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string): Foo;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.outputs.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Get usage for the model in both input and output", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: Foo): Foo;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.roundTrips.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Get usage for the model which is used in two operations", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: Foo): void;
|
||||
op test2(@path id: string): Foo;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.roundTrips.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Get usage for the model as the template argument", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model template.")
|
||||
model TemplateModel<T> {
|
||||
@doc("name of the model.")
|
||||
name: string;
|
||||
prop: T;
|
||||
}
|
||||
@doc("This is a model.")
|
||||
model Foo {
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: TemplateModel<Foo>): void;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("TemplateModelFoo"));
|
||||
assert(usages.inputs.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Test the usage inheritance between base model and derived model", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a base model.")
|
||||
model BaseModel {
|
||||
@doc("name of the model.")
|
||||
base: string;
|
||||
}
|
||||
@doc("This is a model.")
|
||||
model Foo extends BaseModel{
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: Foo): void;
|
||||
op test2(@path id: string): BaseModel;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
// verify that the baseModel will not apply the usage of derived model.
|
||||
assert(usages.outputs.includes("BaseModel"));
|
||||
// verify that the derived model will inherit the usage of base model
|
||||
assert(usages.roundTrips.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Test the usage inheritance between base model and derived model which has model property", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This a model of a property in base model")
|
||||
model propertyModel {
|
||||
@doc("name of the model.")
|
||||
base: string;
|
||||
}
|
||||
@doc("This is a base model.")
|
||||
model BaseModel {
|
||||
@doc("name of the model.")
|
||||
base: string;
|
||||
@doc("a property")
|
||||
prop: propertyModel;
|
||||
}
|
||||
@doc("This is a model.")
|
||||
model Foo extends BaseModel{
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: Foo): void;
|
||||
op test2(@path id: string): BaseModel;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
// verify that the baseModel will not apply the usage of derived model.
|
||||
assert(usages.outputs.includes("BaseModel"));
|
||||
// verify that the derived model will inherit the usage of base model
|
||||
assert(usages.roundTrips.includes("Foo"));
|
||||
//verify that the property model of base model will inherit the usage of the derived model
|
||||
assert(usages.roundTrips.includes("propertyModel"));
|
||||
});
|
||||
|
||||
it("Test the usage of models spread alias", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
alias FooAlias = {
|
||||
@path id: string;
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
};
|
||||
op test(...FooAlias): void;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("TestRequest"));
|
||||
});
|
||||
|
||||
it("Test the usage of body parameter of azure core operation.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
@resource("items")
|
||||
model Foo {
|
||||
@doc("id of Foo")
|
||||
@key
|
||||
@visibility("read","create","query")
|
||||
id: string;
|
||||
@doc("name of Foo")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("The item information.")
|
||||
model FooInfo {
|
||||
@doc("name of Foo")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("this is a response model.")
|
||||
model BatchCreateFooListItemsRequest {
|
||||
@doc("The items to create")
|
||||
fooInfos: FooInfo[];
|
||||
}
|
||||
|
||||
@doc("this is a response model.")
|
||||
model BatchCreateTextListItemsResponse {
|
||||
@doc("The item list.")
|
||||
fooList: Foo[];
|
||||
}
|
||||
interface TextLists{
|
||||
@doc("create items")
|
||||
addItems is ResourceAction<Foo, BatchCreateFooListItemsRequest, BatchCreateTextListItemsResponse>;
|
||||
}
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("BatchCreateFooListItemsRequest"));
|
||||
assert(usages.inputs.includes("FooInfo"));
|
||||
assert(usages.outputs.includes("BatchCreateTextListItemsResponse"));
|
||||
});
|
||||
|
||||
it("Test the usage of body parameter and return type of azure core resource operation.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
@resource("items")
|
||||
model Foo {
|
||||
@doc("id of Foo")
|
||||
@key
|
||||
@visibility("read","create","query")
|
||||
id: string;
|
||||
@doc("name of Foo")
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface FooClient{
|
||||
@doc("create Foo")
|
||||
createFoo is ResourceCreateOrUpdate<Foo>;
|
||||
}
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.roundTrips.includes("Foo"));
|
||||
});
|
||||
|
||||
it("Test the usage of body polymorphism type in azure core resource operation.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
@resource("items")
|
||||
model Foo {
|
||||
@doc("id of Foo")
|
||||
@key
|
||||
@visibility("read","create","query")
|
||||
id: string;
|
||||
@doc("name of Foo")
|
||||
name: string;
|
||||
}
|
||||
|
||||
#suppress "@azure-tools/typespec-azure-core/documentation-required" "The ModelProperty named 'discriminatorProperty' should have a documentation or description, please use decorator @doc to add it"
|
||||
@discriminator("discriminatorProperty")
|
||||
@doc("Base model with discriminator property.")
|
||||
model BaseModelWithDiscriminator {
|
||||
@doc("Optional property on base")
|
||||
optionalPropertyOnBase?: string;
|
||||
|
||||
@doc("Required property on base")
|
||||
requiredPropertyOnBase: int32;
|
||||
}
|
||||
|
||||
#suppress "@azure-tools/typespec-azure-core/documentation-required" "The ModelProperty named 'discriminatorProperty' should have a documentation or description, please use decorator @doc to add it"
|
||||
@doc("Deriver model with discriminator property.")
|
||||
model DerivedModelWithDiscriminatorA extends BaseModelWithDiscriminator {
|
||||
discriminatorProperty: "A";
|
||||
|
||||
@doc("Required string.")
|
||||
requiredString: string;
|
||||
}
|
||||
|
||||
interface FooClient{
|
||||
@doc("create Foo")
|
||||
op testFoo is Azure.Core.StandardResourceOperations.ResourceCollectionAction<
|
||||
Foo,
|
||||
BaseModelWithDiscriminator,
|
||||
{}
|
||||
>;
|
||||
}
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("BaseModelWithDiscriminator"));
|
||||
assert(usages.inputs.includes("DerivedModelWithDiscriminatorA"));
|
||||
});
|
||||
|
||||
it("Test the usage of response polymorphism type in azure core resource operation.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
@resource("items")
|
||||
model Foo {
|
||||
@doc("id of Foo")
|
||||
@key
|
||||
@visibility("read","create","query")
|
||||
id: string;
|
||||
@doc("name of Foo")
|
||||
name: string;
|
||||
}
|
||||
|
||||
@doc("This is nested model.")
|
||||
model NestedModel {
|
||||
@doc("id of NestedModel")
|
||||
id: string;
|
||||
}
|
||||
|
||||
#suppress "@azure-tools/typespec-azure-core/documentation-required" "The ModelProperty named 'discriminatorProperty' should have a documentation or description, please use decorator @doc to add it"
|
||||
@discriminator("discriminatorProperty")
|
||||
@doc("Base model with discriminator property.")
|
||||
model BaseModelWithDiscriminator {
|
||||
@doc("Optional property on base")
|
||||
optionalPropertyOnBase?: string;
|
||||
|
||||
@doc("Required property on base")
|
||||
requiredPropertyOnBase: int32;
|
||||
}
|
||||
|
||||
#suppress "@azure-tools/typespec-azure-core/documentation-required" "The ModelProperty named 'discriminatorProperty' should have a documentation or description, please use decorator @doc to add it"
|
||||
@doc("Deriver model with discriminator property.")
|
||||
model DerivedModelWithDiscriminatorA extends BaseModelWithDiscriminator {
|
||||
discriminatorProperty: "A";
|
||||
|
||||
@doc("Required string.")
|
||||
requiredString: string;
|
||||
|
||||
@doc("property with complex model type.")
|
||||
nestedModel: NestedModel;
|
||||
}
|
||||
|
||||
interface FooClient{
|
||||
@doc("create Foo")
|
||||
op testFoo is Azure.Core.StandardResourceOperations.ResourceCollectionAction<
|
||||
Foo,
|
||||
{},
|
||||
BaseModelWithDiscriminator
|
||||
>;
|
||||
}
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: true }
|
||||
);
|
||||
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.outputs.includes("BaseModelWithDiscriminator"));
|
||||
assert(usages.outputs.includes("DerivedModelWithDiscriminatorA"));
|
||||
assert(usages.outputs.includes("NestedModel"));
|
||||
});
|
||||
|
||||
it("Get usage for the model which is renamed by projected name", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("This is a model.")
|
||||
@projectedName("azure", "FooRenamed")
|
||||
model Foo {
|
||||
@doc("name of the Foo")
|
||||
name: string;
|
||||
}
|
||||
op test(@path id: string, @body foo: Foo): Foo;
|
||||
`,
|
||||
runner
|
||||
);
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.roundTrips.includes("FooRenamed"));
|
||||
});
|
||||
|
||||
it("Test the usage of enum which is renamed via @projectedName.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("fixed string enum")
|
||||
@projectedName("azure", "SimpleEnumRenamed")
|
||||
enum SimpleEnum {
|
||||
@doc("Enum value one")
|
||||
One: "1",
|
||||
@doc("Enum value two")
|
||||
Two: "2",
|
||||
@doc("Enum value four")
|
||||
Four: "4"
|
||||
}
|
||||
|
||||
op test(@path id: SimpleEnum): void;
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsAzureCoreNeeded: false }
|
||||
);
|
||||
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("SimpleEnumRenamed"));
|
||||
});
|
||||
|
||||
it("Test the usage of model which is renamed via @clientName.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
@doc("A model plan to rename")
|
||||
@clientName("RenamedModel")
|
||||
model ModelToRename {
|
||||
value: string;
|
||||
}
|
||||
|
||||
op test(@body body: ModelToRename): void;
|
||||
`,
|
||||
runner,
|
||||
{ IsNamespaceNeeded: true, IsTCGCNeeded: true }
|
||||
);
|
||||
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const usages = getUsages(sdkContext, services[0].operations);
|
||||
assert(usages.inputs.includes("RenamedModel"));
|
||||
});
|
||||
|
||||
it("Test the usage of return type of a customized LRO operation.", async () => {
|
||||
const program = await typeSpecCompile(
|
||||
`
|
||||
#suppress "@azure-tools/typespec-azure-core/documentation-required" "MUST fix in next version"
|
||||
@doc("The status of the processing job.")
|
||||
@lroStatus
|
||||
enum JobStatus {
|
||||
NotStarted: "notStarted",
|
||||
Running: "running",
|
||||
Succeeded: "succeeded",
|
||||
Failed: "failed",
|
||||
Canceled: "canceled",
|
||||
}
|
||||
|
||||
@doc("Provides status details for long running operations.")
|
||||
model HealthInsightsOperationStatus<
|
||||
TStatusResult = never,
|
||||
TStatusError = Foundations.Error
|
||||
> {
|
||||
@key("operationId")
|
||||
@doc("The unique ID of the operation.")
|
||||
@visibility("read")
|
||||
id: Azure.Core.uuid;
|
||||
|
||||
@doc("The status of the operation")
|
||||
@visibility("read")
|
||||
@lroStatus
|
||||
status: JobStatus;
|
||||
|
||||
@doc("The date and time when the processing job was created.")
|
||||
@visibility("read")
|
||||
createdDateTime?: utcDateTime;
|
||||
|
||||
@doc("The date and time when the processing job is set to expire.")
|
||||
@visibility("read")
|
||||
expirationDateTime?: utcDateTime;
|
||||
|
||||
@doc("The date and time when the processing job was last updated.")
|
||||
@visibility("read")
|
||||
lastUpdateDateTime?: utcDateTime;
|
||||
|
||||
@doc("Error object that describes the error when status is Failed.")
|
||||
error?: TStatusError;
|
||||
|
||||
@doc("The result of the operation.")
|
||||
@lroResult
|
||||
result?: TStatusResult;
|
||||
}
|
||||
|
||||
@doc("The location of an instance of {name}", TResource)
|
||||
scalar HealthInsightsResourceLocation<TResource extends {}> extends url;
|
||||
|
||||
@doc("Metadata for long running operation status monitor locations")
|
||||
model HealthInsightsLongRunningStatusLocation<TStatusResult = never> {
|
||||
@pollingLocation
|
||||
@doc("The location for monitoring the operation state.")
|
||||
@TypeSpec.Http.header("Operation-Location")
|
||||
operationLocation: HealthInsightsResourceLocation<HealthInsightsOperationStatus<TStatusResult>>;
|
||||
}
|
||||
#suppress "@azure-tools/typespec-azure-core/long-running-polling-operation-required" "This is a template"
|
||||
@doc("Long running RPC operation template")
|
||||
op HealthInsightsLongRunningRpcOperation<
|
||||
TParams extends TypeSpec.Reflection.Model,
|
||||
TResponse extends TypeSpec.Reflection.Model,
|
||||
Traits extends Record<unknown> = {}
|
||||
> is Azure.Core.RpcOperation<
|
||||
TParams & RepeatabilityRequestHeaders,
|
||||
Foundations.AcceptedResponse<HealthInsightsLongRunningStatusLocation<TResponse> &
|
||||
Foundations.RetryAfterHeader> &
|
||||
RepeatabilityResponseHeaders &
|
||||
HealthInsightsOperationStatus,
|
||||
Traits
|
||||
>;
|
||||
@trait("HealthInsightsRetryAfterTrait")
|
||||
@doc("Health Insights retry after trait")
|
||||
model HealthInsightsRetryAfterTrait {
|
||||
#suppress "@azure-tools/typespec-providerhub/no-inline-model" "This inline model is never used directly in operations."
|
||||
@doc("The retry-after header.")
|
||||
retryAfter: {
|
||||
@traitLocation(TraitLocation.Response)
|
||||
response: Foundations.RetryAfterHeader;
|
||||
};
|
||||
}
|
||||
|
||||
@doc("The inference results for the Radiology Insights request.")
|
||||
model RadiologyInsightsInferenceResult {
|
||||
id: string;
|
||||
}
|
||||
alias Request = {
|
||||
@doc("The list of patients, including their clinical information and data.")
|
||||
patients: string[];
|
||||
};
|
||||
@resource("radiology-insights/jobs")
|
||||
@doc("The response for the Radiology Insights request.")
|
||||
model RadiologyInsightsResult
|
||||
is HealthInsightsOperationStatus<RadiologyInsightsInferenceResult>;
|
||||
|
||||
@doc("The body of the Radiology Insights request.")
|
||||
model RadiologyInsightsData {
|
||||
...Request;
|
||||
|
||||
@doc("Configuration affecting the Radiology Insights model's inference.")
|
||||
configuration?: string;
|
||||
}
|
||||
|
||||
#suppress "@azure-tools/typespec-azure-core/long-running-polling-operation-required" "This is a template"
|
||||
@doc("Long running Pool operation template")
|
||||
op HealthInsightsLongRunningPollOperation<TResult extends TypeSpec.Reflection.Model> is Azure.Core.RpcOperation<
|
||||
{
|
||||
@doc("A processing job identifier.")
|
||||
@path("id")
|
||||
id: Azure.Core.uuid;
|
||||
},
|
||||
TResult,
|
||||
HealthInsightsRetryAfterTrait
|
||||
>;
|
||||
|
||||
interface LegacyLro {
|
||||
#suppress "@azure-tools/typespec-azure-core/no-rpc-path-params" "Service uses a jobId in the path"
|
||||
@summary("Get Radiology Insights job details")
|
||||
@tag("RadiologyInsights")
|
||||
@doc("Gets the status and details of the Radiology Insights job.")
|
||||
@get
|
||||
@route("/radiology-insights/jobs/{id}")
|
||||
@convenientAPI(false)
|
||||
getJob is HealthInsightsLongRunningPollOperation<RadiologyInsightsResult>;
|
||||
|
||||
#suppress "@azure-tools/typespec-azure-core/long-running-polling-operation-required" "Polling through operation-location"
|
||||
#suppress "@azure-tools/typespec-azure-core/use-standard-operations" "There is no long-running RPC template in Azure.Core"
|
||||
@summary("Create Radiology Insights job")
|
||||
@tag("RadiologyInsights")
|
||||
@doc("Creates a Radiology Insights job with the given request body.")
|
||||
@pollingOperation(LegacyLro.getJob)
|
||||
@route("/radiology-insights/jobs")
|
||||
@convenientAPI(true)
|
||||
createJob is HealthInsightsLongRunningRpcOperation<
|
||||
RadiologyInsightsData,
|
||||
RadiologyInsightsResult
|
||||
>;
|
||||
}
|
||||
`,
|
||||
runner,
|
||||
{
|
||||
IsNamespaceNeeded: true,
|
||||
IsAzureCoreNeeded: true,
|
||||
IsTCGCNeeded: true,
|
||||
}
|
||||
);
|
||||
|
||||
const context = createEmitterContext(program);
|
||||
const sdkContext = createNetSdkContext(context);
|
||||
const [services] = getAllHttpServices(program);
|
||||
const convenienceOperations = services[0].operations.slice(1);
|
||||
const usages = getUsages(sdkContext, convenienceOperations);
|
||||
assert(usages.outputs.includes("RadiologyInsightsInferenceResult"));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,130 @@
|
|||
import { AzureCoreTestLibrary } from "@azure-tools/typespec-azure-core/testing";
|
||||
import { createSdkContext, SdkContext } from "@azure-tools/typespec-client-generator-core";
|
||||
import { SdkTestLibrary } from "@azure-tools/typespec-client-generator-core/testing";
|
||||
import {
|
||||
CompilerOptions,
|
||||
EmitContext,
|
||||
isGlobalNamespace,
|
||||
Namespace,
|
||||
navigateTypesInNamespace,
|
||||
Program,
|
||||
Type,
|
||||
} from "@typespec/compiler";
|
||||
import { createTestHost, TestHost } from "@typespec/compiler/testing";
|
||||
import { HttpTestLibrary } from "@typespec/http/testing";
|
||||
import { RestTestLibrary } from "@typespec/rest/testing";
|
||||
import { VersioningTestLibrary } from "@typespec/versioning/testing";
|
||||
import { getFormattedType, getInputType } from "../../../src/lib/model.js";
|
||||
import { NetEmitterOptions } from "../../../src/options.js";
|
||||
import { InputEnumType, InputModelType } from "../../../src/type/inputType.js";
|
||||
|
||||
export async function createEmitterTestHost(): Promise<TestHost> {
|
||||
return createTestHost({
|
||||
libraries: [
|
||||
RestTestLibrary,
|
||||
HttpTestLibrary,
|
||||
VersioningTestLibrary,
|
||||
AzureCoreTestLibrary,
|
||||
SdkTestLibrary,
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export interface TypeSpecCompileOptions {
|
||||
IsNamespaceNeeded?: boolean;
|
||||
IsAzureCoreNeeded?: boolean;
|
||||
IsTCGCNeeded?: boolean;
|
||||
}
|
||||
|
||||
export async function typeSpecCompile(
|
||||
content: string,
|
||||
host: TestHost,
|
||||
options?: TypeSpecCompileOptions
|
||||
) {
|
||||
const needNamespaces = options?.IsNamespaceNeeded ?? true;
|
||||
const needAzureCore = options?.IsAzureCoreNeeded ?? false;
|
||||
const needTCGC = options?.IsTCGCNeeded ?? false;
|
||||
const namespace = `
|
||||
@versioned(Versions)
|
||||
@useAuth(ApiKeyAuth<ApiKeyLocation.header, "api-key">)
|
||||
@service({
|
||||
title: "Azure Csharp emitter Testing",
|
||||
})
|
||||
|
||||
namespace Azure.Csharp.Testing;
|
||||
|
||||
enum Versions {
|
||||
${needAzureCore ? "@useDependency(Azure.Core.Versions.v1_0_Preview_1)" : ""}
|
||||
"2023-01-01-preview"
|
||||
}
|
||||
|
||||
`;
|
||||
const fileContent = `
|
||||
import "@typespec/rest";
|
||||
import "@typespec/http";
|
||||
import "@typespec/versioning";
|
||||
${needAzureCore ? 'import "@azure-tools/typespec-azure-core";' : ""}
|
||||
${needTCGC ? 'import "@azure-tools/typespec-client-generator-core";' : ""}
|
||||
using TypeSpec.Rest;
|
||||
using TypeSpec.Http;
|
||||
using TypeSpec.Versioning;
|
||||
${needAzureCore ? "using Azure.Core;\nusing Azure.Core.Traits;" : ""}
|
||||
${needTCGC ? "using Azure.ClientGenerator.Core;" : ""}
|
||||
|
||||
${needNamespaces ? namespace : ""}
|
||||
${content}
|
||||
`;
|
||||
host.addTypeSpecFile("main.tsp", fileContent);
|
||||
const cliOptions = {
|
||||
warningAsError: false,
|
||||
} as CompilerOptions;
|
||||
await host.compile("./", cliOptions);
|
||||
return host.program;
|
||||
}
|
||||
|
||||
export function createEmitterContext(program: Program): EmitContext<NetEmitterOptions> {
|
||||
return {
|
||||
program: program,
|
||||
emitterOutputDir: "./",
|
||||
options: {
|
||||
outputFile: "tspCodeModel.json",
|
||||
logFile: "log.json",
|
||||
skipSDKGeneration: false,
|
||||
"new-project": false,
|
||||
"clear-output-folder": false,
|
||||
"save-inputs": false,
|
||||
"generate-protocol-methods": true,
|
||||
"generate-convenience-methods": true,
|
||||
"package-name": undefined,
|
||||
} as NetEmitterOptions,
|
||||
} as EmitContext<NetEmitterOptions>;
|
||||
}
|
||||
|
||||
/* Navigate all the models in the whole namespace. */
|
||||
export function navigateModels(
|
||||
context: SdkContext<NetEmitterOptions>,
|
||||
namespace: Namespace,
|
||||
models: Map<string, InputModelType>,
|
||||
enums: Map<string, InputEnumType>
|
||||
) {
|
||||
const computeModel = (x: Type) =>
|
||||
getInputType(context, getFormattedType(context.program, x), models, enums) as any;
|
||||
const skipSubNamespaces = isGlobalNamespace(context.program, namespace);
|
||||
navigateTypesInNamespace(
|
||||
namespace,
|
||||
{
|
||||
model: (x) => x.name !== "" && x.kind === "Model" && computeModel(x),
|
||||
scalar: computeModel,
|
||||
enum: computeModel,
|
||||
union: (x) => x.name !== undefined && computeModel(x),
|
||||
},
|
||||
{ skipSubNamespaces }
|
||||
);
|
||||
}
|
||||
|
||||
/* We always need to pass in the emitter name now that it is required so making a helper to do this. */
|
||||
export function createNetSdkContext(
|
||||
program: EmitContext<NetEmitterOptions>
|
||||
): SdkContext<NetEmitterOptions> {
|
||||
return createSdkContext(program, "@azure-tools/typespec-azure");
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"alwaysStrict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"preserveConstEnums": true,
|
||||
"module": "node16",
|
||||
"moduleResolution": "node16",
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"sourceMap": true,
|
||||
"declarationMap": true,
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"stripInternal": true,
|
||||
"noEmitHelpers": false,
|
||||
"target": "es2021",
|
||||
"lib": ["es2021"],
|
||||
"experimentalDecorators": true,
|
||||
"newLine": "LF",
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"tsBuildInfoFile": "temp/tsconfig.tsbuildinfo",
|
||||
"types": ["node", "mocha"],
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "test/**/*.ts"]
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
packages:
|
||||
- packages/*
|
||||
- e2e
|
||||
- "packages/*"
|
||||
- "e2e"
|
||||
- "!packages/http-client-csharp/**"
|
||||
|
|
Загрузка…
Ссылка в новой задаче