Split core HTTP functionality from `@typespec/rest` into `@typespec/http` (#1668)

* Decouple `autoRoute` from HTTP library routing implementation

* Split HTTP functionality of `@typespec/rest` to `@typespec/http`

* Lint and format

* Add Rush change files

* Update playground

* Generate separate documentation for HTTP library

* Minor cleanup and library references

* Add back `isAutoRoute` accessor function

* Add `OperationParameterOptions` to customize operation parameter logic

* Update playground configuration to add `@typespec/http` import

* Remove duplicate diagnostics in REST library

* Update REST and HTTP README files

* Add a changelog entry about `@routeReset` removal
This commit is contained in:
David Wilson 2023-02-27 20:29:55 +02:00 коммит произвёл GitHub
Родитель d1b20c1238
Коммит 3e669c74c1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
83 изменённых файлов: 2438 добавлений и 1883 удалений

Просмотреть файл

@ -100,7 +100,7 @@ Here is a very small TypeSpec example that uses the `@typespec/openapi3` library
#### sample.tsp #### sample.tsp
```typespec ```typespec
import "@typespec/rest"; import "@typespec/http";
using TypeSpec.Http; using TypeSpec.Http;
@ -183,6 +183,7 @@ Example
// Stable setup // Stable setup
"dependencies": { "dependencies": {
"@typespec/compiler": "~0.30.0", "@typespec/compiler": "~0.30.0",
"@typespec/http": "~0.14.0",
"@typespec/rest": "~0.14.0", "@typespec/rest": "~0.14.0",
"@typespec/openapi": "~0.9.0", "@typespec/openapi": "~0.9.0",
} }
@ -191,6 +192,7 @@ Example
// In this example: compiler and openapi have changes but rest library has none // In this example: compiler and openapi have changes but rest library has none
"dependencies": { "dependencies": {
"@typespec/compiler": "~0.31.0-dev.5", "@typespec/compiler": "~0.31.0-dev.5",
"@typespec/http": "~0.14.0",
"@typespec/rest": "~0.14.0", // No changes to @typespec/rest library so need to stay the latest. "@typespec/rest": "~0.14.0", // No changes to @typespec/rest library so need to stay the latest.
"@typespec/openapi": "~0.10.0-dev.2", "@typespec/openapi": "~0.10.0-dev.2",
} }
@ -203,6 +205,7 @@ Example
| Core functionality | | | | | Core functionality | | | |
| [@typespec/compiler][compiler_src] | [Changelog][compiler_chg] | [![](https://img.shields.io/npm/v/@typespec/compiler)](https://www.npmjs.com/package/@typespec/compiler) | ![](https://img.shields.io/npm/v/@typespec/compiler/next) | | [@typespec/compiler][compiler_src] | [Changelog][compiler_chg] | [![](https://img.shields.io/npm/v/@typespec/compiler)](https://www.npmjs.com/package/@typespec/compiler) | ![](https://img.shields.io/npm/v/@typespec/compiler/next) |
| TypeSpec Libraries | | | | | TypeSpec Libraries | | | |
| [@typespec/http][http_src] | [Changelog][http_chg] | [![](https://img.shields.io/npm/v/@typespec/http)](https://www.npmjs.com/package/@typespec/http) | ![](https://img.shields.io/npm/v/@typespec/http/next) |
| [@typespec/rest][rest_src] | [Changelog][rest_chg] | [![](https://img.shields.io/npm/v/@typespec/rest)](https://www.npmjs.com/package/@typespec/rest) | ![](https://img.shields.io/npm/v/@typespec/rest/next) | | [@typespec/rest][rest_src] | [Changelog][rest_chg] | [![](https://img.shields.io/npm/v/@typespec/rest)](https://www.npmjs.com/package/@typespec/rest) | ![](https://img.shields.io/npm/v/@typespec/rest/next) |
| [@typespec/openapi][openapi_src] | [Changelog][openapi_chg] | [![](https://img.shields.io/npm/v/@typespec/openapi)](https://www.npmjs.com/package/@typespec/openapi) | ![](https://img.shields.io/npm/v/@typespec/openapi/next) | | [@typespec/openapi][openapi_src] | [Changelog][openapi_chg] | [![](https://img.shields.io/npm/v/@typespec/openapi)](https://www.npmjs.com/package/@typespec/openapi) | ![](https://img.shields.io/npm/v/@typespec/openapi/next) |
| [@typespec/openapi3][openapi3_src] | [Changelog][openapi3_chg] | [![](https://img.shields.io/npm/v/@typespec/openapi3)](https://www.npmjs.com/package/@typespec/openapi3) | ![](https://img.shields.io/npm/v/@typespec/openapi3/next) | | [@typespec/openapi3][openapi3_src] | [Changelog][openapi3_chg] | [![](https://img.shields.io/npm/v/@typespec/openapi3)](https://www.npmjs.com/package/@typespec/openapi3) | ![](https://img.shields.io/npm/v/@typespec/openapi3/next) |
@ -215,6 +218,8 @@ Example
[compiler_src]: packages/compiler [compiler_src]: packages/compiler
[compiler_chg]: packages/compiler/CHANGELOG.md [compiler_chg]: packages/compiler/CHANGELOG.md
[http_src]: packages/http
[http_chg]: packages/http/CHANGELOG.md
[rest_src]: packages/rest [rest_src]: packages/rest
[rest_chg]: packages/rest/CHANGELOG.md [rest_chg]: packages/rest/CHANGELOG.md
[openapi_src]: packages/openapi [openapi_src]: packages/openapi

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/http",
"comment": "Move core HTTP functionality from `@typespec/rest` into a new `@typespec/http` library",
"type": "none"
}
],
"packageName": "@typespec/http"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/openapi",
"comment": "Use new `@typespec/http` library",
"type": "none"
}
],
"packageName": "@typespec/openapi"
}

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@typespec/openapi3",
"comment": "Use new `@typespec/http` library",
"type": "none"
}
],
"packageName": "@typespec/openapi3"
}

Просмотреть файл

@ -0,0 +1,20 @@
{
"changes": [
{
"packageName": "@typespec/rest",
"comment": "Move core HTTP functionality from `@typespec/rest` into a new `@typespec/http` library",
"type": "none"
},
{
"packageName": "@typespec/rest",
"comment": "**Breaking change:** The `@autoRoute` decorator can no longer be applied to namespaces",
"type": "none"
},
{
"packageName": "@typespec/rest",
"comment": "**Breaking change:** The unimplemented `@routeReset` decorator has been removed",
"type": "none"
}
],
"packageName": "@typespec/rest"
}

140
common/config/rush/pnpm-lock.yaml сгенерированный
Просмотреть файл

@ -3,7 +3,6 @@ lockfileVersion: 5.3
specifiers: specifiers:
'@babel/code-frame': ~7.18.6 '@babel/code-frame': ~7.18.6
'@babel/core': ^7.0.0 '@babel/core': ^7.0.0
'@typespec/compiler-v0.37': npm:@typespec/compiler@0.37.0
'@docusaurus/core': ^2.2.0 '@docusaurus/core': ^2.2.0
'@docusaurus/module-type-aliases': ^2.2.0 '@docusaurus/module-type-aliases': ^2.2.0
'@docusaurus/preset-classic': ^2.2.0 '@docusaurus/preset-classic': ^2.2.0
@ -21,12 +20,11 @@ specifiers:
'@rollup/plugin-replace': ~4.0.0 '@rollup/plugin-replace': ~4.0.0
'@rollup/plugin-virtual': ~3.0.1 '@rollup/plugin-virtual': ~3.0.1
'@rush-temp/bundler': file:./projects/bundler.tgz '@rush-temp/bundler': file:./projects/bundler.tgz
'@rush-temp/typespec-vs': file:./projects/typespec-vs.tgz
'@rush-temp/typespec-vscode': file:./projects/typespec-vscode.tgz
'@rush-temp/compiler': file:./projects/compiler.tgz '@rush-temp/compiler': file:./projects/compiler.tgz
'@rush-temp/eslint-config-typespec': file:./projects/eslint-config-typespec.tgz '@rush-temp/eslint-config-typespec': file:./projects/eslint-config-typespec.tgz
'@rush-temp/eslint-plugin': file:./projects/eslint-plugin.tgz '@rush-temp/eslint-plugin': file:./projects/eslint-plugin.tgz
'@rush-temp/html-program-viewer': file:./projects/html-program-viewer.tgz '@rush-temp/html-program-viewer': file:./projects/html-program-viewer.tgz
'@rush-temp/http': file:./projects/http.tgz
'@rush-temp/internal-build-utils': file:./projects/internal-build-utils.tgz '@rush-temp/internal-build-utils': file:./projects/internal-build-utils.tgz
'@rush-temp/library-linter': file:./projects/library-linter.tgz '@rush-temp/library-linter': file:./projects/library-linter.tgz
'@rush-temp/lint': file:./projects/lint.tgz '@rush-temp/lint': file:./projects/lint.tgz
@ -40,6 +38,8 @@ specifiers:
'@rush-temp/samples': file:./projects/samples.tgz '@rush-temp/samples': file:./projects/samples.tgz
'@rush-temp/spec': file:./projects/spec.tgz '@rush-temp/spec': file:./projects/spec.tgz
'@rush-temp/tmlanguage-generator': file:./projects/tmlanguage-generator.tgz '@rush-temp/tmlanguage-generator': file:./projects/tmlanguage-generator.tgz
'@rush-temp/typespec-vs': file:./projects/typespec-vs.tgz
'@rush-temp/typespec-vscode': file:./projects/typespec-vscode.tgz
'@rush-temp/versioning': file:./projects/versioning.tgz '@rush-temp/versioning': file:./projects/versioning.tgz
'@rush-temp/website': file:./projects/website.tgz '@rush-temp/website': file:./projects/website.tgz
'@rushstack/eslint-patch': '1.1.0 ' '@rushstack/eslint-patch': '1.1.0 '
@ -64,6 +64,7 @@ specifiers:
'@typescript-eslint/eslint-plugin': ^5.30.7 '@typescript-eslint/eslint-plugin': ^5.30.7
'@typescript-eslint/parser': ^5.30.7 '@typescript-eslint/parser': ^5.30.7
'@typescript-eslint/utils': ~5.26.0 '@typescript-eslint/utils': ~5.26.0
'@typespec/compiler-v0.37': npm:@cadl-lang/compiler@0.37.0
'@vitejs/plugin-react': ~2.2.0 '@vitejs/plugin-react': ~2.2.0
'@vscode/vsce': ~2.15.0 '@vscode/vsce': ~2.15.0
ajv: ~8.11.2 ajv: ~8.11.2
@ -130,7 +131,6 @@ specifiers:
dependencies: dependencies:
'@babel/code-frame': 7.18.6 '@babel/code-frame': 7.18.6
'@babel/core': 7.20.12 '@babel/core': 7.20.12
'@typespec/compiler-v0.37': /@typespec/compiler/0.37.0
'@docusaurus/core': 2.3.0_8d89849c7c13db1dc34255908e1757ca '@docusaurus/core': 2.3.0_8d89849c7c13db1dc34255908e1757ca
'@docusaurus/module-type-aliases': 2.3.0_react-dom@18.2.0+react@18.2.0 '@docusaurus/module-type-aliases': 2.3.0_react-dom@18.2.0+react@18.2.0
'@docusaurus/preset-classic': 2.3.0_d6adfce1a9735f2773fdfdc805a804cb '@docusaurus/preset-classic': 2.3.0_d6adfce1a9735f2773fdfdc805a804cb
@ -148,12 +148,11 @@ dependencies:
'@rollup/plugin-replace': 4.0.0_rollup@3.4.0 '@rollup/plugin-replace': 4.0.0_rollup@3.4.0
'@rollup/plugin-virtual': 3.0.1_rollup@3.4.0 '@rollup/plugin-virtual': 3.0.1_rollup@3.4.0
'@rush-temp/bundler': file:projects/bundler.tgz '@rush-temp/bundler': file:projects/bundler.tgz
'@rush-temp/typespec-vs': file:projects/typespec-vs.tgz
'@rush-temp/typespec-vscode': file:projects/typespec-vscode.tgz
'@rush-temp/compiler': file:projects/compiler.tgz '@rush-temp/compiler': file:projects/compiler.tgz
'@rush-temp/eslint-config-typespec': file:projects/eslint-config-typespec.tgz_prettier@2.8.3 '@rush-temp/eslint-config-typespec': file:projects/eslint-config-typespec.tgz_prettier@2.8.3
'@rush-temp/eslint-plugin': file:projects/eslint-plugin.tgz '@rush-temp/eslint-plugin': file:projects/eslint-plugin.tgz
'@rush-temp/html-program-viewer': file:projects/html-program-viewer.tgz '@rush-temp/html-program-viewer': file:projects/html-program-viewer.tgz
'@rush-temp/http': file:projects/http.tgz
'@rush-temp/internal-build-utils': file:projects/internal-build-utils.tgz '@rush-temp/internal-build-utils': file:projects/internal-build-utils.tgz
'@rush-temp/library-linter': file:projects/library-linter.tgz '@rush-temp/library-linter': file:projects/library-linter.tgz
'@rush-temp/lint': file:projects/lint.tgz '@rush-temp/lint': file:projects/lint.tgz
@ -167,6 +166,8 @@ dependencies:
'@rush-temp/samples': file:projects/samples.tgz '@rush-temp/samples': file:projects/samples.tgz
'@rush-temp/spec': file:projects/spec.tgz '@rush-temp/spec': file:projects/spec.tgz
'@rush-temp/tmlanguage-generator': file:projects/tmlanguage-generator.tgz '@rush-temp/tmlanguage-generator': file:projects/tmlanguage-generator.tgz
'@rush-temp/typespec-vs': file:projects/typespec-vs.tgz
'@rush-temp/typespec-vscode': file:projects/typespec-vscode.tgz
'@rush-temp/versioning': file:projects/versioning.tgz '@rush-temp/versioning': file:projects/versioning.tgz
'@rush-temp/website': file:projects/website.tgz_@types+react@18.0.27 '@rush-temp/website': file:projects/website.tgz_@types+react@18.0.27
'@rushstack/eslint-patch': 1.1.0 '@rushstack/eslint-patch': 1.1.0
@ -191,6 +192,7 @@ dependencies:
'@typescript-eslint/eslint-plugin': 5.49.0_67a61cd63cbad5b63527389b6b4cdf8f '@typescript-eslint/eslint-plugin': 5.49.0_67a61cd63cbad5b63527389b6b4cdf8f
'@typescript-eslint/parser': 5.49.0_eslint@8.33.0+typescript@4.9.5 '@typescript-eslint/parser': 5.49.0_eslint@8.33.0+typescript@4.9.5
'@typescript-eslint/utils': 5.26.0_eslint@8.33.0+typescript@4.9.5 '@typescript-eslint/utils': 5.26.0_eslint@8.33.0+typescript@4.9.5
'@typespec/compiler-v0.37': /@cadl-lang/compiler/0.37.0
'@vitejs/plugin-react': 2.2.0_vite@3.2.5 '@vitejs/plugin-react': 2.2.0_vite@3.2.5
'@vscode/vsce': 2.15.0 '@vscode/vsce': 2.15.0
ajv: 8.11.2 ajv: 8.11.2
@ -1772,7 +1774,7 @@ packages:
resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==} resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==}
dev: false dev: false
/@typespec/compiler/0.37.0: /@cadl-lang/compiler/0.37.0:
resolution: {integrity: sha512-jHMqPZmM4evQlu7oY9vj6PEM+f+OhnfPqAdwxALrU2gwsLcwYG1h8rkjX/iK2KfeewCbXuRT/hztOTo3pcbYWA==} resolution: {integrity: sha512-jHMqPZmM4evQlu7oY9vj6PEM+f+OhnfPqAdwxALrU2gwsLcwYG1h8rkjX/iK2KfeewCbXuRT/hztOTo3pcbYWA==}
engines: {node: '>=16.0.0'} engines: {node: '>=16.0.0'}
hasBin: true hasBin: true
@ -15281,7 +15283,7 @@ packages:
dev: false dev: false
file:projects/bundler.tgz: file:projects/bundler.tgz:
resolution: {integrity: sha512-sUO7vssAYoEY7pQrVpcJ0MEHIhN5Lrqlc79wmBLPHb3tuWGpHPGA909BIGNHNuneGLO68kA/gMlDGgBfv4cNgg==, tarball: file:projects/bundler.tgz} resolution: {integrity: sha512-2QTp6cW0nHCRYpyhBuY0LJxfQaXujdjaApLqkJHOJZFu5X++cSMFIDH7E8yTgCBBOTCes/my2erb7GMPuoUQHg==, tarball: file:projects/bundler.tgz}
name: '@rush-temp/bundler' name: '@rush-temp/bundler'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15310,40 +15312,8 @@ packages:
- terser - terser
dev: false dev: false
file:projects/typespec-vs.tgz:
resolution: {integrity: sha512-i/10VOQOkEl9M0wDLMHSYuZlwiwnSM2gqt7xmTUcdEn5vkbufZGa0x9ZoLRhnC91DB4QYPb67i7IxYeemOKG6w==, tarball: file:projects/typespec-vs.tgz}
name: '@rush-temp/typespec-vs'
version: 0.0.0
dev: false
file:projects/typespec-vscode.tgz:
resolution: {integrity: sha512-0N+mjSTMWK0DFvoeFN95U/kIVyq9sYRB+AWLk8dTe7DHsJFKqm3s801KmFQIEn/gKrbxniC8ig3JcSGveMJzdA==, tarball: file:projects/typespec-vscode.tgz}
name: '@rush-temp/typespec-vscode'
version: 0.0.0
dependencies:
'@rollup/plugin-commonjs': 23.0.7_rollup@3.4.0
'@rollup/plugin-node-resolve': 15.0.1_rollup@3.4.0
'@types/mkdirp': 1.0.2
'@types/mocha': 10.0.1
'@types/node': 18.11.18
'@types/vscode': 1.53.0
'@vscode/vsce': 2.15.0
c8: 7.12.0
eslint: 8.33.0
mkdirp: 1.0.4
mocha: 10.1.0
mocha-junit-reporter: 2.2.0_mocha@10.1.0
mocha-multi-reporters: 1.5.1_mocha@10.1.0
rimraf: 3.0.2
rollup: 3.4.0
typescript: 4.9.5
vscode-languageclient: 8.0.2
transitivePeerDependencies:
- supports-color
dev: false
file:projects/compiler.tgz: file:projects/compiler.tgz:
resolution: {integrity: sha512-i1BeyaHFBrW1eoGxnRhPuNLkeK7eJ12VJQ89i9MRwnaCCkPi15Zloqdqyk+4UoLLeOKjuaJ2JqBR1YlBEGV6ng==, tarball: file:projects/compiler.tgz} resolution: {integrity: sha512-E/67YdnjZc8zQyM9W57MRhwNhI+iZ9Tl2LnmsrY5Dz+AKJ+BqWgEOsOAoLrxOuiXrYhVbtFzjwJf0Jzi8LdySA==, tarball: file:projects/compiler.tgz}
name: '@rush-temp/compiler' name: '@rush-temp/compiler'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15392,7 +15362,7 @@ packages:
dev: false dev: false
file:projects/eslint-config-typespec.tgz_prettier@2.8.3: file:projects/eslint-config-typespec.tgz_prettier@2.8.3:
resolution: {integrity: sha512-ovAW4RMqoyTK/d6UwLoIiS7KwDgNWLfSt1Q1Pg0NDshJJailWLxtTrgcU5yn/8eL1vm9bRFfbiI0OvOhBsjCYA==, tarball: file:projects/eslint-config-typespec.tgz} resolution: {integrity: sha512-EyCznsDzBazJ943GKlNWfw3DHB7AZmc/RkOHMRs9Lj/F36+MtuNpzcKBqglzqP5ZygaT3QsaQx839rXrx6EGzQ==, tarball: file:projects/eslint-config-typespec.tgz}
id: file:projects/eslint-config-typespec.tgz id: file:projects/eslint-config-typespec.tgz
name: '@rush-temp/eslint-config-typespec' name: '@rush-temp/eslint-config-typespec'
version: 0.0.0 version: 0.0.0
@ -15412,7 +15382,7 @@ packages:
dev: false dev: false
file:projects/eslint-plugin.tgz: file:projects/eslint-plugin.tgz:
resolution: {integrity: sha512-JpaB9Vgn/wscM771iqqV7a0yquhUwfspuFfHnatJaNhNV4mB09q1oyOjz7TxdG3vBUwCrffFgQ937LEu7CHIIA==, tarball: file:projects/eslint-plugin.tgz} resolution: {integrity: sha512-IutMRWMzDh8ekd+G0zQIEVUaYm9eq57oxWdIc9gUWrUODcjS+GTnTkTSqPkuHjVp23r4rPuisOagH4cUzARaPw==, tarball: file:projects/eslint-plugin.tgz}
name: '@rush-temp/eslint-plugin' name: '@rush-temp/eslint-plugin'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15432,7 +15402,7 @@ packages:
dev: false dev: false
file:projects/html-program-viewer.tgz: file:projects/html-program-viewer.tgz:
resolution: {integrity: sha512-Sx0YBNJzEm4H8ob+j0qFBftLTkkAct8jsqZfsyz3bQcWRicqqROdXjc8dq+AHrj5wWTlHrYpsA35owxr6/v0mw==, tarball: file:projects/html-program-viewer.tgz} resolution: {integrity: sha512-ErwYPLrbB7F4e+bqtexPP2lFJluP/ifs0uIrxHV/Z85oQjwum8bdcQRKNEY2YSGFpYy+op1ZLVukL1lEGJRN2g==, tarball: file:projects/html-program-viewer.tgz}
name: '@rush-temp/html-program-viewer' name: '@rush-temp/html-program-viewer'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15458,8 +15428,26 @@ packages:
- supports-color - supports-color
dev: false dev: false
file:projects/http.tgz:
resolution: {integrity: sha512-+8QdG+qvoJ0GOqYSy/IrdPG79DMrtyDx0IE1486I1TaUfvnj7e7LhXdAkVfEA6NxFZeEkwKGZ7Rxt8JZgTRuUg==, tarball: file:projects/http.tgz}
name: '@rush-temp/http'
version: 0.0.0
dependencies:
'@types/mocha': 10.0.1
'@types/node': 18.11.18
c8: 7.12.0
eslint: 8.33.0
mocha: 10.1.0
mocha-junit-reporter: 2.2.0_mocha@10.1.0
mocha-multi-reporters: 1.5.1_mocha@10.1.0
rimraf: 3.0.2
typescript: 4.9.5
transitivePeerDependencies:
- supports-color
dev: false
file:projects/internal-build-utils.tgz: file:projects/internal-build-utils.tgz:
resolution: {integrity: sha512-AuoNyH0tlfYGipnYP9ZAIWG7fA8A7NwzrSryKCZlQUUbU1ewH3b3cntF1e1kA5SNoU8U9FC49S+FaYTvO7rHpg==, tarball: file:projects/internal-build-utils.tgz} resolution: {integrity: sha512-JN9avh7DaGqtRw3kxRvvpDFdDrKokHIYNe52qpSZU8CBE1n6TQTQgumtjaTC6kxdaG4OpxnMaWAbbnZeu3QyQw==, tarball: file:projects/internal-build-utils.tgz}
name: '@rush-temp/internal-build-utils' name: '@rush-temp/internal-build-utils'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15483,7 +15471,7 @@ packages:
dev: false dev: false
file:projects/library-linter.tgz: file:projects/library-linter.tgz:
resolution: {integrity: sha512-Fka0bz2WHnISX0u4IJzFTvryU1TACZ1Rf0PPossAYZAhpgnTPQc0GTtyxr4MB5iW2TTaWR/OcR8cAFxYn0OypQ==, tarball: file:projects/library-linter.tgz} resolution: {integrity: sha512-YxB1A4KBqtBOahnkriSZuPENCoaqNEI5e5WkLVa5Rzdd1inZoKi3EXXyX+nu1WmbgI3MgS7hoaMVfaIpYWtLxQ==, tarball: file:projects/library-linter.tgz}
name: '@rush-temp/library-linter' name: '@rush-temp/library-linter'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15501,7 +15489,7 @@ packages:
dev: false dev: false
file:projects/lint.tgz: file:projects/lint.tgz:
resolution: {integrity: sha512-A5XqSvfcIlQ0zZiEdsFUi/BQYozV9ZlgGH6PTP9r1yjeHF60eRfCn7eQ7qmByAUP4HpuedROQFUc27NeVGhvNQ==, tarball: file:projects/lint.tgz} resolution: {integrity: sha512-FGk655BBWZ30EdulGe5jklyl8JH0DT0vKqckZj8cLqUUtqYVuHIKpgUimH27vuk5wekRR3d1clMkK9Gqjr96uw==, tarball: file:projects/lint.tgz}
name: '@rush-temp/lint' name: '@rush-temp/lint'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15519,13 +15507,13 @@ packages:
dev: false dev: false
file:projects/migrate.tgz: file:projects/migrate.tgz:
resolution: {integrity: sha512-1Ma3qCMMfG1Ia4PFP2yap3q38qSa5tRfttPbcz4E6NEkCIzT3v10mAttASpeKvTtlcgc4WOA7yhQNceEsQlSVw==, tarball: file:projects/migrate.tgz} resolution: {integrity: sha512-6q4jXfgG3kBpX8Hg/tj9rsgSEO8Qfl8mJpe7XKrRx9/21MAdhSkocOnBA/IS6CBF5kQMVyumJxAnmH78yeLCsA==, tarball: file:projects/migrate.tgz}
name: '@rush-temp/migrate' name: '@rush-temp/migrate'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
'@typespec/compiler-v0.37': /@typespec/compiler/0.37.0
'@types/mocha': 10.0.1 '@types/mocha': 10.0.1
'@types/node': 18.11.18 '@types/node': 18.11.18
'@typespec/compiler-v0.37': /@cadl-lang/compiler/0.37.0
c8: 7.12.0 c8: 7.12.0
eslint: 8.33.0 eslint: 8.33.0
globby: 13.1.3 globby: 13.1.3
@ -15539,7 +15527,7 @@ packages:
dev: false dev: false
file:projects/openapi.tgz: file:projects/openapi.tgz:
resolution: {integrity: sha512-CK5gME9cSVV9ZYCnXCaoi6gj0zZ7aSSP5vAGqveADERx513T+EL6Q06Lcp+4xwi45A7KSOOGYdeX9S/gnUejAA==, tarball: file:projects/openapi.tgz} resolution: {integrity: sha512-tqUvUFEZb3kr+MR/bSdTrTEj3HGtgY1jMHiUE8JCQcr09FzxOOHRVvplEu1reHm5y+JISLuXqzvU+1NRfOEIvQ==, tarball: file:projects/openapi.tgz}
name: '@rush-temp/openapi' name: '@rush-temp/openapi'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15557,7 +15545,7 @@ packages:
dev: false dev: false
file:projects/openapi3.tgz: file:projects/openapi3.tgz:
resolution: {integrity: sha512-GQHFstH+1aQuPdR8JbXabGkjhZpShwtReIOu45pILsbCeXLB44YF0SJ+6uCc8BAagAKzzGnf/PZx0aXqYxYwEA==, tarball: file:projects/openapi3.tgz} resolution: {integrity: sha512-+PHWnKVDGJixPRMlC+6itwq88+fx5YAS4LXV0kZt8Ioa6zXxR51u2A8X8r/Mu/b6+CG6M551odTjvxeVKJ8MoQ==, tarball: file:projects/openapi3.tgz}
name: '@rush-temp/openapi3' name: '@rush-temp/openapi3'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15577,7 +15565,7 @@ packages:
dev: false dev: false
file:projects/playground.tgz_rollup@3.4.0: file:projects/playground.tgz_rollup@3.4.0:
resolution: {integrity: sha512-5JQ/htgUdR2QkudeSnFYUuncGJAAtc3rH7PH0rKvd80qYrSioVaiJt4B43NDIdgm/kJzbyIVNsZufnCFy6v0vw==, tarball: file:projects/playground.tgz} resolution: {integrity: sha512-c/ILHfQHEWFiRnAzJoFaJmNVbfHxQ2wc/T1a64rMX3VxMHlU+8mxlqAIacoqhRABlUfJWw4itIvU70x3tA/NWQ==, tarball: file:projects/playground.tgz}
id: file:projects/playground.tgz id: file:projects/playground.tgz
name: '@rush-temp/playground' name: '@rush-temp/playground'
version: 0.0.0 version: 0.0.0
@ -15633,7 +15621,7 @@ packages:
dev: false dev: false
file:projects/prettier-plugin-typespec.tgz: file:projects/prettier-plugin-typespec.tgz:
resolution: {integrity: sha512-GQEQVjZpwz9D0zGQReL53HT5BBX0Q1hPEDggYYbefl1xvnPhnTm6PWtVJxxzsOVnR51X8GXqZGpsSTHnAyJgzQ==, tarball: file:projects/prettier-plugin-typespec.tgz} resolution: {integrity: sha512-8LdJ1gW/+DKqFWba1QEPiADV9i2YYno4NxWQUwt4lAhOiZG9g0m8urceuGcA4uMZ1zujSq3W9XUhjcKi3jHdhA==, tarball: file:projects/prettier-plugin-typespec.tgz}
name: '@rush-temp/prettier-plugin-typespec' name: '@rush-temp/prettier-plugin-typespec'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15651,7 +15639,7 @@ packages:
dev: false dev: false
file:projects/ref-doc.tgz: file:projects/ref-doc.tgz:
resolution: {integrity: sha512-wS+w8Y2yYQ+VuQMVvzQ9NNH5WZnje28vj0yRGn+TQ0ROnhHEWBqVFDbJ7kiBS6l7Z4VELj6r5lLPpV49xpMcdA==, tarball: file:projects/ref-doc.tgz} resolution: {integrity: sha512-F05bWju+vc/BFJ4CslZBKji0W2zzeUYXYy9m301kHclKjj3a5yxZzgxlvaGLUo1HIrsVterjimr7qfoG1qNcyw==, tarball: file:projects/ref-doc.tgz}
name: '@rush-temp/ref-doc' name: '@rush-temp/ref-doc'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15673,7 +15661,7 @@ packages:
dev: false dev: false
file:projects/rest.tgz: file:projects/rest.tgz:
resolution: {integrity: sha512-qmAjQIZG/a2eXCuOLa9gv2rsXESSO/N/rhuyljhvaer1Tmy6JgNf55ipsNwiijxPCM7qL4ofb0a73DnuZNSjBA==, tarball: file:projects/rest.tgz} resolution: {integrity: sha512-ptBSo46OFKkgaUZZb0kjmBBuZg/egYcGFIRaYqiS7oaoigs06co1hjT+ji4FE+7NfZAD1jNA38Kd6chFH8SVkw==, tarball: file:projects/rest.tgz}
name: '@rush-temp/rest' name: '@rush-temp/rest'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15691,7 +15679,7 @@ packages:
dev: false dev: false
file:projects/samples.tgz: file:projects/samples.tgz:
resolution: {integrity: sha512-J8rfWGyUAC6FxDqSpObgDxNOvO0nNJZqbBGKaGuM/4TBIyP5GdSA5uZcC78o4pJxmAfHoUB+JpVDpKeySH+Gtw==, tarball: file:projects/samples.tgz} resolution: {integrity: sha512-lnDPU+AYTd0YwkPNpnzismxBohIgIhxeZPFzLYsFQPm73JIt6nqdECwuu4B8G3kG/KhA0ds9qqy+suF6WI1uTw==, tarball: file:projects/samples.tgz}
name: '@rush-temp/samples' name: '@rush-temp/samples'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15703,7 +15691,7 @@ packages:
dev: false dev: false
file:projects/spec.tgz: file:projects/spec.tgz:
resolution: {integrity: sha512-v3Z58mWy52wY1DKsE+T0T1K2BTCvjYrYHr4tsih7Zk8QZHZqrhSqfK7tPNTQJ5MLiXdYDusxJZb3inUKmdHClQ==, tarball: file:projects/spec.tgz} resolution: {integrity: sha512-pj6X+JJQIRzYDmPmyx9tcoRvPndJn1RrI/AJApESrfXWUNF7kOnAunmGd7VvESJfpOhFvLl50fAdg9r5cuhNVw==, tarball: file:projects/spec.tgz}
name: '@rush-temp/spec' name: '@rush-temp/spec'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15718,7 +15706,7 @@ packages:
dev: false dev: false
file:projects/tmlanguage-generator.tgz: file:projects/tmlanguage-generator.tgz:
resolution: {integrity: sha512-DejsT0k6kmu6GmONzDQw96JqgEIYqWYjeELVamATe1yDkshqoPDttcj2jnAYWLT1yl+COtWynzb6pTNdUJx6tw==, tarball: file:projects/tmlanguage-generator.tgz} resolution: {integrity: sha512-vr/oeq6/6mo79rEz2Et39cROBjxYr7rwX7NvJkt/cLPvq3CzK22kgixQkyPhScyCZVPW4WRmv2iurKbH8s7G+Q==, tarball: file:projects/tmlanguage-generator.tgz}
name: '@rush-temp/tmlanguage-generator' name: '@rush-temp/tmlanguage-generator'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15733,8 +15721,40 @@ packages:
- supports-color - supports-color
dev: false dev: false
file:projects/typespec-vs.tgz:
resolution: {integrity: sha512-S8FACAX4yQTj2TnM1wafi55Z9SFJnTvW4MaDOlIkO2x48DpnbHZaBzSNIn43i4iLLvYcpAnazuFNKk2dwtEiIg==, tarball: file:projects/typespec-vs.tgz}
name: '@rush-temp/typespec-vs'
version: 0.0.0
dev: false
file:projects/typespec-vscode.tgz:
resolution: {integrity: sha512-cKCr5r+q3U04L4+WvAh3YLMHGQW0jP26/eD4YoQzW0qF5TfRg1qTTVAnCONSggiHGR9GGhGVl2kWefA6OHpt3w==, tarball: file:projects/typespec-vscode.tgz}
name: '@rush-temp/typespec-vscode'
version: 0.0.0
dependencies:
'@rollup/plugin-commonjs': 23.0.7_rollup@3.4.0
'@rollup/plugin-node-resolve': 15.0.1_rollup@3.4.0
'@types/mkdirp': 1.0.2
'@types/mocha': 10.0.1
'@types/node': 18.11.18
'@types/vscode': 1.53.0
'@vscode/vsce': 2.15.0
c8: 7.12.0
eslint: 8.33.0
mkdirp: 1.0.4
mocha: 10.1.0
mocha-junit-reporter: 2.2.0_mocha@10.1.0
mocha-multi-reporters: 1.5.1_mocha@10.1.0
rimraf: 3.0.2
rollup: 3.4.0
typescript: 4.9.5
vscode-languageclient: 8.0.2
transitivePeerDependencies:
- supports-color
dev: false
file:projects/versioning.tgz: file:projects/versioning.tgz:
resolution: {integrity: sha512-W9N1Bu61Uv0uucYLOTIUtyWyc90ZMw4IIHpImbxutNRKSwDiB/RezfurWyV00baH309ua3e+Yc2MW0VJZfM9jw==, tarball: file:projects/versioning.tgz} resolution: {integrity: sha512-kZZ3sFk/i3U9Sl0TxlLaxRHEMSmvAVy3EKpFIwhH+7wEQN+cpwZvuBpuCWj263JQK7yIE1js973eKXajZIGPcg==, tarball: file:projects/versioning.tgz}
name: '@rush-temp/versioning' name: '@rush-temp/versioning'
version: 0.0.0 version: 0.0.0
dependencies: dependencies:
@ -15752,7 +15772,7 @@ packages:
dev: false dev: false
file:projects/website.tgz_@types+react@18.0.27: file:projects/website.tgz_@types+react@18.0.27:
resolution: {integrity: sha512-2QdPBteBUQopsDuQXD+Jd6S60XMMyx4ruWIj/Ys3jcaxAEDI9JUKfWXpz9aR8ySQ5ckj48T63QjsNWo/8fB9yw==, tarball: file:projects/website.tgz} resolution: {integrity: sha512-DP2BOM3gpf89GPfCB2zPKEjHbsnbIovcSyTyh86cAk7hQviHDQvStyVLE4PmTZDM/FNF60uwaKGt4GM48BrYNQ==, tarball: file:projects/website.tgz}
id: file:projects/website.tgz id: file:projects/website.tgz
name: '@rush-temp/website' name: '@rush-temp/website'
version: 0.0.0 version: 0.0.0

Просмотреть файл

@ -23,16 +23,16 @@ However, if your emitter does want to split types as OpenAPI does, then it will
These are the main API involved in handling these features. See the linked API reference documentation for more details. These are the main API involved in handling these features. See the linked API reference documentation for more details.
- [`getRequestVisibility(HttpVerb): Visibility`](../standard-library/rest/reference/js-api/modules/http.md#getrequestvisibility) - Use this to determine the visibility implied for data in the request parameters or body. Also note that [`Visibility.Read`](../standard-library/rest/reference/js-api/enums/http.Visibility#item) is always applied for response data and therefore there is no corresponding API for the response. - [`getRequestVisibility(HttpVerb): Visibility`](../standard-library/http/reference/js-api/index.md#getrequestvisibility) - Use this to determine the visibility implied for data in the request parameters or body. Also note that [`Visibility.Read`](../standard-library/rest/reference/js-api/enums/http.Visibility#item) is always applied for response data and therefore there is no corresponding API for the response.
- [`MetadataInfo`](../standard-library/rest/reference/js-api/interfaces/http.MetadataInfo.md) - Create this once for each program using [`createMetadataInfo(Program, MetadataInfoOptions)`](../standard-library/rest/reference/js-api/modules/http.md#createmetadatainfo) then use it to reason about metadata and visibility implications with the API below. - [`MetadataInfo`](../standard-library/http/reference/js-api/interfaces/MetadataInfo.md) - Create this once for each program using [`createMetadataInfo(Program, MetadataInfoOptions)`](../standard-library/http/reference/js-api/index.md#createmetadatainfo) then use it to reason about metadata and visibility implications with the API below.
- [`MetadataInfo.getEffectivePayloadType(Type, Visibility): Type`](../standard-library/rest/reference/js-api/interfaces/http.MetadataInfo.md#geteffectivepayloadtype) - Use this recursively on every type that is referenced. When given an anonymous model sourced entirely from a single named model after metadata is moved elsewhere or invisible properties are removed, it will recover the named model. This handles the commonly discussed case of seeing that `op something(...Thing)` receives a `Thing` in its request body, but also many other cases. - [`MetadataInfo.getEffectivePayloadType(Type, Visibility): Type`](../standard-library/http/reference/js-api/interfaces/MetadataInfo.md#geteffectivepayloadtype) - Use this recursively on every type that is referenced. When given an anonymous model sourced entirely from a single named model after metadata is moved elsewhere or invisible properties are removed, it will recover the named model. This handles the commonly discussed case of seeing that `op something(...Thing)` receives a `Thing` in its request body, but also many other cases.
- [`MetadataInfo.isTransformed(Model, Visibility)`](../standard-library/rest/reference/js-api/interfaces/http.MetadataInfo.md#istransformed) - Use this to check if a type undergoes any changes in shape due to visibility or metadata. If not, this can allow for simplifications in emit. - [`MetadataInfo.isTransformed(Model, Visibility)`](../standard-library/http/reference/js-api/interfaces/MetadataInfo.md#istransformed) - Use this to check if a type undergoes any changes in shape due to visibility or metadata. If not, this can allow for simplifications in emit.
- [`MetadataInfo.isPayloadProperty(ModelProperty, Visibility): boolean`](../standard-library/rest/reference/js-api/interfaces/http.MetadataInfo.md#ispayloadproperty) - Use this to check if a property is transmitted as an object property in the payload and is not invisible or metadata sent elsewhere. - [`MetadataInfo.isPayloadProperty(ModelProperty, Visibility): boolean`](../standard-library/http/reference/js-api/interfaces/MetadataInfo.md#ispayloadproperty) - Use this to check if a property is transmitted as an object property in the payload and is not invisible or metadata sent elsewhere.
- [`MetadataInfo.isOptional(ModelProperty, Visibility): boolean`](../standard-library/rest/reference/js-api/interfaces/http.MetadataInfo.md#isoptional) - Use this to determine if a property is optional for the given visibility. This will differ from `ModelProperty.isOptional` when the Visibility is Update in which case the property is always considered optional. - [`MetadataInfo.isOptional(ModelProperty, Visibility): boolean`](../standard-library/http/reference/js-api/interfaces/MetadataInfo.md#isoptional) - Use this to determine if a property is optional for the given visibility. This will differ from `ModelProperty.isOptional` when the Visibility is Update in which case the property is always considered optional.
- [`Visibility.Item`](../standard-library/rest/reference/js-api/enums/http.Visibility#item) - Add this flag when recursing into an array. This moves all metadata into the payload, which can be useful in scenarios like batching API calls. - [`Visibility.Item`](../standard-library/rest/reference/js-api/enums/http.Visibility#item) - Add this flag when recursing into an array. This moves all metadata into the payload, which can be useful in scenarios like batching API calls.

Просмотреть файл

@ -70,9 +70,7 @@ namespace Pets {
### Automatic route generation ### Automatic route generation
Instead of manually specifying routes using the `@route` decorator, you automatically generate Instead of manually specifying routes using the `@route` decorator, you automatically generate routes from operation parameters by applying the `@autoRoute` decorator to an operation or interface containing operations.
routes from operation parameters by applying the `@autoRoute` decorator to an operation, namespace,
or interface containing operations.
For this to work, an operation's path parameters (those marked with `@path`) must also be marked with For this to work, an operation's path parameters (those marked with `@path`) must also be marked with
the `@segment` decorator to define the preceding path segment. the `@segment` decorator to define the preceding path segment.

Просмотреть файл

@ -0,0 +1,275 @@
---
title: "Data types"
toc_min_heading_level: 2
toc_max_heading_level: 3
---
# Data types
## TypeSpec.Http
### `Response` {#TypeSpec.Http.Response}
```typespec
model Response<Status>
```
#### Template Parameters
| Name | Description |
| ------ | ----------- |
| Status | |
### `Body` {#TypeSpec.Http.Body}
Defines a model with a single property of the given type, marked with `@body`.
This can be useful in situations where you cannot use a bare T as the body
and it is awkward to add a property.
```typespec
model Body<T>
```
#### Template Parameters
| Name | Description |
| ---- | ----------- |
| T | |
### `LocationHeader` {#TypeSpec.Http.LocationHeader}
```typespec
model TypeSpec.Http.LocationHeader
```
### `HeaderOptions` {#TypeSpec.Http.HeaderOptions}
Header options.
```typespec
model TypeSpec.Http.HeaderOptions
```
### `OkResponse` {#TypeSpec.Http.OkResponse}
```typespec
model TypeSpec.Http.OkResponse
```
### `CreatedResponse` {#TypeSpec.Http.CreatedResponse}
```typespec
model TypeSpec.Http.CreatedResponse
```
### `AcceptedResponse` {#TypeSpec.Http.AcceptedResponse}
```typespec
model TypeSpec.Http.AcceptedResponse
```
### `NoContentResponse` {#TypeSpec.Http.NoContentResponse}
```typespec
model TypeSpec.Http.NoContentResponse
```
### `MovedResponse` {#TypeSpec.Http.MovedResponse}
```typespec
model TypeSpec.Http.MovedResponse
```
### `NotModifiedResponse` {#TypeSpec.Http.NotModifiedResponse}
```typespec
model TypeSpec.Http.NotModifiedResponse
```
### `BadRequestResponse` {#TypeSpec.Http.BadRequestResponse}
```typespec
model TypeSpec.Http.BadRequestResponse
```
### `UnauthorizedResponse` {#TypeSpec.Http.UnauthorizedResponse}
```typespec
model TypeSpec.Http.UnauthorizedResponse
```
### `ForbiddenResponse` {#TypeSpec.Http.ForbiddenResponse}
```typespec
model TypeSpec.Http.ForbiddenResponse
```
### `NotFoundResponse` {#TypeSpec.Http.NotFoundResponse}
```typespec
model TypeSpec.Http.NotFoundResponse
```
### `ConflictResponse` {#TypeSpec.Http.ConflictResponse}
```typespec
model TypeSpec.Http.ConflictResponse
```
### `PlainData` {#TypeSpec.Http.PlainData}
Produces a new model with the same properties as T, but with `@query`,
`@header`, `@body`, and `@path` decorators removed from all properties.
```typespec
model PlainData<T>
```
#### Template Parameters
| Name | Description |
| ---- | ----------- |
| T | |
### `QueryOptions` {#TypeSpec.Http.QueryOptions}
Query parameter options.
```typespec
model TypeSpec.Http.QueryOptions
```
### `BasicAuth` {#TypeSpec.Http.BasicAuth}
Basic authentication is a simple authentication scheme built into the HTTP protocol.
The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password.
For example, to authorize as demo / `p@55w0rd` the client would send
```
Authorization: Basic ZGVtbzpwQDU1dzByZA==
```
```typespec
model TypeSpec.Http.BasicAuth
```
### `BearerAuth` {#TypeSpec.Http.BearerAuth}
Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens.
The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The bearer token is a cryptic string, usually generated by the server in response to a login request.
The client must send this token in the Authorization header when making requests to protected resources:
```
Authorization: Bearer <token>
```
```typespec
model TypeSpec.Http.BearerAuth
```
### `ApiKeyAuth` {#TypeSpec.Http.ApiKeyAuth}
An API key is a token that a client provides when making API calls. The key can be sent in the query string:
```
GET /something?api_key=abcdef12345
```
or as a request header
```
GET /something HTTP/1.1
X-API-Key: abcdef12345
```
or as a cookie
```
GET /something HTTP/1.1
Cookie: X-API-KEY=abcdef12345
```
```typespec
model ApiKeyAuth<TLocation, TName>
```
#### Template Parameters
| Name | Description |
| --------- | ----------- |
| TLocation | |
| TName | |
### `OAuth2Auth` {#TypeSpec.Http.OAuth2Auth}
OAuth 2.0 is an authorization protocol that gives an API client limited access to user data on a web server.
OAuth relies on authentication scenarios called flows, which allow the resource owner (user) to share the protected content from the resource server without sharing their credentials.
For that purpose, an OAuth 2.0 server issues access tokens that the client applications can use to access protected resources on behalf of the resource owner.
For more information about OAuth 2.0, see oauth.net and RFC 6749.
```typespec
model OAuth2Auth<TFlows>
```
#### Template Parameters
| Name | Description |
| ------ | ----------- |
| TFlows | |
### `AuthorizationCodeFlow` {#TypeSpec.Http.AuthorizationCodeFlow}
Authorization Code flow
```typespec
model TypeSpec.Http.AuthorizationCodeFlow
```
### `ImplicitFlow` {#TypeSpec.Http.ImplicitFlow}
Implicit flow
```typespec
model TypeSpec.Http.ImplicitFlow
```
### `PasswordFlow` {#TypeSpec.Http.PasswordFlow}
Resource Owner Password flow
```typespec
model TypeSpec.Http.PasswordFlow
```
### `ClientCredentialsFlow` {#TypeSpec.Http.ClientCredentialsFlow}
Client credentials flow
```typespec
model TypeSpec.Http.ClientCredentialsFlow
```
### `AuthType` {#TypeSpec.Http.AuthType}
Authentication type
```typespec
enum TypeSpec.Http.AuthType
```
### `ApiKeyLocation` {#TypeSpec.Http.ApiKeyLocation}
Describes the location of the API key
```typespec
enum TypeSpec.Http.ApiKeyLocation
```
### `OAuth2FlowType` {#TypeSpec.Http.OAuth2FlowType}
Describes the OAuth2 flow type
```typespec
enum TypeSpec.Http.OAuth2FlowType
```

Просмотреть файл

@ -0,0 +1,305 @@
---
title: "Decorators"
toc_min_heading_level: 2
toc_max_heading_level: 3
---
# Decorators
## TypeSpec.Http
### `@statusCode` {#@TypeSpec.Http.statusCode}
Specify the status code for this response. Property type must be a status code integer or a union of status code integer.
```typespec
dec TypeSpec.Http.statusCode(target: TypeSpec.Reflection.ModelProperty)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
op read(): {@statusCode: 200, @body pet: Pet}
op create(): {@statusCode: 201 | 202}
```
### `@body` {#@TypeSpec.Http.body}
Explicitly specify that this property is to be set as the body
```typespec
dec TypeSpec.Http.body(target: TypeSpec.Reflection.ModelProperty)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
op upload(@body image: bytes): void;
op download(): {@body image: bytes};
```
### `@header` {#@TypeSpec.Http.header}
Specify this property is to be sent or received as an http header.
```typespec
dec TypeSpec.Http.header(target: TypeSpec.Reflection.ModelProperty, headerNameOrOptions?: TypeSpec.string | TypeSpec.Http.HeaderOptions)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ------------------- | ------------------------------------------------------ | ------------------------------------------------------------------ |
| headerNameOrOptions | `union TypeSpec.string \| TypeSpec.Http.HeaderOptions` | Optional name of the header when sent over http or header options. |
### `@query` {#@TypeSpec.Http.query}
Specify this property is to be sent as a query parameter.
```typespec
dec TypeSpec.Http.query(target: TypeSpec.Reflection.ModelProperty, queryNameOrOptions?: TypeSpec.string | TypeSpec.Http.QueryOptions)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------- |
| queryNameOrOptions | `union TypeSpec.string \| TypeSpec.Http.QueryOptions` | Optional name of the query when included in the url or query parameter options. |
### `@path` {#@TypeSpec.Http.path}
Explicitly specify that this property is to be interpolated as a path parameter.
```typespec
dec TypeSpec.Http.path(target: TypeSpec.Reflection.ModelProperty, paramName?: TypeSpec.string)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| --------- | ------------------------ | --------------------------------------------------- |
| paramName | `scalar TypeSpec.string` | Optional name of the parameter in the url template. |
### `@get` {#@TypeSpec.Http.get}
Specify the http verb for the target operation to be `GET`.
```typespec
dec TypeSpec.Http.get(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@get op read(): string
```
### `@put` {#@TypeSpec.Http.put}
Specify the http verb for the target operation to be `PUT`.
```typespec
dec TypeSpec.Http.put(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@put op set(pet: Pet): void
```
### `@post` {#@TypeSpec.Http.post}
Specify the http verb for the target operation to be `POST`.
```typespec
dec TypeSpec.Http.post(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@post op create(pet: Pet): void
```
### `@patch` {#@TypeSpec.Http.patch}
Specify the http verb for the target operation to be `PATCH`.
```typespec
dec TypeSpec.Http.patch(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@patch op update(pet: Pet): void
```
### `@delete` {#@TypeSpec.Http.delete}
Specify the http verb for the target operation to be `DELETE`.
```typespec
dec TypeSpec.Http.delete(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@delete op set(petId: string): void
```
### `@head` {#@TypeSpec.Http.head}
Specify the http verb for the target operation to be `HEAD`.
```typespec
dec TypeSpec.Http.head(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@head op ping(petId: string): void
```
### `@server` {#@TypeSpec.Http.server}
Specify the endpoint for this service.
```typespec
dec TypeSpec.Http.server(target: TypeSpec.Reflection.Namespace, url: TypeSpec.string, description: TypeSpec.string, parameters?: TypeSpec.object)
```
#### Target
`Namespace`
#### Parameters
| Name | Type | Description |
| ----------- | ------------------------ | ------------------------------------------------------- |
| url | `scalar TypeSpec.string` | Description of the endpoint |
| description | `scalar TypeSpec.string` | |
| parameters | `model TypeSpec.object` | Optional set of parameters used to interpolate the url. |
### `@useAuth` {#@TypeSpec.Http.useAuth}
Specify this service authentication. See the [documentation in the Http library][https://microsoft.github.io/typespec/standard-library/rest/authentication] for full details.
```typespec
dec TypeSpec.Http.useAuth(target: TypeSpec.Reflection.Namespace, auth: TypeSpec.object | TypeSpec.Reflection.Union | TypeSpec.object[])
```
#### Target
`Namespace`
#### Parameters
| Name | Type | Description |
| ---- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| auth | `union TypeSpec.object \| TypeSpec.Reflection.Union \| TypeSpec.object[]` | Authentication configuration. Can be a single security scheme, a union(either option is valid authentication) or a tuple(Must use all authentication together) |
### `@includeInapplicableMetadataInPayload` {#@TypeSpec.Http.includeInapplicableMetadataInPayload}
Specify if inapplicable metadata should be included in the payload for the given entity.
```typespec
dec TypeSpec.Http.includeInapplicableMetadataInPayload(target: unknown, value: TypeSpec.boolean)
```
#### Target
`(intrinsic) unknown`
#### Parameters
| Name | Type | Description |
| ----- | ------------------------- | ----------- |
| value | `scalar TypeSpec.boolean` | |

Просмотреть файл

@ -0,0 +1,53 @@
---
title: Index
sidebar_position: 0
toc_min_heading_level: 2
toc_max_heading_level: 3
---
## TypeSpec.Http
### Decorators
- [`@statusCode`](./decorators.md#@TypeSpec.Http.statusCode)
- [`@body`](./decorators.md#@TypeSpec.Http.body)
- [`@header`](./decorators.md#@TypeSpec.Http.header)
- [`@query`](./decorators.md#@TypeSpec.Http.query)
- [`@path`](./decorators.md#@TypeSpec.Http.path)
- [`@get`](./decorators.md#@TypeSpec.Http.get)
- [`@put`](./decorators.md#@TypeSpec.Http.put)
- [`@post`](./decorators.md#@TypeSpec.Http.post)
- [`@patch`](./decorators.md#@TypeSpec.Http.patch)
- [`@delete`](./decorators.md#@TypeSpec.Http.delete)
- [`@head`](./decorators.md#@TypeSpec.Http.head)
- [`@server`](./decorators.md#@TypeSpec.Http.server)
- [`@useAuth`](./decorators.md#@TypeSpec.Http.useAuth)
- [`@includeInapplicableMetadataInPayload`](./decorators.md#@TypeSpec.Http.includeInapplicableMetadataInPayload)
### Models
- [`Response`](./data-types.md#TypeSpec.Http.Response)
- [`Body`](./data-types.md#TypeSpec.Http.Body)
- [`LocationHeader`](./data-types.md#TypeSpec.Http.LocationHeader)
- [`HeaderOptions`](./data-types.md#TypeSpec.Http.HeaderOptions)
- [`OkResponse`](./data-types.md#TypeSpec.Http.OkResponse)
- [`CreatedResponse`](./data-types.md#TypeSpec.Http.CreatedResponse)
- [`AcceptedResponse`](./data-types.md#TypeSpec.Http.AcceptedResponse)
- [`NoContentResponse`](./data-types.md#TypeSpec.Http.NoContentResponse)
- [`MovedResponse`](./data-types.md#TypeSpec.Http.MovedResponse)
- [`NotModifiedResponse`](./data-types.md#TypeSpec.Http.NotModifiedResponse)
- [`BadRequestResponse`](./data-types.md#TypeSpec.Http.BadRequestResponse)
- [`UnauthorizedResponse`](./data-types.md#TypeSpec.Http.UnauthorizedResponse)
- [`ForbiddenResponse`](./data-types.md#TypeSpec.Http.ForbiddenResponse)
- [`NotFoundResponse`](./data-types.md#TypeSpec.Http.NotFoundResponse)
- [`ConflictResponse`](./data-types.md#TypeSpec.Http.ConflictResponse)
- [`PlainData`](./data-types.md#TypeSpec.Http.PlainData)
- [`QueryOptions`](./data-types.md#TypeSpec.Http.QueryOptions)
- [`BasicAuth`](./data-types.md#TypeSpec.Http.BasicAuth)
- [`BearerAuth`](./data-types.md#TypeSpec.Http.BearerAuth)
- [`ApiKeyAuth`](./data-types.md#TypeSpec.Http.ApiKeyAuth)
- [`OAuth2Auth`](./data-types.md#TypeSpec.Http.OAuth2Auth)
- [`AuthorizationCodeFlow`](./data-types.md#TypeSpec.Http.AuthorizationCodeFlow)
- [`ImplicitFlow`](./data-types.md#TypeSpec.Http.ImplicitFlow)
- [`PasswordFlow`](./data-types.md#TypeSpec.Http.PasswordFlow)
- [`ClientCredentialsFlow`](./data-types.md#TypeSpec.Http.ClientCredentialsFlow)

Просмотреть файл

@ -6,274 +6,6 @@ toc_max_heading_level: 3
# Data types # Data types
## TypeSpec.Http
### `Response` {#TypeSpec.Http.Response}
```typespec
model Response<Status>
```
#### Template Parameters
| Name | Description |
| ------ | ----------- |
| Status | |
### `Body` {#TypeSpec.Http.Body}
Defines a model with a single property of the given type, marked with `@body`.
This can be useful in situations where you cannot use a bare T as the body
and it is awkward to add a property.
```typespec
model Body<T>
```
#### Template Parameters
| Name | Description |
| ---- | ----------- |
| T | |
### `LocationHeader` {#TypeSpec.Http.LocationHeader}
```typespec
model TypeSpec.Http.LocationHeader
```
### `HeaderOptions` {#TypeSpec.Http.HeaderOptions}
Header options.
```typespec
model TypeSpec.Http.HeaderOptions
```
### `OkResponse` {#TypeSpec.Http.OkResponse}
```typespec
model TypeSpec.Http.OkResponse
```
### `CreatedResponse` {#TypeSpec.Http.CreatedResponse}
```typespec
model TypeSpec.Http.CreatedResponse
```
### `AcceptedResponse` {#TypeSpec.Http.AcceptedResponse}
```typespec
model TypeSpec.Http.AcceptedResponse
```
### `NoContentResponse` {#TypeSpec.Http.NoContentResponse}
```typespec
model TypeSpec.Http.NoContentResponse
```
### `MovedResponse` {#TypeSpec.Http.MovedResponse}
```typespec
model TypeSpec.Http.MovedResponse
```
### `NotModifiedResponse` {#TypeSpec.Http.NotModifiedResponse}
```typespec
model TypeSpec.Http.NotModifiedResponse
```
### `BadRequestResponse` {#TypeSpec.Http.BadRequestResponse}
```typespec
model TypeSpec.Http.BadRequestResponse
```
### `UnauthorizedResponse` {#TypeSpec.Http.UnauthorizedResponse}
```typespec
model TypeSpec.Http.UnauthorizedResponse
```
### `ForbiddenResponse` {#TypeSpec.Http.ForbiddenResponse}
```typespec
model TypeSpec.Http.ForbiddenResponse
```
### `NotFoundResponse` {#TypeSpec.Http.NotFoundResponse}
```typespec
model TypeSpec.Http.NotFoundResponse
```
### `ConflictResponse` {#TypeSpec.Http.ConflictResponse}
```typespec
model TypeSpec.Http.ConflictResponse
```
### `PlainData` {#TypeSpec.Http.PlainData}
Produces a new model with the same properties as T, but with `@query`,
`@header`, `@body`, and `@path` decorators removed from all properties.
```typespec
model PlainData<T>
```
#### Template Parameters
| Name | Description |
| ---- | ----------- |
| T | |
### `QueryOptions` {#TypeSpec.Http.QueryOptions}
Query parameter options.
```typespec
model TypeSpec.Http.QueryOptions
```
### `BasicAuth` {#TypeSpec.Http.BasicAuth}
Basic authentication is a simple authentication scheme built into the HTTP protocol.
The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password.
For example, to authorize as demo / `p@55w0rd` the client would send
```
Authorization: Basic ZGVtbzpwQDU1dzByZA==
```
```typespec
model TypeSpec.Http.BasicAuth
```
### `BearerAuth` {#TypeSpec.Http.BearerAuth}
Bearer authentication (also called token authentication) is an HTTP authentication scheme that involves security tokens called bearer tokens.
The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The bearer token is a cryptic string, usually generated by the server in response to a login request.
The client must send this token in the Authorization header when making requests to protected resources:
```
Authorization: Bearer <token>
```
```typespec
model TypeSpec.Http.BearerAuth
```
### `ApiKeyAuth` {#TypeSpec.Http.ApiKeyAuth}
An API key is a token that a client provides when making API calls. The key can be sent in the query string:
```
GET /something?api_key=abcdef12345
```
or as a request header
```
GET /something HTTP/1.1
X-API-Key: abcdef12345
```
or as a cookie
```
GET /something HTTP/1.1
Cookie: X-API-KEY=abcdef12345
```
```typespec
model ApiKeyAuth<TLocation, TName>
```
#### Template Parameters
| Name | Description |
| --------- | ----------- |
| TLocation | |
| TName | |
### `OAuth2Auth` {#TypeSpec.Http.OAuth2Auth}
OAuth 2.0 is an authorization protocol that gives an API client limited access to user data on a web server.
OAuth relies on authentication scenarios called flows, which allow the resource owner (user) to share the protected content from the resource server without sharing their credentials.
For that purpose, an OAuth 2.0 server issues access tokens that the client applications can use to access protected resources on behalf of the resource owner.
For more information about OAuth 2.0, see oauth.net and RFC 6749.
```typespec
model OAuth2Auth<TFlows>
```
#### Template Parameters
| Name | Description |
| ------ | ----------- |
| TFlows | |
### `AuthorizationCodeFlow` {#TypeSpec.Http.AuthorizationCodeFlow}
Authorization Code flow
```typespec
model TypeSpec.Http.AuthorizationCodeFlow
```
### `ImplicitFlow` {#TypeSpec.Http.ImplicitFlow}
Implicit flow
```typespec
model TypeSpec.Http.ImplicitFlow
```
### `PasswordFlow` {#TypeSpec.Http.PasswordFlow}
Resource Owner Password flow
```typespec
model TypeSpec.Http.PasswordFlow
```
### `ClientCredentialsFlow` {#TypeSpec.Http.ClientCredentialsFlow}
Client credentials flow
```typespec
model TypeSpec.Http.ClientCredentialsFlow
```
### `AuthType` {#TypeSpec.Http.AuthType}
Authentication type
```typespec
enum TypeSpec.Http.AuthType
```
### `ApiKeyLocation` {#TypeSpec.Http.ApiKeyLocation}
Describes the location of the API key
```typespec
enum TypeSpec.Http.ApiKeyLocation
```
### `OAuth2FlowType` {#TypeSpec.Http.OAuth2FlowType}
Describes the OAuth2 flow type
```typespec
enum TypeSpec.Http.OAuth2FlowType
```
## TypeSpec.Rest.Resource ## TypeSpec.Rest.Resource
### `ResourceError` {#TypeSpec.Rest.Resource.ResourceError} ### `ResourceError` {#TypeSpec.Rest.Resource.ResourceError}

Просмотреть файл

@ -6,317 +6,19 @@ toc_max_heading_level: 3
# Decorators # Decorators
## TypeSpec.Http
### `@statusCode` {#@TypeSpec.Http.statusCode}
Specify the status code for this response. Property type must be a status code integer or a union of status code integer.
```typespec
dec TypeSpec.Http.statusCode(target: TypeSpec.Reflection.ModelProperty)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
op read(): {@statusCode: 200, @body pet: Pet}
op create(): {@statusCode: 201 | 202}
```
### `@body` {#@TypeSpec.Http.body}
Explicitly specify that this property is to be set as the body
```typespec
dec TypeSpec.Http.body(target: TypeSpec.Reflection.ModelProperty)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
op upload(@body image: bytes): void;
op download(): {@body image: bytes};
```
### `@header` {#@TypeSpec.Http.header}
Specify this property is to be sent or received as an http header.
```typespec
dec TypeSpec.Http.header(target: TypeSpec.Reflection.ModelProperty, headerNameOrOptions?: TypeSpec.string | TypeSpec.Http.HeaderOptions)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ------------------- | ------------------------------------------------------ | ------------------------------------------------------------------ |
| headerNameOrOptions | `union TypeSpec.string \| TypeSpec.Http.HeaderOptions` | Optional name of the header when sent over http or header options. |
### `@query` {#@TypeSpec.Http.query}
Specify this property is to be sent as a query parameter.
```typespec
dec TypeSpec.Http.query(target: TypeSpec.Reflection.ModelProperty, queryNameOrOptions?: TypeSpec.string | TypeSpec.Http.QueryOptions)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| ------------------ | ----------------------------------------------------- | ------------------------------------------------------------------------------- |
| queryNameOrOptions | `union TypeSpec.string \| TypeSpec.Http.QueryOptions` | Optional name of the query when included in the url or query parameter options. |
### `@path` {#@TypeSpec.Http.path}
Explicitly specify that this property is to be interpolated as a path parameter.
```typespec
dec TypeSpec.Http.path(target: TypeSpec.Reflection.ModelProperty, paramName?: TypeSpec.string)
```
#### Target
`ModelProperty`
#### Parameters
| Name | Type | Description |
| --------- | ------------------------ | --------------------------------------------------- |
| paramName | `scalar TypeSpec.string` | Optional name of the parameter in the url template. |
### `@get` {#@TypeSpec.Http.get}
Specify the http verb for the target operation to be `GET`.
```typespec
dec TypeSpec.Http.get(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@get op read(): string
```
### `@put` {#@TypeSpec.Http.put}
Specify the http verb for the target operation to be `PUT`.
```typespec
dec TypeSpec.Http.put(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@put op set(pet: Pet): void
```
### `@post` {#@TypeSpec.Http.post}
Specify the http verb for the target operation to be `POST`.
```typespec
dec TypeSpec.Http.post(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@post op create(pet: Pet): void
```
### `@patch` {#@TypeSpec.Http.patch}
Specify the http verb for the target operation to be `PATCH`.
```typespec
dec TypeSpec.Http.patch(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@patch op update(pet: Pet): void
```
### `@delete` {#@TypeSpec.Http.delete}
Specify the http verb for the target operation to be `DELETE`.
```typespec
dec TypeSpec.Http.delete(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@delete op set(petId: string): void
```
### `@head` {#@TypeSpec.Http.head}
Specify the http verb for the target operation to be `HEAD`.
```typespec
dec TypeSpec.Http.head(target: TypeSpec.Reflection.Operation)
```
#### Target
`Operation`
#### Parameters
| Name | Type | Description |
| ---- | ---- | ----------- |
#### Examples
```typespec
@head op ping(petId: string): void
```
### `@server` {#@TypeSpec.Http.server}
Specify the endpoint for this service.
```typespec
dec TypeSpec.Http.server(target: TypeSpec.Reflection.Namespace, url: TypeSpec.string, description: TypeSpec.string, parameters?: TypeSpec.object)
```
#### Target
`Namespace`
#### Parameters
| Name | Type | Description |
| ----------- | ------------------------ | ------------------------------------------------------- |
| url | `scalar TypeSpec.string` | Description of the endpoint |
| description | `scalar TypeSpec.string` | |
| parameters | `model TypeSpec.object` | Optional set of parameters used to interpolate the url. |
### `@useAuth` {#@TypeSpec.Http.useAuth}
Specify this service authentication. See the [documentation in the Http library][https://microsoft.github.io/typespec/standard-library/rest/authentication] for full details.
```typespec
dec TypeSpec.Http.useAuth(target: TypeSpec.Reflection.Namespace, auth: TypeSpec.object | TypeSpec.Reflection.Union | TypeSpec.object[])
```
#### Target
`Namespace`
#### Parameters
| Name | Type | Description |
| ---- | ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| auth | `union TypeSpec.object \| TypeSpec.Reflection.Union \| TypeSpec.object[]` | Authentication configuration. Can be a single security scheme, a union(either option is valid authentication) or a tuple(Must use all authentication together) |
### `@includeInapplicableMetadataInPayload` {#@TypeSpec.Http.includeInapplicableMetadataInPayload}
Specify if inapplicable metadata should be included in the payload for the given entity.
```typespec
dec TypeSpec.Http.includeInapplicableMetadataInPayload(target: unknown, value: TypeSpec.boolean)
```
#### Target
`(intrinsic) unknown`
#### Parameters
| Name | Type | Description |
| ----- | ------------------------- | ----------- |
| value | `scalar TypeSpec.boolean` | |
## TypeSpec.Rest ## TypeSpec.Rest
### `@autoRoute` {#@TypeSpec.Rest.autoRoute} ### `@autoRoute` {#@TypeSpec.Rest.autoRoute}
This namespace, interface or operation should resolve its route automatically. To be used with resource types where the route segments area defined on the models. This interface or operation should resolve its route automatically. To be used with resource types where the route segments area defined on the models.
```typespec ```typespec
dec TypeSpec.Rest.autoRoute(target: TypeSpec.Reflection.Namespace | TypeSpec.Reflection.Interface | TypeSpec.Reflection.Operation) dec TypeSpec.Rest.autoRoute(target: TypeSpec.Reflection.Interface | TypeSpec.Reflection.Operation)
``` ```
#### Target #### Target
`union TypeSpec.Reflection.Namespace | TypeSpec.Reflection.Interface | TypeSpec.Reflection.Operation` `union TypeSpec.Reflection.Interface | TypeSpec.Reflection.Operation`
#### Parameters #### Parameters

Просмотреть файл

@ -5,53 +5,6 @@ toc_min_heading_level: 2
toc_max_heading_level: 3 toc_max_heading_level: 3
--- ---
## TypeSpec.Http
### Decorators
- [`@statusCode`](./decorators.md#@TypeSpec.Http.statusCode)
- [`@body`](./decorators.md#@TypeSpec.Http.body)
- [`@header`](./decorators.md#@TypeSpec.Http.header)
- [`@query`](./decorators.md#@TypeSpec.Http.query)
- [`@path`](./decorators.md#@TypeSpec.Http.path)
- [`@get`](./decorators.md#@TypeSpec.Http.get)
- [`@put`](./decorators.md#@TypeSpec.Http.put)
- [`@post`](./decorators.md#@TypeSpec.Http.post)
- [`@patch`](./decorators.md#@TypeSpec.Http.patch)
- [`@delete`](./decorators.md#@TypeSpec.Http.delete)
- [`@head`](./decorators.md#@TypeSpec.Http.head)
- [`@server`](./decorators.md#@TypeSpec.Http.server)
- [`@useAuth`](./decorators.md#@TypeSpec.Http.useAuth)
- [`@includeInapplicableMetadataInPayload`](./decorators.md#@TypeSpec.Http.includeInapplicableMetadataInPayload)
### Models
- [`Response`](./data-types.md#TypeSpec.Http.Response)
- [`Body`](./data-types.md#TypeSpec.Http.Body)
- [`LocationHeader`](./data-types.md#TypeSpec.Http.LocationHeader)
- [`HeaderOptions`](./data-types.md#TypeSpec.Http.HeaderOptions)
- [`OkResponse`](./data-types.md#TypeSpec.Http.OkResponse)
- [`CreatedResponse`](./data-types.md#TypeSpec.Http.CreatedResponse)
- [`AcceptedResponse`](./data-types.md#TypeSpec.Http.AcceptedResponse)
- [`NoContentResponse`](./data-types.md#TypeSpec.Http.NoContentResponse)
- [`MovedResponse`](./data-types.md#TypeSpec.Http.MovedResponse)
- [`NotModifiedResponse`](./data-types.md#TypeSpec.Http.NotModifiedResponse)
- [`BadRequestResponse`](./data-types.md#TypeSpec.Http.BadRequestResponse)
- [`UnauthorizedResponse`](./data-types.md#TypeSpec.Http.UnauthorizedResponse)
- [`ForbiddenResponse`](./data-types.md#TypeSpec.Http.ForbiddenResponse)
- [`NotFoundResponse`](./data-types.md#TypeSpec.Http.NotFoundResponse)
- [`ConflictResponse`](./data-types.md#TypeSpec.Http.ConflictResponse)
- [`PlainData`](./data-types.md#TypeSpec.Http.PlainData)
- [`QueryOptions`](./data-types.md#TypeSpec.Http.QueryOptions)
- [`BasicAuth`](./data-types.md#TypeSpec.Http.BasicAuth)
- [`BearerAuth`](./data-types.md#TypeSpec.Http.BearerAuth)
- [`ApiKeyAuth`](./data-types.md#TypeSpec.Http.ApiKeyAuth)
- [`OAuth2Auth`](./data-types.md#TypeSpec.Http.OAuth2Auth)
- [`AuthorizationCodeFlow`](./data-types.md#TypeSpec.Http.AuthorizationCodeFlow)
- [`ImplicitFlow`](./data-types.md#TypeSpec.Http.ImplicitFlow)
- [`PasswordFlow`](./data-types.md#TypeSpec.Http.PasswordFlow)
- [`ClientCredentialsFlow`](./data-types.md#TypeSpec.Http.ClientCredentialsFlow)
## TypeSpec.Rest ## TypeSpec.Rest
### Decorators ### Decorators

Просмотреть файл

@ -44,9 +44,7 @@ interface MyPetOps extends PetOps {
### Automatic route generation ### Automatic route generation
Instead of manually specifying routes using the `@route` decorator, you automatically generate Instead of manually specifying routes using the `@route` decorator, you automatically generate routes from operation parameters by applying the `@autoRoute` decorator to an operation or interface containing operations.
routes from operation parameters by applying the `@autoRoute` decorator to an operation, namespace,
or interface containing operations.
For this to work, an operation's path parameters (those marked with `@path`) must also be marked with For this to work, an operation's path parameters (those marked with `@path`) must also be marked with
the `@segment` decorator to define the preceding path segment. the `@segment` decorator to define the preceding path segment.

Просмотреть файл

@ -3,6 +3,7 @@
"dependencies": { "dependencies": {
"@typespec/compiler": "latest", "@typespec/compiler": "latest",
"@typespec/rest": "latest", "@typespec/rest": "latest",
"@typespec/http": "latest",
"@typespec/versioning": "latest", "@typespec/versioning": "latest",
"@typespec/openapi": "latest", "@typespec/openapi": "latest",
"@typespec/openapi3": "latest" "@typespec/openapi3": "latest"

3
packages/http/.c8rc.json Normal file
Просмотреть файл

@ -0,0 +1,3 @@
{
"reporter": ["cobertura", "json", "text"]
}

Просмотреть файл

@ -0,0 +1,7 @@
require("@typespec/eslint-config-typespec/patch/modern-module-resolution");
module.exports = {
plugins: ["@typespec/eslint-plugin"],
extends: ["@typespec/eslint-config-typespec", "plugin:@typespec/eslint-plugin/recommended"],
parserOptions: { tsconfigRootDir: __dirname },
};

Просмотреть файл

@ -0,0 +1,7 @@
timeout: 5000
require: source-map-support/register
spec: "dist/test/**/*.test.js"
ignore: "dist/test/manual/**/*.js"
# Config for https://www.npmjs.com/package/mocha-multi-reporters
reporterOptions: "configFile=mocha.reporter.config.json"

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "@typespec/http",
"entries": []
}

21
packages/http/LICENSE Normal file
Просмотреть файл

@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

106
packages/http/README.md Normal file
Просмотреть файл

@ -0,0 +1,106 @@
# TypeSpec HTTP Library
This package provides [TypeSpec](https://github.com/microsoft/TypeSpec) decorators, models, and interfaces to describe HTTP APIs. With fundamental models and decorators defined in TypeSpec.Http namespace, you will be able describe basic http level operations.
## Install
In your typespec project root
```bash
npm install @typespec/http
```
## Usage
```TypeSpec
import "@typespec/http";
using TypeSpec.Http;
```
For more information, consult the [HTTP and REST](https://microsoft.github.io/typespec/docs/standard-library/http/) section of the TypeSpec guide.
## Library Tour
`@typespec/http` library defines of the following artifacts:
- [TypeSpec HTTP Library](#typespec-http-library)
- [Install](#install)
- [Usage](#usage)
- [Library Tour](#library-tour)
- [Models](#models)
- [Decorators](#decorators)
- [See also](#see-also)
## Models
| Model | Notes |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| LocationHeader | Location header |
| Response&lt;Status> | &lt;Status> is numerical status code. |
| OkResponse&lt;T> | Response&lt;200> with T as the response body model type. |
| CreatedResponse | Response&lt;201> |
| AcceptedResponse | Response&lt;202> |
| NoContentResponse | Response&lt;204> |
| MovedResponse | Response&lt;301> with LocationHeader for redirected URL |
| NotModifiedResponse | Response&lt;304> |
| UnauthorizedResponse | Response&lt;401> |
| NotFoundResponse | Response&lt;404> |
| ConflictResponse | Response&lt;409> |
| PlainData&lt;T> | Produces a new model with the same properties as T, but with @query, @header, @body, and @path decorators removed from all properties. |
| BasicAuth | Configure `basic` authentication with @useAuth |
| BearerAuth | Configure `bearer` authentication with @useAuth |
| ApiKeyAuth<TLocation, TName> | Configure `apiKey` authentication with @useAuth |
| OAuth2Auth<TFlows> | Configure `oauth2` authentication with @useAuth |
## Decorators
The `@typespec/http` library defines the following decorators in `TypeSpec.Http` namespace:
| Declarator | Scope | Usage |
| ----------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @get | operations | indicating operation uses HTTP `GET` verb. |
| @put | operations | indicating operation uses HTTP `PUT` verb. |
| @post | operations | indicating operation uses HTTP `POST` verb. |
| @patch | operations | indicating operation uses HTTP `PATCH` verb. |
| @delete | operations | indicating operation uses HTTP `DEL` verb. |
| @head | operations | indicating operation uses HTTP `HEAD` verb. |
| @header | model properties and operation parameters | indicating the properties are request or response headers. |
| @query | model properties and operation parameters | indicating the properties are in the request query string. |
| @body | model properties and operation parameters | indicating the property is in request or response body. Only one allowed per model and operation. |
| @path | model properties and operation parameters | indicating the properties are in request path. |
| @statusCode | model properties and operation parameters | indicating the property is the return status code. Only one allowed per model. |
| @server | namespace | Configure the server url for the service. |
| @route | operations, namespaces, interfaces | Syntax:<br> `@route(routeString)`<br><br>Note:<br>`@route` defines the relative route URI for the target operation. The `routeString` argument should be a URI fragment that may contain one or more path parameter fields. If the namespace or interface that contains the operation is also marked with a `@route` decorator, it will be used as a prefix to the route URI of the operation. |
| @useAuth | namespace | Configure the service authentication. |
## How to
### Specify content type
To specify the content type you can add a `@header contentType: <value>` in the operation parameter(For request content type) or return type(For response content type)
Example: return `application/png` byte body
```typespec
op getPng(): {
@header contentType: "application/png";
@body _: bytes;
};
```
Example: expect `application/png` byte body
```typespec
op getPng(@header contentType: "application/png", @body _: bytes): void;
```
## See also
- [HTTP example](https://cadlplayground.z22.web.core.windows.net/?c=aW1wb3J0ICJAY2FkbC1sYW5nL3Jlc3QiOwoKQHNlcnZpY2VUaXRsZSgiV2lkZ2V0IFPGFSIpCm5hbWVzcGFjZSBEZW1vxxg7CnVzaW5nIENhZGwuSHR0cDsKCm1vZGVsIMdAewogIEBrZXkgaWQ6IHN0cmluZzsKICB3ZWlnaHQ6IGludDMyxBFjb2xvcjogInJlZCIgfCAiYmx1ZSI7Cn0KCkBlcnJvcsdWRcQMxVVjb2Rly0BtZXNzYWdlymR9CgppbnRlcmbkALLmAI3nALTFP0DkAJ1saXN0KCk6xx9bXSB8xmHEUUByb3V0ZSgid8Uccy97aWR9IinGOHJlYWQoQHBhdGjrANfJSM1GcG9zdCBjcmVhdGUoQGJvZHkgxAXIK9Y0x3pjdXN0b21HZXTId8kR6gC0yjh9Cg%3D%3D):
- [TypeSpec Getting Started](https://github.com/microsoft/typespec#getting-started)
- [TypeSpec Website](https://microsoft.github.io/typespec)
```
```

Просмотреть файл

Просмотреть файл

@ -1,4 +1,4 @@
import "../dist/src/http/index.js"; import "../dist/src/index.js";
import "./http-decorators.tsp"; import "./http-decorators.tsp";
import "./auth.tsp"; import "./auth.tsp";

Просмотреть файл

@ -0,0 +1,3 @@
{
"reporterEnabled": "spec, mocha-junit-reporter"
}

Просмотреть файл

@ -0,0 +1,72 @@
{
"name": "@typespec/http",
"version": "0.40.0",
"author": "Microsoft Corporation",
"description": "TypeSpec HTTP protocol binding",
"homepage": "https://github.com/Microsoft/typespec",
"readme": "https://github.com/Microsoft/typespec/blob/master/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",
"tspMain": "lib/http.tsp",
"exports": {
".": "./dist/src/index.js",
"./testing": "./dist/src/testing/index.js"
},
"typesVersions": {
"*": {
"*": [
"./dist/src/index.d.ts"
],
"testing": [
"./dist/src/testing/index.d.ts"
]
}
},
"engines": {
"node": ">=16.0.0"
},
"scripts": {
"clean": "rimraf ./dist ./temp",
"build": "tsc -p . && npm run lint-typespec-library",
"watch": "tsc -p . --watch",
"lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit",
"test": "mocha",
"test-official": "c8 mocha --forbid-only --reporter mocha-multi-reporters",
"lint": "eslint . --ext .ts --max-warnings=0",
"lint:fix": "eslint . --fix --ext .ts"
},
"files": [
"lib/*.tsp",
"dist/**",
"!dist/test/**"
],
"peerDependencies": {
"@typespec/compiler": "~0.40.0"
},
"devDependencies": {
"@types/mocha": "~10.0.0",
"@types/node": "~18.11.9",
"@typespec/compiler": "~0.40.0",
"@typespec/eslint-config-typespec": "~0.5.0",
"@typespec/library-linter": "~0.40.0",
"@typespec/eslint-plugin": "~0.40.0",
"eslint": "^8.12.0",
"mocha": "~10.1.0",
"mocha-junit-reporter": "~2.2.0",
"mocha-multi-reporters": "~1.5.1",
"c8": "~7.12.0",
"rimraf": "~3.0.2",
"typescript": "~4.9.3"
}
}

Просмотреть файл

@ -1,6 +1,6 @@
import { createDiagnosticCollector, Diagnostic, ModelProperty, Program } from "@typespec/compiler"; import { createDiagnosticCollector, Diagnostic, ModelProperty, Program } from "@typespec/compiler";
import { createDiagnostic } from "../lib.js";
import { getHeaderFieldName } from "./decorators.js"; import { getHeaderFieldName } from "./decorators.js";
import { createDiagnostic } from "./lib.js";
/** /**
* Check if the given model property is the content type header. * Check if the given model property is the content type header.

Просмотреть файл

@ -4,7 +4,6 @@ import {
Diagnostic, Diagnostic,
DiagnosticTarget, DiagnosticTarget,
getDoc, getDoc,
Interface,
Model, Model,
ModelProperty, ModelProperty,
Namespace, Namespace,
@ -18,8 +17,8 @@ import {
validateDecoratorTarget, validateDecoratorTarget,
validateDecoratorUniqueOnNode, validateDecoratorUniqueOnNode,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { createDiagnostic, createStateSymbol, reportDiagnostic } from "../lib.js"; import { createDiagnostic, createStateSymbol, reportDiagnostic } from "./lib.js";
import { extractParamsFromPath } from "../utils.js"; import { setRoute } from "./route.js";
import { import {
AuthenticationOption, AuthenticationOption,
HeaderFieldOptions, HeaderFieldOptions,
@ -27,10 +26,9 @@ import {
HttpVerb, HttpVerb,
PathParameterOptions, PathParameterOptions,
QueryParameterOptions, QueryParameterOptions,
RouteOptions,
RoutePath,
ServiceAuthentication, ServiceAuthentication,
} from "./types.js"; } from "./types.js";
import { extractParamsFromPath } from "./utils.js";
export const namespace = "TypeSpec.Http"; export const namespace = "TypeSpec.Http";
@ -587,71 +585,10 @@ export function $route(context: DecoratorContext, entity: Type, path: string, pa
setRoute(context, entity, { setRoute(context, entity, {
path, path,
isReset: false,
shared: extractSharedValue(context, parameters), shared: extractSharedValue(context, parameters),
}); });
} }
export function $routeReset(
context: DecoratorContext,
entity: Type,
path: string,
parameters?: Model
) {
setRoute(context, entity, {
path,
isReset: true,
shared: extractSharedValue(context, parameters),
});
}
const routeOptionsKey = createStateSymbol("routeOptions");
export function setRouteOptionsForNamespace(
program: Program,
namespace: Namespace,
options: RouteOptions
) {
program.stateMap(routeOptionsKey).set(namespace, options);
}
export function getRouteOptionsForNamespace(
program: Program,
namespace: Namespace
): RouteOptions | undefined {
return program.stateMap(routeOptionsKey).get(namespace);
}
const routesKey = createStateSymbol("routes");
function setRoute(context: DecoratorContext, entity: Type, details: RoutePath) {
if (
!validateDecoratorTarget(context, entity, "@route", ["Namespace", "Interface", "Operation"])
) {
return;
}
const state = context.program.stateMap(routesKey);
if (state.has(entity) && entity.kind === "Namespace") {
const existingValue: RoutePath = state.get(entity);
if (existingValue.path !== details.path) {
reportDiagnostic(context.program, {
code: "duplicate-route-decorator",
messageId: "namespace",
target: entity,
});
}
} else {
state.set(entity, details);
}
}
export function getRoutePath(
program: Program,
entity: Namespace | Interface | Operation
): RoutePath | undefined {
return program.stateMap(routesKey).get(entity);
}
const includeInapplicableMetadataInPayloadKey = createStateSymbol( const includeInapplicableMetadataInPayloadKey = createStateSymbol(
"includeInapplicableMetadataInPayload" "includeInapplicableMetadataInPayload"
); );

Просмотреть файл

@ -6,4 +6,6 @@ export * from "./metadata.js";
export * from "./operations.js"; export * from "./operations.js";
export * from "./parameters.js"; export * from "./parameters.js";
export * from "./responses.js"; export * from "./responses.js";
export * from "./route.js";
export * from "./types.js"; export * from "./types.js";
export * from "./validate.js";

127
packages/http/src/lib.ts Normal file
Просмотреть файл

@ -0,0 +1,127 @@
import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
const libDefinition = {
name: "@typespec/http",
diagnostics: {
"http-verb-duplicate": {
severity: "error",
messages: {
default: paramMessage`HTTP verb already applied to ${"entityName"}`,
},
},
"http-verb-wrong-type": {
severity: "error",
messages: {
default: paramMessage`Cannot use @${"verb"} on a ${"entityKind"}`,
},
},
"missing-path-param": {
severity: "error",
messages: {
default: paramMessage`Path contains parameter ${"param"} but wasn't found in given parameters`,
},
},
"optional-path-param": {
severity: "error",
messages: {
default: paramMessage`Path parameter '${"paramName"}' cannot be optional.`,
},
},
"missing-server-param": {
severity: "error",
messages: {
default: paramMessage`Server url contains parameter '${"param"}' but wasn't found in given parameters`,
},
},
"duplicate-body": {
severity: "error",
messages: {
default: "Operation has multiple @body parameters declared",
duplicateUnannotated:
"Operation has multiple unannotated parameters. There can only be one representing the body",
bodyAndUnannotated:
"Operation has a @body and an unannotated parameter. There can only be one representing the body",
},
},
"duplicate-route-decorator": {
severity: "error",
messages: {
namespace: "@route was defined twice on this namespace and has different values.",
},
},
"operation-param-duplicate-type": {
severity: "error",
messages: {
default: paramMessage`Param ${"paramName"} has multiple types: [${"types"}]`,
},
},
"duplicate-operation": {
severity: "error",
messages: {
default: paramMessage`Duplicate operation "${"operationName"}" routed at "${"verb"} ${"path"}".`,
},
},
"status-code-invalid": {
severity: "error",
messages: {
default:
"statusCode value must be a numeric or string literal or union of numeric or string literals",
value: "statusCode value must be a three digit code between 100 and 599",
},
},
"content-type-string": {
severity: "error",
messages: {
default: "contentType parameter must be a string literal or union of string literals",
},
},
"duplicate-response": {
severity: "error",
messages: {
default: paramMessage`Multiple return types for content type ${"contentType"} and status code ${"statusCode"}`,
},
},
"content-type-ignored": {
severity: "warning",
messages: {
default: "`Content-Type` header ignored because there is no body.",
},
},
"no-routes": {
severity: "warning",
messages: {
default:
"Current spec is not exposing any routes. This could be to not having the service namespace marked with @service.",
},
},
"invalid-type-for-auth": {
severity: "error",
messages: {
default: paramMessage`@useAuth ${"kind"} only accept Auth model, Tuple of auth model or union of auth model.`,
},
},
"shared-boolean": {
severity: "error",
messages: {
default: "shared parameter must be a boolean.",
},
},
"write-visibility-not-supported": {
severity: "warning",
messages: {
default: `@visibility("write") is not supported. Use @visibility("update"), @visibility("create") or @visibility("create", "update") as appropriate.`,
},
},
"multipart-model": {
severity: "error",
messages: {
default: "Multipart request body must be a model.",
},
},
},
} as const;
const httpLib = createTypeSpecLibrary(libDefinition);
const { reportDiagnostic, createDiagnostic, createStateSymbol } = httpLib;
export { httpLib, reportDiagnostic, createDiagnostic, createStateSymbol };

Просмотреть файл

Просмотреть файл

@ -15,10 +15,9 @@ import {
Program, Program,
SyntaxKind, SyntaxKind,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { createDiagnostic, reportDiagnostic } from "../lib.js"; import { createDiagnostic, reportDiagnostic } from "./lib.js";
import { getRoutePath } from "./decorators.js";
import { getResponsesForOperation } from "./responses.js"; import { getResponsesForOperation } from "./responses.js";
import { resolvePathAndParameters } from "./route.js"; import { isSharedRoute, resolvePathAndParameters } from "./route.js";
import { import {
HttpOperation, HttpOperation,
HttpService, HttpService,
@ -140,8 +139,7 @@ export function validateRouteUnique(
if (operation.overloading !== undefined && isOverloadSameEndpoint(operation as any)) { if (operation.overloading !== undefined && isOverloadSameEndpoint(operation as any)) {
continue; continue;
} }
const pathShared = getRoutePath(program, operation.operation)?.shared ?? false; if (isSharedRoute(program, operation.operation)) {
if (pathShared) {
continue; continue;
} }
let map = grouped.get(path); let map = grouped.get(path);

Просмотреть файл

@ -7,8 +7,6 @@ import {
Program, Program,
Type, Type,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { createDiagnostic } from "../lib.js";
import { getAction, getCollectionAction, getResourceOperation } from "../rest.js";
import { getContentTypes, isContentTypeHeader } from "./content-types.js"; import { getContentTypes, isContentTypeHeader } from "./content-types.js";
import { import {
getHeaderFieldOptions, getHeaderFieldOptions,
@ -17,6 +15,7 @@ import {
getQueryParamOptions, getQueryParamOptions,
isBody, isBody,
} from "./decorators.js"; } from "./decorators.js";
import { createDiagnostic } from "./lib.js";
import { gatherMetadata, getRequestVisibility, isMetadata } from "./metadata.js"; import { gatherMetadata, getRequestVisibility, isMetadata } from "./metadata.js";
import { import {
HttpOperation, HttpOperation,
@ -24,26 +23,24 @@ import {
HttpOperationParameters, HttpOperationParameters,
HttpOperationRequestBody, HttpOperationRequestBody,
HttpVerb, HttpVerb,
OperationParameterOptions,
} from "./types.js"; } from "./types.js";
export function getOperationParameters( export function getOperationParameters(
program: Program, program: Program,
operation: Operation, operation: Operation,
overloadBase?: HttpOperation, overloadBase?: HttpOperation,
knownPathParamNames: string[] = [] knownPathParamNames: string[] = [],
options: OperationParameterOptions = {}
): [HttpOperationParameters, readonly Diagnostic[]] { ): [HttpOperationParameters, readonly Diagnostic[]] {
const verb = getExplicitVerbForOperation(program, operation); const verb =
(options?.verbSelector && options.verbSelector(program, operation)) ??
getOperationVerb(program, operation) ??
overloadBase?.verb;
if (verb) { if (verb) {
return getOperationParametersForVerb(program, operation, verb, knownPathParamNames); return getOperationParametersForVerb(program, operation, verb, knownPathParamNames);
} }
if (overloadBase) {
return getOperationParametersForVerb(
program,
operation,
overloadBase.verb,
knownPathParamNames
);
}
// If no verb is explicitly specified, it is POST if there is a body and // If no verb is explicitly specified, it is POST if there is a body and
// GET otherwise. Theoretically, it is possible to use @visibility // GET otherwise. Theoretically, it is possible to use @visibility
@ -214,25 +211,3 @@ function computeHttpOperationBody(
}; };
return [body, diagnostics]; return [body, diagnostics];
} }
function getExplicitVerbForOperation(program: Program, operation: Operation): HttpVerb | undefined {
const resourceOperation = getResourceOperation(program, operation);
const verb =
(resourceOperation && resourceOperationToVerb[resourceOperation.operation]) ??
getOperationVerb(program, operation) ??
// TODO: Enable this verb choice to be customized!
(getAction(program, operation) || getCollectionAction(program, operation) ? "post" : undefined);
return verb;
}
// TODO: Make this overridable by libraries
const resourceOperationToVerb: any = {
read: "get",
create: "post",
createOrUpdate: "patch",
createOrReplace: "put",
update: "patch",
delete: "delete",
list: "get",
};

Просмотреть файл

@ -14,7 +14,6 @@ import {
Type, Type,
walkPropertiesInherited, walkPropertiesInherited,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { createDiagnostic } from "../lib.js";
import { getContentTypes, isContentTypeHeader } from "./content-types.js"; import { getContentTypes, isContentTypeHeader } from "./content-types.js";
import { import {
getHeaderFieldName, getHeaderFieldName,
@ -24,6 +23,7 @@ import {
isHeader, isHeader,
isStatusCode, isStatusCode,
} from "./decorators.js"; } from "./decorators.js";
import { createDiagnostic } from "./lib.js";
import { gatherMetadata, isApplicableMetadata, Visibility } from "./metadata.js"; import { gatherMetadata, isApplicableMetadata, Visibility } from "./metadata.js";
import { HttpOperationResponse } from "./types.js"; import { HttpOperationResponse } from "./types.js";

258
packages/http/src/route.ts Normal file
Просмотреть файл

@ -0,0 +1,258 @@
import {
createDiagnosticCollector,
DecoratorContext,
DiagnosticResult,
Interface,
Namespace,
Operation,
Program,
Type,
validateDecoratorTarget,
} from "@typespec/compiler";
import { createDiagnostic, createStateSymbol, reportDiagnostic } from "./lib.js";
import { getOperationParameters } from "./parameters.js";
import {
HttpOperation,
HttpOperationParameters,
RouteOptions,
RoutePath,
RouteProducer,
RouteProducerResult,
RouteResolutionOptions,
} from "./types.js";
import { extractParamsFromPath } from "./utils.js";
// The set of allowed segment separator characters
const AllowedSegmentSeparators = ["/", ":"];
function normalizeFragment(fragment: string) {
if (fragment.length > 0 && AllowedSegmentSeparators.indexOf(fragment[0]) < 0) {
// Insert the default separator
fragment = `/${fragment}`;
}
// Trim any trailing slash
return fragment.replace(/\/$/g, "");
}
function buildPath(pathFragments: string[]) {
// Join all fragments with leading and trailing slashes trimmed
const path =
pathFragments.length === 0
? "/"
: pathFragments
.map(normalizeFragment)
.filter((x) => x !== "")
.join("");
// The final path must start with a '/'
return path.length > 0 && path[0] === "/" ? path : `/${path}`;
}
export function resolvePathAndParameters(
program: Program,
operation: Operation,
overloadBase: HttpOperation | undefined,
options: RouteResolutionOptions
): DiagnosticResult<{
path: string;
pathSegments: string[];
parameters: HttpOperationParameters;
}> {
const diagnostics = createDiagnosticCollector();
const { segments, parameters } = diagnostics.pipe(
getRouteSegments(program, operation, overloadBase, options)
);
// Pull out path parameters to verify what's in the path string
const paramByName = new Set(
parameters.parameters.filter(({ type }) => type === "path").map(({ param }) => param.name)
);
// Ensure that all of the parameters defined in the route are accounted for in
// the operation parameters
const routeParams = segments.flatMap(extractParamsFromPath);
for (const routeParam of routeParams) {
if (!paramByName.has(routeParam)) {
diagnostics.add(
createDiagnostic({
code: "missing-path-param",
format: { param: routeParam },
target: operation,
})
);
}
}
return diagnostics.wrap({
path: buildPath(segments),
pathSegments: segments,
parameters,
});
}
function collectSegmentsAndOptions(
program: Program,
source: Interface | Namespace | undefined
): [string[], RouteOptions] {
if (source === undefined) return [[], {}];
const [parentSegments, parentOptions] = collectSegmentsAndOptions(program, source.namespace);
const route = getRoutePath(program, source)?.path;
const options =
source.kind === "Namespace" ? getRouteOptionsForNamespace(program, source) ?? {} : {};
return [[...parentSegments, ...(route ? [route] : [])], { ...parentOptions, ...options }];
}
function getRouteSegments(
program: Program,
operation: Operation,
overloadBase: HttpOperation | undefined,
options: RouteResolutionOptions
): DiagnosticResult<RouteProducerResult> {
const diagnostics = createDiagnosticCollector();
const [parentSegments, parentOptions] = collectSegmentsAndOptions(
program,
operation.interface ?? operation.namespace
);
const routeProducer = getRouteProducer(program, operation) ?? DefaultRouteProducer;
const result = diagnostics.pipe(
routeProducer(program, operation, parentSegments, overloadBase, {
...parentOptions,
...options,
})
);
return diagnostics.wrap(result);
}
const externalInterfaces = createStateSymbol("externalInterfaces");
/**
* @deprecated DO NOT USE. For internal use only as a workaround.
* @param program Program
* @param target Target namespace
* @param sourceInterface Interface that should be included in namespace.
*/
export function includeInterfaceRoutesInNamespace(
program: Program,
target: Namespace,
sourceInterface: string
) {
let array = program.stateMap(externalInterfaces).get(target);
if (array === undefined) {
array = [];
program.stateMap(externalInterfaces).set(target, array);
}
array.push(sourceInterface);
}
const routeProducerKey = createStateSymbol("routeProducer");
export function DefaultRouteProducer(
program: Program,
operation: Operation,
parentSegments: string[],
overloadBase: HttpOperation | undefined,
options: RouteOptions
): DiagnosticResult<RouteProducerResult> {
const diagnostics = createDiagnosticCollector();
const routePath = getRoutePath(program, operation)?.path;
const segments =
!routePath && overloadBase
? overloadBase.pathSegments
: [...parentSegments, ...(routePath ? [routePath] : [])];
const routeParams = segments.flatMap(extractParamsFromPath);
const parameters: HttpOperationParameters = diagnostics.pipe(
getOperationParameters(program, operation, overloadBase, routeParams, options.paramOptions)
);
// Pull out path parameters to verify what's in the path string
const unreferencedPathParamNames = new Set(
parameters.parameters.filter(({ type }) => type === "path").map(({ param }) => param.name)
);
// Compile the list of all route params that aren't represented in the route
for (const routeParam of routeParams) {
unreferencedPathParamNames.delete(routeParam);
}
// Add any remaining declared path params
for (const paramName of unreferencedPathParamNames) {
segments.push(`{${paramName}}`);
}
return diagnostics.wrap({
segments,
parameters,
});
}
export function setRouteProducer(
program: Program,
operation: Operation,
routeProducer: RouteProducer
): void {
program.stateMap(routeProducerKey).set(operation, routeProducer);
}
export function getRouteProducer(program: Program, operation: Operation): RouteProducer {
return program.stateMap(routeProducerKey).get(operation);
}
const routesKey = createStateSymbol("routes");
export function setRoute(context: DecoratorContext, entity: Type, details: RoutePath) {
if (
!validateDecoratorTarget(context, entity, "@route", ["Namespace", "Interface", "Operation"])
) {
return;
}
const state = context.program.stateMap(routesKey);
if (state.has(entity) && entity.kind === "Namespace") {
const existingValue: RoutePath = state.get(entity);
if (existingValue.path !== details.path) {
reportDiagnostic(context.program, {
code: "duplicate-route-decorator",
messageId: "namespace",
target: entity,
});
}
} else {
state.set(entity, details);
}
}
export function isSharedRoute(program: Program, operation: Operation): boolean {
return program.stateMap(routesKey).get(operation)?.shared;
}
export function getRoutePath(
program: Program,
entity: Namespace | Interface | Operation
): RoutePath | undefined {
return program.stateMap(routesKey).get(entity);
}
const routeOptionsKey = createStateSymbol("routeOptions");
export function setRouteOptionsForNamespace(
program: Program,
namespace: Namespace,
options: RouteOptions
) {
program.stateMap(routeOptionsKey).set(namespace, options);
}
export function getRouteOptionsForNamespace(
program: Program,
namespace: Namespace
): RouteOptions | undefined {
return program.stateMap(routeOptionsKey).get(namespace);
}

Просмотреть файл

@ -0,0 +1,8 @@
import { resolvePath } from "@typespec/compiler";
import { createTestLibrary, TypeSpecTestLibrary } from "@typespec/compiler/testing";
import { fileURLToPath } from "url";
export const HttpTestLibrary: TypeSpecTestLibrary = createTestLibrary({
name: "@typespec/http",
packageRoot: resolvePath(fileURLToPath(import.meta.url), "../../../../"),
});

Просмотреть файл

@ -1,9 +1,11 @@
import { import {
DiagnosticResult,
Interface, Interface,
ListOperationOptions, ListOperationOptions,
ModelProperty, ModelProperty,
Namespace, Namespace,
Operation, Operation,
Program,
Type, Type,
} from "@typespec/compiler"; } from "@typespec/compiler";
@ -168,23 +170,39 @@ export interface OAuth2Scope {
export type OperationContainer = Namespace | Interface; export type OperationContainer = Namespace | Interface;
export interface FilteredRouteParam { export type OperationVerbSelector = (
routeParamString?: string; program: Program,
excludeFromOperationParams?: boolean; operation: Operation
} ) => HttpVerb | undefined;
export interface AutoRouteOptions { export interface OperationParameterOptions {
routeParamFilter?: (op: Operation, param: ModelProperty) => FilteredRouteParam | undefined; verbSelector?: OperationVerbSelector;
} }
export interface RouteOptions { export interface RouteOptions {
autoRouteOptions?: AutoRouteOptions; // Other options can be passed through the interface
[prop: string]: any;
paramOptions?: OperationParameterOptions;
} }
export interface RouteResolutionOptions extends RouteOptions { export interface RouteResolutionOptions extends RouteOptions {
listOptions?: ListOperationOptions; listOptions?: ListOperationOptions;
} }
export interface RouteProducerResult {
segments: string[];
parameters: HttpOperationParameters;
}
export type RouteProducer = (
program: Program,
operation: Operation,
parentSegments: string[],
overloadBase: HttpOperation | undefined,
options: RouteOptions
) => DiagnosticResult<RouteProducerResult>;
export interface HeaderFieldOptions { export interface HeaderFieldOptions {
type: "header"; type: "header";
name: string; name: string;
@ -296,7 +314,6 @@ export interface HttpOperation {
export interface RoutePath { export interface RoutePath {
path: string; path: string;
isReset: boolean;
shared: boolean; shared: boolean;
} }

Просмотреть файл

Просмотреть файл

@ -0,0 +1,10 @@
import { Program } from "@typespec/compiler";
import { getAllHttpServices } from "./operations.js";
export function $onValidate(program: Program) {
// Pass along any diagnostics that might be returned from the HTTP library
const [, diagnostics] = getAllHttpServices(program);
if (diagnostics.length > 0) {
program.reportDiagnostics(diagnostics);
}
}

Просмотреть файл

@ -20,10 +20,10 @@ import {
isPathParam, isPathParam,
isQueryParam, isQueryParam,
isStatusCode, isStatusCode,
} from "../src/http/decorators.js"; } from "../src/decorators.js";
import { createHttpTestRunner } from "./test-host.js"; import { createHttpTestRunner } from "./test-host.js";
describe("rest: http decorators", () => { describe("http: decorators", () => {
let runner: BasicTestRunner; let runner: BasicTestRunner;
beforeEach(async () => { beforeEach(async () => {
@ -233,11 +233,11 @@ describe("rest: http decorators", () => {
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ {
code: "@typespec/rest/duplicate-operation", code: "@typespec/http/duplicate-operation",
message: `Duplicate operation "test" routed at "get /test".`, message: `Duplicate operation "test" routed at "get /test".`,
}, },
{ {
code: "@typespec/rest/duplicate-operation", code: "@typespec/http/duplicate-operation",
message: `Duplicate operation "test2" routed at "get /test".`, message: `Duplicate operation "test2" routed at "get /test".`,
}, },
]); ]);
@ -258,7 +258,7 @@ describe("rest: http decorators", () => {
`); `);
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ {
code: "@typespec/rest/shared-boolean", code: "@typespec/http/shared-boolean",
message: `shared parameter must be a boolean.`, message: `shared parameter must be a boolean.`,
}, },
]); ]);
@ -293,7 +293,7 @@ describe("rest: http decorators", () => {
`); `);
expectDiagnostics(diagnostics, { expectDiagnostics(diagnostics, {
code: "@typespec/rest/optional-path-param", code: "@typespec/http/optional-path-param",
message: "Path parameter 'myPath' cannot be optional.", message: "Path parameter 'myPath' cannot be optional.",
}); });
}); });
@ -482,7 +482,7 @@ describe("rest: http decorators", () => {
`); `);
expectDiagnostics(diagnostics, { expectDiagnostics(diagnostics, {
code: "@typespec/rest/missing-server-param", code: "@typespec/http/missing-server-param",
message: "Server url contains parameter 'name' but wasn't found in given parameters", message: "Server url contains parameter 'name' but wasn't found in given parameters",
}); });
}); });
@ -703,7 +703,7 @@ describe("rest: http decorators", () => {
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ {
severity: "warning", severity: "warning",
code: "@typespec/rest/write-visibility-not-supported", code: "@typespec/http/write-visibility-not-supported",
}, },
]); ]);
}); });

Просмотреть файл

@ -1,10 +1,10 @@
import { Operation } from "@typespec/compiler"; import { Operation } from "@typespec/compiler";
import { BasicTestRunner, expectDiagnostics } from "@typespec/compiler/testing"; import { BasicTestRunner, expectDiagnostics } from "@typespec/compiler/testing";
import { strictEqual } from "assert"; import { strictEqual } from "assert";
import { getHttpOperation, listHttpOperationsIn } from "../src/http/index.js"; import { getHttpOperation, listHttpOperationsIn } from "../src/index.js";
import { createHttpTestRunner } from "./test-host.js"; import { createHttpTestRunner } from "./test-host.js";
describe("rest: overloads", () => { describe("http: overloads", () => {
let runner: BasicTestRunner; let runner: BasicTestRunner;
beforeEach(async () => { beforeEach(async () => {
@ -40,8 +40,7 @@ describe("rest: overloads", () => {
@route("/uploadString") @route("/uploadString")
@test op uploadString(data: string, @header contentType: "text/plain" ): void; @test op uploadString(data: string, @header contentType: "text/plain" ): void;
@overload(upload) @overload(upload)
@post @post @test op uploadBytes(data: bytes, @header contentType: "application/octet-stream"): void;
@test op uploadBytes(data: bytes, @header contentType: "application/octet-stream"): void;
`)) as { upload: Operation; uploadString: Operation; uploadBytes: Operation }; `)) as { upload: Operation; uploadString: Operation; uploadBytes: Operation };
const [uploadHttp] = getHttpOperation(runner.program, upload); const [uploadHttp] = getHttpOperation(runner.program, upload);
@ -96,11 +95,11 @@ describe("rest: overloads", () => {
`); `);
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ {
code: "@typespec/rest/duplicate-operation", code: "@typespec/http/duplicate-operation",
message: `Duplicate operation "otherUpload" routed at "post /upload".`, message: `Duplicate operation "otherUpload" routed at "post /upload".`,
}, },
{ {
code: "@typespec/rest/duplicate-operation", code: "@typespec/http/duplicate-operation",
message: `Duplicate operation "upload" routed at "post /upload".`, message: `Duplicate operation "upload" routed at "post /upload".`,
}, },
]); ]);
@ -121,11 +120,11 @@ describe("rest: overloads", () => {
`); `);
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ {
code: "@typespec/rest/duplicate-operation", code: "@typespec/http/duplicate-operation",
message: `Duplicate operation "otherUploadString" routed at "post /uploadString".`, message: `Duplicate operation "otherUploadString" routed at "post /uploadString".`,
}, },
{ {
code: "@typespec/rest/duplicate-operation", code: "@typespec/http/duplicate-operation",
message: `Duplicate operation "uploadString" routed at "post /uploadString".`, message: `Duplicate operation "uploadString" routed at "post /uploadString".`,
}, },
]); ]);

Просмотреть файл

@ -1,20 +1,20 @@
import { TestHost } from "@typespec/compiler/testing"; import { TestHost } from "@typespec/compiler/testing";
import { ok, strictEqual } from "assert"; import { ok, strictEqual } from "assert";
import { isBody, isHeader, isPathParam, isQueryParam } from "../src/http/decorators.js"; import { isBody, isHeader, isPathParam, isQueryParam } from "../src/decorators.js";
import { createRestTestHost } from "./test-host.js"; import { createHttpTestHost } from "./test-host.js";
describe("rest: plain data", () => { describe("http: plain data", () => {
let testHost: TestHost; let testHost: TestHost;
beforeEach(async () => { beforeEach(async () => {
testHost = await createRestTestHost(); testHost = await createHttpTestHost();
}); });
it("removes header/query/body/path", async () => { it("removes header/query/body/path", async () => {
testHost.addTypeSpecFile( testHost.addTypeSpecFile(
"main.tsp", "main.tsp",
` `
import "@typespec/rest"; import "@typespec/http";
using TypeSpec.Http; using TypeSpec.Http;
@test @test

Просмотреть файл

@ -3,7 +3,7 @@ import { expectDiagnosticEmpty, expectDiagnostics } from "@typespec/compiler/tes
import { deepStrictEqual, ok, strictEqual } from "assert"; import { deepStrictEqual, ok, strictEqual } from "assert";
import { compileOperations, getOperationsWithServiceNamespace } from "./test-host.js"; import { compileOperations, getOperationsWithServiceNamespace } from "./test-host.js";
describe("rest: responses", () => { describe("http: responses", () => {
it("issues diagnostics for duplicate body decorator", async () => { it("issues diagnostics for duplicate body decorator", async () => {
const [_, diagnostics] = await compileOperations( const [_, diagnostics] = await compileOperations(
` `
@ -20,7 +20,7 @@ describe("rest: responses", () => {
} }
` `
); );
expectDiagnostics(diagnostics, [{ code: "@typespec/rest/duplicate-body" }]); expectDiagnostics(diagnostics, [{ code: "@typespec/http/duplicate-body" }]);
}); });
it("issues diagnostics for return type with duplicate status code", async () => { it("issues diagnostics for return type with duplicate status code", async () => {
@ -40,7 +40,7 @@ describe("rest: responses", () => {
` `
); );
expectDiagnostics(diagnostics, { expectDiagnostics(diagnostics, {
code: "@typespec/rest/duplicate-response", code: "@typespec/http/duplicate-response",
message: "Multiple return types for content type application/json and status code 200", message: "Multiple return types for content type application/json and status code 200",
}); });
}); });
@ -70,9 +70,9 @@ describe("rest: responses", () => {
` `
); );
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ code: "@typespec/rest/content-type-string" }, { code: "@typespec/http/content-type-string" },
{ code: "@typespec/rest/content-type-string" }, { code: "@typespec/http/content-type-string" },
{ code: "@typespec/rest/content-type-string" }, { code: "@typespec/http/content-type-string" },
]); ]);
}); });

Просмотреть файл

@ -0,0 +1,548 @@
import { expectDiagnosticEmpty, expectDiagnostics } from "@typespec/compiler/testing";
import { deepStrictEqual, strictEqual } from "assert";
import { HttpOperation } from "../src/index.js";
import { compileOperations, getOperations, getRoutesFor } from "./test-host.js";
describe("http: routes", () => {
// Describe how routes should be included.
describe("route inclusion", () => {
function expectRouteIncluded(routes: HttpOperation[], expectedRoutePaths: string[]) {
const includedRoutes = routes.map((x) => x.path);
deepStrictEqual(includedRoutes, expectedRoutePaths);
}
describe("when there is NO service namespace", () => {
it("operations at the document root are included", async () => {
const routes = await getOperations(`
@route("/one")
@get op one(): string;
@route("/two")
@get op two(): string;
`);
expectRouteIncluded(routes, ["/one", "/two"]);
});
it("interface at the document root are included", async () => {
const routes = await getOperations(`
interface Foo {
@get index(): void;
}
`);
expectRouteIncluded(routes, ["/"]);
});
it("generic operation at the document root are NOT included", async () => {
const routes = await getOperations(`
@route("/not-included")
@get op index<T>(): T;
`);
expectRouteIncluded(routes, []);
});
it("generic interface at the document root are NOT included", async () => {
const routes = await getOperations(`
interface Foo<T> {
@route("/not-included")
@get index(): T;
}
`);
expectRouteIncluded(routes, []);
});
it("routes inside a namespace not marked as the service namespace aren't be included", async () => {
const routes = await getOperations(
`
namespace Foo {
@get op index(): void;
}
`
);
deepStrictEqual(routes, []);
});
});
describe("when there is a service namespace", () => {
it("operation in the service namespace are included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService;
@get op index(): void;
`
);
expectRouteIncluded(routes, ["/"]);
});
it("operation at the root of the document are NOT included", async () => {
const routes = await getOperations(
`
@route("/not-included")
@get op notIncluded(): void;
@service({title: "My Service"})
namespace MyService {
@route("/included")
@get op included(): void;
}
`
);
expectRouteIncluded(routes, ["/included"]);
});
it("interface in the service namespace are included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService;
interface Foo {
@get index(): void;
}`
);
expectRouteIncluded(routes, ["/"]);
});
it("operation in namespace in the service namespace are be included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService;
namespace MyArea{
@get op index(): void;
}
`
);
expectRouteIncluded(routes, ["/"]);
});
it("operation in a different namespace are not included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService {
@route("/included")
@get op test(): string;
}
namespace MyLib {
@route("/not-included")
@get op notIncluded(): void;
}
`
);
expectRouteIncluded(routes, ["/included"]);
});
});
});
it("combines routes on namespaced bare operations", async () => {
const routes = await getRoutesFor(
`
@route("/things")
namespace Things {
@get op GetThing(): string;
@route("/{thingId}")
@put op CreateThing(@path thingId: string): string;
@route("/{thingId}/subthings")
namespace Subthing {
@get op GetSubthing(@path thingId: string): string;
@route("/{subthingId}")
@post op CreateSubthing(@path thingId: string, @path subthingId: string): string;
}
}
`
);
deepStrictEqual(routes, [
{ verb: "get", path: "/things", params: [] },
{ verb: "put", path: "/things/{thingId}", params: ["thingId"] },
{ verb: "get", path: "/things/{thingId}/subthings", params: ["thingId"] },
{
verb: "post",
path: "/things/{thingId}/subthings/{subthingId}",
params: ["thingId", "subthingId"],
},
]);
});
it("combines routes between namespaces and interfaces", async () => {
const routes = await getRoutesFor(
`
@route("/things")
namespace Things {
@get op GetThing(): string;
@route("/{thingId}")
@put op CreateThing(@path thingId: string): string;
@route("/{thingId}/subthings")
interface Subthing {
@get GetSubthing(@path thingId: string): string;
@route("/{subthingId}")
@post CreateSubthing(@path thingId: string, @path subthingId: string): string;
}
}
`
);
deepStrictEqual(routes, [
{ verb: "get", path: "/things", params: [] },
{ verb: "put", path: "/things/{thingId}", params: ["thingId"] },
{ verb: "get", path: "/things/{thingId}/subthings", params: ["thingId"] },
{
verb: "post",
path: "/things/{thingId}/subthings/{subthingId}",
params: ["thingId", "subthingId"],
},
]);
});
it("join empty route segments correctly", async () => {
const routes = await getRoutesFor(
`
@route("")
interface Foo {
@get @route("") index(): {};
}
`
);
deepStrictEqual(routes, [{ verb: "get", path: "/", params: [] }]);
});
it("join / route segments correctly", async () => {
const routes = await getRoutesFor(
`
@route("/")
interface Foo {
@get @route("/") index(): {};
}
`
);
deepStrictEqual(routes, [{ verb: "get", path: "/", params: [] }]);
});
it("always produces a route starting with /", async () => {
const routes = await getRoutesFor(
`
@get
@route(":action")
op colonRoute(): {};
`
);
deepStrictEqual(routes, [{ verb: "get", path: "/:action", params: [] }]);
});
it("defaults to POST when operation has a body but didn't specify the verb", async () => {
const routes = await getRoutesFor(`
@route("/test")
op get(@body body: string): string;
`);
deepStrictEqual(routes, [
{
verb: "post",
path: "/test",
params: [],
},
]);
});
it("emit diagnostics if 2 operation have the same path and verb", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
op get1(): string;
@route("/test")
op get2(): string;
`);
// Has one diagnostic per duplicate operation
strictEqual(diagnostics.length, 2);
strictEqual(diagnostics[0].code, "@typespec/http/duplicate-operation");
strictEqual(diagnostics[0].message, `Duplicate operation "get1" routed at "get /test".`);
strictEqual(diagnostics[1].code, "@typespec/http/duplicate-operation");
strictEqual(diagnostics[1].message, `Duplicate operation "get2" routed at "get /test".`);
});
describe("operation parameters", () => {
it("emit diagnostic for parameters with multiple http request annotations", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@body body: string, @path @query multiParam: string): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/http/operation-param-duplicate-type",
message: "Param multiParam has multiple types: [query, path]",
});
});
it("emit diagnostic when there is an unannotated parameter and a @body param", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(param1: string, @body param2: string): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/http/duplicate-body",
message:
"Operation has a @body and an unannotated parameter. There can only be one representing the body",
});
});
it("emit diagnostic when there are multiple @body param", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@query select: string, @body param1: string, @body param2: string): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/http/duplicate-body",
message: "Operation has multiple @body parameters declared",
});
});
it("emit error if using multipart/form-data contentType parameter with a body not being a model", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@header contentType: "multipart/form-data", @body body: string | int32): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/http/multipart-model",
message: "Multipart request body must be a model.",
});
});
it("emit warning if using contentType parameter without a body", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@header contentType: "image/png"): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/http/content-type-ignored",
message: "`Content-Type` header ignored because there is no body.",
});
});
it("resolve body when defined with @body", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@query select: string, @body bodyParam: string): string;
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test",
params: { params: [{ type: "query", name: "select" }], body: "bodyParam" },
},
]);
});
it("resolves single unannotated parameter as request body", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@query select: string, unannotatedBodyParam: string): string;
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test",
params: {
params: [{ type: "query", name: "select" }],
body: ["unannotatedBodyParam"],
},
},
]);
});
it("resolves multiple unannotated parameters as request body", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test")
@get op get(
@query select: string,
unannotatedBodyParam1: string,
unannotatedBodyParam2: string): string;
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test",
params: {
params: [{ type: "query", name: "select" }],
body: ["unannotatedBodyParam1", "unannotatedBodyParam2"],
},
},
]);
});
it("resolves unannotated path parameters that are included in the route path", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test/{name}/sub/{foo}")
@get op get(
name: string,
foo: string
): string;
@route("/nested/{name}")
namespace A {
@route("sub")
namespace B {
@route("{bar}")
@get op get(
name: string,
bar: string
): string;
}
}
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test/{name}/sub/{foo}",
params: {
params: [
{ type: "path", name: "name" },
{ type: "path", name: "foo" },
],
body: undefined,
},
},
{
verb: "get",
path: "/nested/{name}/sub/{bar}",
params: {
params: [
{ type: "path", name: "name" },
{ type: "path", name: "bar" },
],
body: undefined,
},
},
]);
});
});
describe("double @route", () => {
it("emit diagnostic if specifying route twice on operation", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@route("/test")
op get(): string;
`);
expectDiagnostics(diagnostics, [
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
]);
});
it("emit diagnostic if specifying route twice on interface", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@route("/test")
interface Foo {
get(): string
}
`);
expectDiagnostics(diagnostics, [
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
]);
});
it("emit diagnostic if namespace have route but different values", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test1")
namespace Foo {
@route("/get1")
op get1(): string;
}
@route("/test2")
namespace Foo {
@route("/get2")
op get2(): string;
}
`);
expectDiagnostics(diagnostics, {
code: "@typespec/http/duplicate-route-decorator",
message: "@route was defined twice on this namespace and has different values.",
});
});
it("merge namespace if @route value is the same", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
namespace Foo {
@route("/get1")
op get1(): string;
}
@route("/test")
namespace Foo {
@route("/get2")
op get2(): string;
}
`);
expectDiagnosticEmpty(diagnostics);
});
});
it("skips templated operations", async () => {
const routes = await getRoutesFor(
`
@route("/things")
namespace Things {
@get op GetThingBase<TResponse>(): TResponse;
op GetThing is GetThingBase<string>;
@route("/{thingId}")
@put op CreateThing(@path thingId: string): string;
}
`
);
deepStrictEqual(routes, [
{ verb: "get", path: "/things", params: [] },
{ verb: "put", path: "/things/{thingId}", params: ["thingId"] },
]);
});
});

Просмотреть файл

@ -0,0 +1,107 @@
import { Diagnostic } from "@typespec/compiler";
import {
BasicTestRunner,
createTestHost,
createTestWrapper,
expectDiagnosticEmpty,
TestHost,
} from "@typespec/compiler/testing";
import {
getAllHttpServices,
HttpOperation,
HttpOperationParameter,
HttpVerb,
RouteResolutionOptions,
} from "../src/index.js";
import { HttpTestLibrary } from "../src/testing/index.js";
export async function createHttpTestHost(): Promise<TestHost> {
return createTestHost({
libraries: [HttpTestLibrary],
});
}
export async function createHttpTestRunner(): Promise<BasicTestRunner> {
const host = await createHttpTestHost();
return createTestWrapper(host, { autoUsings: ["TypeSpec.Http"] });
}
export interface RouteDetails {
path: string;
verb: HttpVerb;
params: string[];
}
export async function getRoutesFor(
code: string,
routeOptions?: RouteResolutionOptions
): Promise<RouteDetails[]> {
const [routes, diagnostics] = await compileOperations(code, routeOptions);
expectDiagnosticEmpty(diagnostics);
return routes.map((route) => ({
...route,
params: route.params.params
.map(({ type, name }) => (type === "path" ? name : undefined))
.filter((p) => p !== undefined) as string[],
}));
}
export interface SimpleOperationDetails {
verb: HttpVerb;
path: string;
params: {
params: Array<{ name: string; type: HttpOperationParameter["type"] }>;
/**
* name of explicit `@body` parameter or array of unannotated parameter names that make up the body.
*/
body?: string | string[];
};
}
export async function compileOperations(
code: string,
routeOptions?: RouteResolutionOptions
): Promise<[SimpleOperationDetails[], readonly Diagnostic[]]> {
const [routes, diagnostics] = await getOperationsWithServiceNamespace(code, routeOptions);
const details = routes.map((r) => {
return {
verb: r.verb,
path: r.path,
params: {
params: r.parameters.parameters.map(({ type, name }) => ({ type, name })),
body:
r.parameters.bodyParameter?.name ??
(r.parameters.bodyType?.kind === "Model"
? Array.from(r.parameters.bodyType.properties.keys())
: undefined),
},
};
});
return [details, diagnostics];
}
export async function getOperationsWithServiceNamespace(
code: string,
routeOptions?: RouteResolutionOptions
): Promise<[HttpOperation[], readonly Diagnostic[]]> {
const runner = await createHttpTestRunner();
await runner.compileAndDiagnose(
`@service({title: "Test Service"}) namespace TestService;
${code}`,
{
noEmit: true,
}
);
const [services] = getAllHttpServices(runner.program, routeOptions);
return [services[0].operations, runner.program.diagnostics];
}
export async function getOperations(code: string): Promise<HttpOperation[]> {
const runner = await createHttpTestRunner();
await runner.compile(code);
const [services, diagnostics] = getAllHttpServices(runner.program);
expectDiagnosticEmpty(diagnostics);
return services[0].operations;
}

Просмотреть файл

@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"references": [{ "path": "../compiler/tsconfig.json" }],
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"tsBuildInfoFile": "temp/tsconfig.tsbuildinfo",
"types": ["node", "mocha"]
},
"include": ["src/**/*.ts", "test/**/*.ts"]
}

Просмотреть файл

@ -53,12 +53,14 @@
], ],
"peerDependencies": { "peerDependencies": {
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0" "@typespec/rest": "~0.40.0"
}, },
"devDependencies": { "devDependencies": {
"@types/mocha": "~10.0.0", "@types/mocha": "~10.0.0",
"@types/node": "~18.11.9", "@types/node": "~18.11.9",
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0", "@typespec/rest": "~0.40.0",
"@typespec/eslint-config-typespec": "~0.5.0", "@typespec/eslint-config-typespec": "~0.5.0",
"@typespec/library-linter": "~0.40.0", "@typespec/library-linter": "~0.40.0",

Просмотреть файл

@ -7,7 +7,7 @@ import {
typespecTypeToJson, typespecTypeToJson,
TypeSpecValue, TypeSpecValue,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { http } from "@typespec/rest"; import { setStatusCode } from "@typespec/http";
import { createStateSymbol, reportDiagnostic } from "./lib.js"; import { createStateSymbol, reportDiagnostic } from "./lib.js";
import { ExtensionKey } from "./types.js"; import { ExtensionKey } from "./types.js";
@ -82,7 +82,7 @@ function isOpenAPIExtensionKey(key: string): key is ExtensionKey {
*/ */
const defaultResponseKey = createStateSymbol("defaultResponse"); const defaultResponseKey = createStateSymbol("defaultResponse");
export function $defaultResponse(context: DecoratorContext, entity: Model) { export function $defaultResponse(context: DecoratorContext, entity: Model) {
http.setStatusCode(context.program, entity, ["*"]); setStatusCode(context.program, entity, ["*"]);
context.program.stateSet(defaultResponseKey).add(entity); context.program.stateSet(defaultResponseKey).add(entity);
} }

Просмотреть файл

@ -1,10 +1,11 @@
import { createTestHost, createTestWrapper } from "@typespec/compiler/testing"; import { createTestHost, createTestWrapper } from "@typespec/compiler/testing";
import { HttpTestLibrary } from "@typespec/http/testing";
import { RestTestLibrary } from "@typespec/rest/testing"; import { RestTestLibrary } from "@typespec/rest/testing";
import { OpenAPITestLibrary } from "../src/testing/index.js"; import { OpenAPITestLibrary } from "../src/testing/index.js";
export async function createOpenAPITestHost() { export async function createOpenAPITestHost() {
return createTestHost({ return createTestHost({
libraries: [RestTestLibrary, OpenAPITestLibrary], libraries: [HttpTestLibrary, RestTestLibrary, OpenAPITestLibrary],
}); });
} }
export async function createOpenAPITestRunner() { export async function createOpenAPITestRunner() {

Просмотреть файл

@ -57,6 +57,7 @@
"peerDependencies": { "peerDependencies": {
"@typespec/versioning": "~0.40.0", "@typespec/versioning": "~0.40.0",
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0", "@typespec/rest": "~0.40.0",
"@typespec/openapi": "~0.40.0" "@typespec/openapi": "~0.40.0"
}, },
@ -65,6 +66,7 @@
"@types/node": "~18.11.9", "@types/node": "~18.11.9",
"@types/js-yaml": "~4.0.1", "@types/js-yaml": "~4.0.1",
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0", "@typespec/rest": "~0.40.0",
"@typespec/openapi": "~0.40.0", "@typespec/openapi": "~0.40.0",
"@typespec/versioning": "~0.40.0", "@typespec/versioning": "~0.40.0",

Просмотреть файл

@ -61,17 +61,7 @@ import {
UnionVariant, UnionVariant,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { import * as http from "@typespec/http";
checkDuplicateTypeName,
getExtensions,
getExternalDocs,
getOpenAPITypeName,
getParameterKey,
isReadonlyProperty,
resolveOperationId,
shouldInline,
} from "@typespec/openapi";
import { http } from "@typespec/rest";
import { import {
createMetadataInfo, createMetadataInfo,
getAuthentication, getAuthentication,
@ -90,7 +80,17 @@ import {
reportIfNoRoutes, reportIfNoRoutes,
ServiceAuthentication, ServiceAuthentication,
Visibility, Visibility,
} from "@typespec/rest/http"; } from "@typespec/http";
import {
checkDuplicateTypeName,
getExtensions,
getExternalDocs,
getOpenAPITypeName,
getParameterKey,
isReadonlyProperty,
resolveOperationId,
shouldInline,
} from "@typespec/openapi";
import { buildVersionProjections } from "@typespec/versioning"; import { buildVersionProjections } from "@typespec/versioning";
import yaml from "js-yaml"; import yaml from "js-yaml";
import { getOneOf, getRef } from "./decorators.js"; import { getOneOf, getRef } from "./decorators.js";

Просмотреть файл

@ -20,7 +20,7 @@ describe("openapi3: types included", () => {
options: { "@typespec/openapi3": { ...options, "output-file": outPath } }, options: { "@typespec/openapi3": { ...options, "output-file": outPath } },
}); });
expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@typespec/rest/no-routes")); expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@typespec/http/no-routes"));
const content = runner.fs.get(outPath)!; const content = runner.fs.get(outPath)!;
return JSON.parse(content); return JSON.parse(content);

Просмотреть файл

@ -46,7 +46,7 @@ describe("openapi3: output file", () => {
options: { "@typespec/openapi3": { ...options, "emitter-output-dir": outputDir } }, options: { "@typespec/openapi3": { ...options, "emitter-output-dir": outputDir } },
}); });
expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@typespec/rest/no-routes")); expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@typespec/http/no-routes"));
} }
function expectOutput( function expectOutput(

Просмотреть файл

@ -268,9 +268,9 @@ describe("openapi3: return types", () => {
` `
); );
expectDiagnostics(diagnostics, [ expectDiagnostics(diagnostics, [
{ code: "@typespec/rest/status-code-invalid" }, { code: "@typespec/http/status-code-invalid" },
{ code: "@typespec/rest/status-code-invalid" }, { code: "@typespec/http/status-code-invalid" },
{ code: "@typespec/rest/status-code-invalid" }, { code: "@typespec/http/status-code-invalid" },
]); ]);
ok(diagnostics[0].message.includes("must be a numeric or string literal")); ok(diagnostics[0].message.includes("must be a numeric or string literal"));
ok(diagnostics[1].message.includes("must be a three digit code between 100 and 599")); ok(diagnostics[1].message.includes("must be a three digit code between 100 and 599"));

Просмотреть файл

@ -4,6 +4,7 @@ import {
expectDiagnosticEmpty, expectDiagnosticEmpty,
resolveVirtualPath, resolveVirtualPath,
} from "@typespec/compiler/testing"; } from "@typespec/compiler/testing";
import { HttpTestLibrary } from "@typespec/http/testing";
import { OpenAPITestLibrary } from "@typespec/openapi/testing"; import { OpenAPITestLibrary } from "@typespec/openapi/testing";
import { RestTestLibrary } from "@typespec/rest/testing"; import { RestTestLibrary } from "@typespec/rest/testing";
import { VersioningTestLibrary } from "@typespec/versioning/testing"; import { VersioningTestLibrary } from "@typespec/versioning/testing";
@ -12,7 +13,13 @@ import { OpenAPI3TestLibrary } from "../src/testing/index.js";
export async function createOpenAPITestHost() { export async function createOpenAPITestHost() {
return createTestHost({ return createTestHost({
libraries: [RestTestLibrary, VersioningTestLibrary, OpenAPITestLibrary, OpenAPI3TestLibrary], libraries: [
HttpTestLibrary,
RestTestLibrary,
VersioningTestLibrary,
OpenAPITestLibrary,
OpenAPI3TestLibrary,
],
}); });
} }
@ -21,7 +28,9 @@ export async function createOpenAPITestRunner({
}: { withVersioning?: boolean } = {}) { }: { withVersioning?: boolean } = {}) {
const host = await createOpenAPITestHost(); const host = await createOpenAPITestHost();
const importAndUsings = ` const importAndUsings = `
import "@typespec/rest"; import "@typespec/openapi"; import "@typespec/http";
import "@typespec/rest";
import "@typespec/openapi";
import "@typespec/openapi3"; import "@typespec/openapi3";
${withVersioning ? `import "@typespec/versioning"` : ""}; ${withVersioning ? `import "@typespec/versioning"` : ""};
using TypeSpec.Rest; using TypeSpec.Rest;
@ -47,7 +56,7 @@ export async function diagnoseOpenApiFor(code: string, options: OpenAPI3EmitterO
emit: ["@typespec/openapi3"], emit: ["@typespec/openapi3"],
options: { "@typespec/openapi3": options as any }, options: { "@typespec/openapi3": options as any },
}); });
return diagnostics.filter((x) => x.code !== "@typespec/rest/no-routes"); return diagnostics.filter((x) => x.code !== "@typespec/http/no-routes");
} }
export async function openApiFor( export async function openApiFor(
@ -59,7 +68,7 @@ export async function openApiFor(
const outPath = resolveVirtualPath("openapi.json"); const outPath = resolveVirtualPath("openapi.json");
host.addTypeSpecFile( host.addTypeSpecFile(
"./main.tsp", "./main.tsp",
`import "@typespec/rest"; import "@typespec/openapi"; import "@typespec/openapi3"; ${ `import "@typespec/http"; import "@typespec/rest"; import "@typespec/openapi"; import "@typespec/openapi3"; ${
versions ? `import "@typespec/versioning"; using TypeSpec.Versioning;` : "" versions ? `import "@typespec/versioning"; using TypeSpec.Versioning;` : ""
}using TypeSpec.Rest;using TypeSpec.Http;using OpenAPI;${code}` }using TypeSpec.Rest;using TypeSpec.Http;using OpenAPI;${code}`
); );
@ -68,7 +77,7 @@ export async function openApiFor(
emit: ["@typespec/openapi3"], emit: ["@typespec/openapi3"],
options: { "@typespec/openapi3": { ...options, "output-file": outPath } }, options: { "@typespec/openapi3": { ...options, "output-file": outPath } },
}); });
expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@typespec/rest/no-routes")); expectDiagnosticEmpty(diagnostics.filter((x) => x.code !== "@typespec/http/no-routes"));
if (!versions) { if (!versions) {
return JSON.parse(host.fs.get(outPath)!); return JSON.parse(host.fs.get(outPath)!);

Просмотреть файл

@ -43,6 +43,7 @@
"dependencies": { "dependencies": {
"@typespec/versioning": "~0.40.0", "@typespec/versioning": "~0.40.0",
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0", "@typespec/rest": "~0.40.0",
"@typespec/openapi3": "~0.40.0", "@typespec/openapi3": "~0.40.0",
"@typespec/openapi": "~0.40.0", "@typespec/openapi": "~0.40.0",

Просмотреть файл

@ -1,4 +1,4 @@
import "@typespec/rest"; import "@typespec/http";
using TypeSpec.Http; using TypeSpec.Http;
@service({ @service({

Просмотреть файл

@ -1,3 +1,4 @@
import "@typespec/http";
import "@typespec/rest"; import "@typespec/rest";
@service({ @service({

Просмотреть файл

@ -1,3 +1,4 @@
import "@typespec/http";
import "@typespec/rest"; import "@typespec/rest";
import "@typespec/openapi3"; import "@typespec/openapi3";

Просмотреть файл

@ -1,3 +1,4 @@
import "@typespec/http";
import "@typespec/rest"; import "@typespec/rest";
import "@typespec/versioning"; import "@typespec/versioning";

Просмотреть файл

@ -5,6 +5,7 @@ const config = definePlaygroundViteConfig({
defaultEmitter: "@typespec/openapi3", defaultEmitter: "@typespec/openapi3",
libraries: [ libraries: [
"@typespec/compiler", "@typespec/compiler",
"@typespec/http",
"@typespec/rest", "@typespec/rest",
"@typespec/openapi", "@typespec/openapi",
"@typespec/versioning", "@typespec/versioning",

Просмотреть файл

@ -1,10 +1,10 @@
# TypeSpec HTTP/Rest Library # TypeSpec REST Library
This package provides [TypeSpec](https://github.com/microsoft/TypeSpec) decorators, models, and interfaces to describe HTTP and REST API. With fundamental models and decorators defined in TypeSpec.Http namespace, you will be able describe basic http level operations. TypeSpec.Rest namespace adds additional predefined models and interfaces. These building blocks make defining REST resources and operations based on standard patterns extremely simple. This package provides [TypeSpec](https://github.com/microsoft/TypeSpec) decorators, models, and interfaces to describe APIs using the [REST style](https://en.wikipedia.org/wiki/Representational_state_transfer). These building blocks make defining REST resources and operations based on standard patterns extremely simple.
## Install ## Install
In your typespec project root In your TypeSpec project root
```bash ```bash
npm install @typespec/rest npm install @typespec/rest
@ -15,11 +15,10 @@ npm install @typespec/rest
```TypeSpec ```TypeSpec
import "@typespec/rest"; import "@typespec/rest";
using TypeSpec.Http;
using TypeSpec.Rest; using TypeSpec.Rest;
``` ```
See [Http and rest](https://microsoft.github.io/typespec/docs/standard-library/http/). See [Http and rest](https://microsoft.github.io/typespec/docs/standard-library/rest/).
## Library Tour ## Library Tour
@ -36,64 +35,19 @@ See [Http and rest](https://microsoft.github.io/typespec/docs/standard-library/h
## Models ## Models
- ### HTTP namespace | Model | Notes |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| Model | Notes | | KeysOf&lt;T> | Dynamically gathers keys of the model type T. |
| ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | Page&lt;T> | A model defines page of T which includes an array of T and optional next link. |
| LocationHeader | Location header | | ParentKeysOf&lt;T> | Dynamically gathers parent keys of the model type T, which are referenced with `@parentResource` decorator. |
| Response&lt;Status> | &lt;Status> is numerical status code. | | ResourceError | The default error response for resource operations that includes <br> `code: int32` and `message string`. |
| OkResponse&lt;T> | Response&lt;200> with T as the response body model type. | | ResourceParameters&lt;TResource> | Represents operation parameters for resource TResource. Default to KeysOf&lt;T>. |
| CreatedResponse | Response&lt;201> | | ResourceCollectionParameters&lt;TResource> | Represents collection operation parameters for resource TResource. Default to ParentKeysOf&lt;T> |
| AcceptedResponse | Response&lt;202> | | ResourceCreatedResponse&lt;T> | Resource create operation completed successfully. |
| NoContentResponse | Response&lt;204> | | ResourceDeletedResponse | Resource deleted successfully. |
| MovedResponse | Response&lt;301> with LocationHeader for redirected URL |
| NotModifiedResponse | Response&lt;304> |
| UnauthorizedResponse | Response&lt;401> |
| NotFoundResponse | Response&lt;404> |
| ConflictResponse | Response&lt;409> |
| PlainData&lt;T> | Produces a new model with the same properties as T, but with @query, @header, @body, and @path decorators removed from all properties. |
| BasicAuth | Configure `basic` authentication with @useAuth |
| BearerAuth | Configure `bearer` authentication with @useAuth |
| ApiKeyAuth<TLocation, TName> | Configure `apiKey` authentication with @useAuth |
| OAuth2Auth<TFlows> | Configure `oauth2` authentication with @useAuth |
- ### REST namespace
| Model | Notes |
| ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- |
| KeysOf&lt;T> | Dynamically gathers keys of the model type T. |
| Page&lt;T> | A model defines page of T which includes an array of T and optional next link. |
| ParentKeysOf&lt;T> | Dynamically gathers parent keys of the model type T, which are referenced with `@parentResource` decorator. |
| ResourceError | The default error response for resource operations that includes <br> `code: int32` and `message string`. |
| ResourceParameters&lt;TResource> | Represents operation parameters for resource TResource. Default to KeysOf&lt;T>. |
| ResourceCollectionParameters&lt;TResource> | Represents collection operation parameters for resource TResource. Default to ParentKeysOf&lt;T> |
| ResourceCreatedResponse&lt;T> | Resource create operation completed successfully. |
| ResourceDeletedResponse | Resource deleted successfully. |
## Decorators ## Decorators
- ### HTTP namespace
The `@typespec/rest` library defines the following decorators in `TypeSpec.Http` namespace:
| Declarator | Scope | Usage |
| ----------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| @get | operations | indicating operation uses HTTP `GET` verb. |
| @put | operations | indicating operation uses HTTP `PUT` verb. |
| @post | operations | indicating operation uses HTTP `POST` verb. |
| @patch | operations | indicating operation uses HTTP `PATCH` verb. |
| @delete | operations | indicating operation uses HTTP `DEL` verb. |
| @head | operations | indicating operation uses HTTP `HEAD` verb. |
| @header | model properties and operation parameters | indicating the properties are request or response headers. |
| @query | model properties and operation parameters | indicating the properties are in the request query string. |
| @body | model properties and operation parameters | indicating the property is in request or response body. Only one allowed per model and operation. |
| @path | model properties and operation parameters | indicating the properties are in request path. |
| @statusCode | model properties and operation parameters | indicating the property is the return status code. Only one allowed per model. |
| @server | namespace | Configure the server url for the service. |
| @route | operations, namespaces, interfaces | Syntax:<br> `@route(routeString)`<br><br>Note:<br>`@route` defines the relative route URI for the target operation. The `routeString` argument should be a URI fragment that may contain one or more path parameter fields. If the namespace or interface that contains the operation is also marked with a `@route` decorator, it will be used as a prefix to the route URI of the operation. |
| @useAuth | namespace | Configure the service authentication. |
- ### REST namespace
The `@typespec/rest` library defines the following decorators in `TypeSpec.Rest` namespace: The `@typespec/rest` library defines the following decorators in `TypeSpec.Rest` namespace:
| Declarator | Scope | Syntax | | Declarator | Scope | Syntax |
@ -115,10 +69,6 @@ The `@typespec/rest` library defines the following decorators in `TypeSpec.Rest`
## Interfaces ## Interfaces
- ### HTTP namespace
None
- ### REST namespace
These standard interfaces defines resource operations in basic building blocks that you can expose on the resources. You can use `extends` to compose the operations to meet the exact needs of your resource APIs. These standard interfaces defines resource operations in basic building blocks that you can expose on the resources. You can use `extends` to compose the operations to meet the exact needs of your resource APIs.
For example, for below `Widget` model For example, for below `Widget` model
@ -173,30 +123,8 @@ interface WidgetService
| ExtensionResourceCollectionOperations&lt;TExtension, TResource, TError> | Combines extension resource POST + LIST operations. | | ExtensionResourceCollectionOperations&lt;TExtension, TResource, TError> | Combines extension resource POST + LIST operations. |
| ExtensionResourceOperations&lt;TExtension, TResource, TError> | Combines extension resource instance and collection operations. Includes GET + PATCH + DEL + POST + LIST operations. | | ExtensionResourceOperations&lt;TExtension, TResource, TError> | Combines extension resource instance and collection operations. Includes GET + PATCH + DEL + POST + LIST operations. |
## How to
### Specify content type
To specify the content type you can add a `@header contentType: <value>` in the operation parameter(For request content type) or return type(For response content type)
Example: return `application/png` byte body
```typespec
op getPng(): {
@header contentType: "application/png";
@body _: bytes;
};
```
Example: expect `application/png` byte body
```typespec
op getPng(@header contentType: "application/png", @body _: bytes): void;
```
## See also ## See also
- [HTTP example](https://cadlplayground.z22.web.core.windows.net/?c=aW1wb3J0ICJAY2FkbC1sYW5nL3Jlc3QiOwoKQHNlcnZpY2VUaXRsZSgiV2lkZ2V0IFPGFSIpCm5hbWVzcGFjZSBEZW1vxxg7CnVzaW5nIENhZGwuSHR0cDsKCm1vZGVsIMdAewogIEBrZXkgaWQ6IHN0cmluZzsKICB3ZWlnaHQ6IGludDMyxBFjb2xvcjogInJlZCIgfCAiYmx1ZSI7Cn0KCkBlcnJvcsdWRcQMxVVjb2Rly0BtZXNzYWdlymR9CgppbnRlcmbkALLmAI3nALTFP0DkAJ1saXN0KCk6xx9bXSB8xmHEUUByb3V0ZSgid8Uccy97aWR9IinGOHJlYWQoQHBhdGjrANfJSM1GcG9zdCBjcmVhdGUoQGJvZHkgxAXIK9Y0x3pjdXN0b21HZXTId8kR6gC0yjh9Cg%3D%3D):
- [REST example](https://cadlplayground.z22.web.core.windows.net/?c=aW1wb3J0ICJAY2FkbC1sYW5nL3Jlc3QiOwoKQHNlcnZpY2VUaXRsZSgiV2lkZ2V0IFPGFSIpCm5hbWVzcGFjZSBEZW1vxxg7Cgp1c2luZyBDYWRsLkh0dHA7zBFSZXN0OwoKbW9kZWwgx1J7CiAgQGtleSBpZDogc3RyaW5nOwogIHdlaWdodDogaW50MzLEEWNvbG9yOiAicmVkIiB8ICJibHVlIjsKfQoKQGVycm9yx1ZFxAzFVWNvZGXLQG1lc3NhZ2XKZH0KCmludGVyZuQAxOYAjecAxiBleHRlbmRzIFJlc291cmNl5AC5xQlPcGVyYXRpb25zPMYyLMZxPsVyQOQA0EByb3V0ZSgiY3VzdG9tR2V0IikgyQwoKTrHa%2BQAgA%3D%3D): - [REST example](https://cadlplayground.z22.web.core.windows.net/?c=aW1wb3J0ICJAY2FkbC1sYW5nL3Jlc3QiOwoKQHNlcnZpY2VUaXRsZSgiV2lkZ2V0IFPGFSIpCm5hbWVzcGFjZSBEZW1vxxg7Cgp1c2luZyBDYWRsLkh0dHA7zBFSZXN0OwoKbW9kZWwgx1J7CiAgQGtleSBpZDogc3RyaW5nOwogIHdlaWdodDogaW50MzLEEWNvbG9yOiAicmVkIiB8ICJibHVlIjsKfQoKQGVycm9yx1ZFxAzFVWNvZGXLQG1lc3NhZ2XKZH0KCmludGVyZuQAxOYAjecAxiBleHRlbmRzIFJlc291cmNl5AC5xQlPcGVyYXRpb25zPMYyLMZxPsVyQOQA0EByb3V0ZSgiY3VzdG9tR2V0IikgyQwoKTrHa%2BQAgA%3D%3D):
- [TypeSpec Getting Started](https://github.com/microsoft/typespec#getting-started) - [TypeSpec Getting Started](https://github.com/microsoft/typespec#getting-started)
- [TypeSpec Website](https://microsoft.github.io/typespec) - [TypeSpec Website](https://microsoft.github.io/typespec)

Просмотреть файл

@ -1,4 +1,4 @@
import "./http.tsp"; import "@typespec/http";
import "../dist/src/index.js"; import "../dist/src/index.js";
import "../dist/src/internal-decorators.js"; import "../dist/src/internal-decorators.js";

Просмотреть файл

@ -3,7 +3,7 @@ namespace TypeSpec.Rest;
using TypeSpec.Reflection; using TypeSpec.Reflection;
/** /**
* This namespace, interface or operation should resolve its route automatically. To be used with resource types where the route segments area defined on the models. * This interface or operation should resolve its route automatically. To be used with resource types where the route segments area defined on the models.
* *
* @example * @example
* *
@ -14,7 +14,7 @@ using TypeSpec.Reflection;
* } * }
* ``` * ```
*/ */
extern dec autoRoute(target: Namespace | Interface | Operation); extern dec autoRoute(target: Interface | Operation);
/** /**
* Defines the preceding path segment for a @path parameter in auto-generated routes. * Defines the preceding path segment for a @path parameter in auto-generated routes.

Просмотреть файл

@ -1,4 +1,4 @@
import "./http.tsp"; import "@typespec/http";
import "./rest-decorators.tsp"; import "./rest-decorators.tsp";
import "./resource.tsp"; import "./resource.tsp";

Просмотреть файл

@ -21,7 +21,6 @@
"tspMain": "lib/rest.tsp", "tspMain": "lib/rest.tsp",
"exports": { "exports": {
".": "./dist/src/index.js", ".": "./dist/src/index.js",
"./http": "./dist/src/http/index.js",
"./testing": "./dist/src/testing/index.js" "./testing": "./dist/src/testing/index.js"
}, },
"typesVersions": { "typesVersions": {
@ -29,9 +28,6 @@
"*": [ "*": [
"./dist/src/index.d.ts" "./dist/src/index.d.ts"
], ],
"http": [
"./dist/src/http/index.d.ts"
],
"testing": [ "testing": [
"./dist/src/testing/index.d.ts" "./dist/src/testing/index.d.ts"
] ]
@ -62,6 +58,7 @@
"@types/mocha": "~10.0.0", "@types/mocha": "~10.0.0",
"@types/node": "~18.11.9", "@types/node": "~18.11.9",
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/eslint-config-typespec": "~0.5.0", "@typespec/eslint-config-typespec": "~0.5.0",
"@typespec/library-linter": "~0.40.0", "@typespec/library-linter": "~0.40.0",
"@typespec/eslint-plugin": "~0.40.0", "@typespec/eslint-plugin": "~0.40.0",

Просмотреть файл

@ -1,241 +0,0 @@
import {
createDiagnosticCollector,
Diagnostic,
Interface,
Namespace,
Operation,
Program,
Type,
} from "@typespec/compiler";
import { createDiagnostic, createStateSymbol } from "../lib.js";
import { getActionSegment, getActionSeparator, getSegment, isAutoRoute } from "../rest.js";
import { extractParamsFromPath } from "../utils.js";
import { getRouteOptionsForNamespace, getRoutePath } from "./decorators.js";
import { getOperationParameters } from "./parameters.js";
import {
HttpOperation,
HttpOperationParameter,
HttpOperationParameters,
RouteOptions,
RouteResolutionOptions,
} from "./types.js";
// The set of allowed segment separator characters
const AllowedSegmentSeparators = ["/", ":"];
function normalizeFragment(fragment: string) {
if (fragment.length > 0 && AllowedSegmentSeparators.indexOf(fragment[0]) < 0) {
// Insert the default separator
fragment = `/${fragment}`;
}
// Trim any trailing slash
return fragment.replace(/\/$/g, "");
}
function buildPath(pathFragments: string[]) {
// Join all fragments with leading and trailing slashes trimmed
const path =
pathFragments.length === 0
? "/"
: pathFragments
.map(normalizeFragment)
.filter((x) => x !== "")
.join("");
// The final path must start with a '/'
return path.length > 0 && path[0] === "/" ? path : `/${path}`;
}
function addActionFragment(program: Program, target: Type, pathFragments: string[]) {
// add the action segment, if present
const defaultSeparator = "/";
const actionSegment = getActionSegment(program, target);
if (actionSegment && actionSegment !== "") {
const actionSeparator = getActionSeparator(program, target) ?? defaultSeparator;
pathFragments.push(`${actionSeparator}${actionSegment}`);
}
}
function addSegmentFragment(program: Program, target: Type, pathFragments: string[]) {
// Don't add the segment prefix if it is meant to be excluded
// (empty string means exclude the segment)
const segment = getSegment(program, target);
if (segment && segment !== "") {
pathFragments.push(`/${segment}`);
}
}
function generatePathFromParameters(
program: Program,
operation: Operation,
pathFragments: string[],
parameters: HttpOperationParameters,
options: RouteResolutionOptions
) {
const filteredParameters: HttpOperationParameter[] = [];
for (const httpParam of parameters.parameters) {
const { type, param } = httpParam;
if (type === "path") {
addSegmentFragment(program, param, pathFragments);
const filteredParam = options.autoRouteOptions?.routeParamFilter?.(operation, param);
if (filteredParam?.routeParamString) {
pathFragments.push(`/${filteredParam.routeParamString}`);
if (filteredParam?.excludeFromOperationParams === true) {
// Skip the rest of the loop so that we don't add the parameter to the final list
continue;
}
} else {
// Add the path variable for the parameter
if (param.type.kind === "String") {
pathFragments.push(`/${param.type.value}`);
continue; // Skip adding to the parameter list
} else {
pathFragments.push(`/{${param.name}}`);
}
}
}
// Push all usable parameters to the filtered list
filteredParameters.push(httpParam);
}
// Replace the original parameters with filtered set
parameters.parameters = filteredParameters;
// Add the operation's own segment if present
addSegmentFragment(program, operation, pathFragments);
// Add the operation's action segment if present
addActionFragment(program, operation, pathFragments);
}
export function resolvePathAndParameters(
program: Program,
operation: Operation,
overloadBase: HttpOperation | undefined,
options: RouteResolutionOptions
): [
{
path: string;
pathSegments: string[];
parameters: HttpOperationParameters;
},
readonly Diagnostic[]
] {
let segments = getOperationRouteSegments(program, operation, overloadBase);
let parameters: HttpOperationParameters;
const diagnostics = createDiagnosticCollector();
if (isAutoRoute(program, operation)) {
const [parentSegments, parentOptions] = getParentSegments(program, operation);
segments = parentSegments.length ? parentSegments : segments;
parameters = diagnostics.pipe(getOperationParameters(program, operation));
// The operation exists within an @autoRoute scope, generate the path. This
// mutates the pathFragments and parameters lists that are passed in!
generatePathFromParameters(program, operation, segments, parameters, {
...parentOptions,
...options,
});
} else {
const declaredPathParams = segments.flatMap(extractParamsFromPath);
parameters = diagnostics.pipe(
getOperationParameters(program, operation, overloadBase, declaredPathParams)
);
// Pull out path parameters to verify what's in the path string
const paramByName = new Map(
parameters.parameters
.filter(({ type }) => type === "path")
.map(({ param }) => [param.name, param])
);
// For each param in the declared path parameters (e.g. /foo/{id} has one, id),
// delete it because it doesn't need to be added to the path.
for (const declaredParam of declaredPathParams) {
const param = paramByName.get(declaredParam);
if (!param) {
diagnostics.add(
createDiagnostic({
code: "missing-path-param",
format: { param: declaredParam },
target: operation,
})
);
continue;
}
paramByName.delete(declaredParam);
}
// Add any remaining declared path params
for (const param of paramByName.keys()) {
segments.push(`{${param}}`);
}
}
return diagnostics.wrap({
path: buildPath(segments),
pathSegments: segments,
parameters,
});
}
function getOperationRouteSegments(
program: Program,
operation: Operation,
overloadBase: HttpOperation | undefined
): string[] {
if (overloadBase !== undefined && getRoutePath(program, operation) === undefined) {
return overloadBase.pathSegments;
} else {
return getRouteSegments(program, operation)[0];
}
}
function getParentSegments(
program: Program,
target: Operation | Interface | Namespace
): [string[], RouteOptions | undefined] {
return "interface" in target && target.interface
? getRouteSegments(program, target.interface)
: target.namespace
? getRouteSegments(program, target.namespace)
: [[], undefined];
}
function getRouteSegments(
program: Program,
target: Operation | Interface | Namespace
): [string[], RouteOptions | undefined] {
const route = getRoutePath(program, target)?.path;
const seg = route ? [route] : [];
const [parentSegments, parentOptions] = getParentSegments(program, target);
const options =
target.kind === "Namespace" ? getRouteOptionsForNamespace(program, target) : undefined;
return [[...parentSegments, ...seg], options ?? parentOptions];
}
const externalInterfaces = createStateSymbol("externalInterfaces");
/**
* @deprecated DO NOT USE. For internal use only as a workaround.
* @param program Program
* @param target Target namespace
* @param sourceInterface Interface that should be included in namespace.
*/
export function includeInterfaceRoutesInNamespace(
program: Program,
target: Namespace,
sourceInterface: string
) {
let array = program.stateMap(externalInterfaces).get(target);
if (array === undefined) {
array = [];
program.stateMap(externalInterfaces).set(target, array);
}
array.push(sourceInterface);
}

Просмотреть файл

@ -1,5 +1,4 @@
export const namespace = "TypeSpec.Rest"; export const namespace = "TypeSpec.Rest";
export * as http from "./http/index.js";
export * from "./resource.js"; export * from "./resource.js";
export * from "./rest.js"; export * from "./rest.js";
export * from "./validate.js"; export * from "./validate.js";

Просмотреть файл

@ -3,24 +3,6 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
const libDefinition = { const libDefinition = {
name: "@typespec/rest", name: "@typespec/rest",
diagnostics: { diagnostics: {
"http-verb-duplicate": {
severity: "error",
messages: {
default: paramMessage`HTTP verb already applied to ${"entityName"}`,
},
},
"http-verb-wrong-type": {
severity: "error",
messages: {
default: paramMessage`Cannot use @${"verb"} on a ${"entityKind"}`,
},
},
"operation-resource-wrong-type": {
severity: "error",
messages: {
default: paramMessage`Cannot register resource operation "${"operation"}" on a ${"kind"}`,
},
},
"not-key-type": { "not-key-type": {
severity: "error", severity: "error",
messages: { messages: {
@ -51,109 +33,6 @@ const libDefinition = {
default: paramMessage`Resource type '${"resourceName"}' has a key property named '${"keyName"}' which conflicts with the key name of a parent or child resource.`, default: paramMessage`Resource type '${"resourceName"}' has a key property named '${"keyName"}' which conflicts with the key name of a parent or child resource.`,
}, },
}, },
"missing-path-param": {
severity: "error",
messages: {
default: paramMessage`Path contains parameter ${"param"} but wasn't found in given parameters`,
},
},
"optional-path-param": {
severity: "error",
messages: {
default: paramMessage`Path parameter '${"paramName"}' cannot be optional.`,
},
},
"missing-server-param": {
severity: "error",
messages: {
default: paramMessage`Server url contains parameter '${"param"}' but wasn't found in given parameters`,
},
},
"duplicate-body": {
severity: "error",
messages: {
default: "Operation has multiple @body parameters declared",
duplicateUnannotated:
"Operation has multiple unannotated parameters. There can only be one representing the body",
bodyAndUnannotated:
"Operation has a @body and an unannotated parameter. There can only be one representing the body",
},
},
"duplicate-route-decorator": {
severity: "error",
messages: {
namespace: "@route was defined twice on this namespace and has different values.",
},
},
"operation-param-duplicate-type": {
severity: "error",
messages: {
default: paramMessage`Param ${"paramName"} has multiple types: [${"types"}]`,
},
},
"duplicate-operation": {
severity: "error",
messages: {
default: paramMessage`Duplicate operation "${"operationName"}" routed at "${"verb"} ${"path"}".`,
},
},
"status-code-invalid": {
severity: "error",
messages: {
default:
"statusCode value must be a numeric or string literal or union of numeric or string literals",
value: "statusCode value must be a three digit code between 100 and 599",
},
},
"content-type-string": {
severity: "error",
messages: {
default: "contentType parameter must be a string literal or union of string literals",
},
},
"duplicate-response": {
severity: "error",
messages: {
default: paramMessage`Multiple return types for content type ${"contentType"} and status code ${"statusCode"}`,
},
},
"content-type-ignored": {
severity: "warning",
messages: {
default: "`Content-Type` header ignored because there is no body.",
},
},
"no-routes": {
severity: "warning",
messages: {
default:
"Current spec is not exposing any routes. This could be to not having the service namespace marked with @service.",
},
},
"invalid-type-for-auth": {
severity: "error",
messages: {
default: paramMessage`@useAuth ${"kind"} only accept Auth model, Tuple of auth model or union of auth model.`,
},
},
"shared-boolean": {
severity: "error",
messages: {
default: "shared parameter must be a boolean.",
},
},
"write-visibility-not-supported": {
severity: "warning",
messages: {
default: `@visibility("write") is not supported. Use @visibility("update"), @visibility("create") or @visibility("create", "update") as appropriate.`,
},
},
"multipart-model": {
severity: "error",
messages: {
default: "Multipart request body must be a model.",
},
},
}, },
} as const; } as const;

Просмотреть файл

@ -10,7 +10,7 @@ import {
Type, Type,
validateDecoratorTarget, validateDecoratorTarget,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { $path } from "./http/decorators.js"; import { $path } from "@typespec/http";
import { createStateSymbol, reportDiagnostic } from "./lib.js"; import { createStateSymbol, reportDiagnostic } from "./lib.js";
export interface ResourceKey { export interface ResourceKey {

Просмотреть файл

@ -1,53 +1,185 @@
import { import {
$list, $list,
createDiagnosticCollector,
DecoratorContext, DecoratorContext,
DiagnosticResult,
Interface, Interface,
Model, Model,
ModelProperty, ModelProperty,
Namespace,
Operation, Operation,
Program, Program,
Scalar, Scalar,
setTypeSpecNamespace, setTypeSpecNamespace,
Type, Type,
} from "@typespec/compiler"; } from "@typespec/compiler";
import {
DefaultRouteProducer,
getOperationParameters,
getOperationVerb,
getRoutePath,
getRouteProducer,
HttpOperation,
HttpOperationParameter,
HttpOperationParameters,
HttpVerb,
RouteOptions,
RouteProducerResult,
setRouteProducer,
} from "@typespec/http";
import { createStateSymbol, reportDiagnostic } from "./lib.js"; import { createStateSymbol, reportDiagnostic } from "./lib.js";
import { getResourceTypeKey } from "./resource.js"; import { getResourceTypeKey } from "./resource.js";
// ----------------- @autoRoute ----------------- // ----------------- @autoRoute -----------------
function addActionFragment(program: Program, target: Type, pathFragments: string[]) {
// add the action segment, if present
const defaultSeparator = "/";
const actionSegment = getActionSegment(program, target);
if (actionSegment && actionSegment !== "") {
const actionSeparator = getActionSeparator(program, target) ?? defaultSeparator;
pathFragments.push(`${actionSeparator}${actionSegment}`);
}
}
function addSegmentFragment(program: Program, target: Type, pathFragments: string[]) {
// Don't add the segment prefix if it is meant to be excluded
// (empty string means exclude the segment)
const segment = getSegment(program, target);
if (segment && segment !== "") {
pathFragments.push(`/${segment}`);
}
}
export interface FilteredRouteParam {
routeParamString?: string;
excludeFromOperationParams?: boolean;
}
export interface AutoRouteOptions {
routeParamFilter?: (op: Operation, param: ModelProperty) => FilteredRouteParam | undefined;
}
// TODO: Make this overridable by libraries
const resourceOperationToVerb: any = {
read: "get",
create: "post",
createOrUpdate: "patch",
createOrReplace: "put",
update: "patch",
delete: "delete",
list: "get",
};
function getResourceOperationHttpVerb(
program: Program,
operation: Operation
): HttpVerb | undefined {
const resourceOperation = getResourceOperation(program, operation);
return (
getOperationVerb(program, operation) ??
(resourceOperation && resourceOperationToVerb[resourceOperation.operation]) ??
(getAction(program, operation) || getCollectionAction(program, operation) ? "post" : undefined)
);
}
function autoRouteProducer(
program: Program,
operation: Operation,
parentSegments: string[],
overloadBase: HttpOperation | undefined,
options: RouteOptions
): DiagnosticResult<RouteProducerResult> {
const diagnostics = createDiagnosticCollector();
const routePath = getRoutePath(program, operation)?.path;
const segments = [...parentSegments, ...(routePath ? [routePath] : [])];
const filteredParameters: HttpOperationParameter[] = [];
const paramOptions = {
...(options?.paramOptions ?? {}),
verbSelector: getResourceOperationHttpVerb,
};
const parameters: HttpOperationParameters = diagnostics.pipe(
getOperationParameters(program, operation, undefined, [], paramOptions)
);
for (const httpParam of parameters.parameters) {
const { type, param } = httpParam;
if (type === "path") {
addSegmentFragment(program, param, segments);
const filteredParam = options.autoRouteOptions?.routeParamFilter?.(operation, param);
if (filteredParam?.routeParamString) {
segments.push(`/${filteredParam.routeParamString}`);
if (filteredParam?.excludeFromOperationParams === true) {
// Skip the rest of the loop so that we don't add the parameter to the final list
continue;
}
} else {
// Add the path variable for the parameter
if (param.type.kind === "String") {
segments.push(`/${param.type.value}`);
continue; // Skip adding to the parameter list
} else {
segments.push(`/{${param.name}}`);
}
}
}
// Push all usable parameters to the filtered list
filteredParameters.push(httpParam);
}
// Replace the original parameters with filtered set
parameters.parameters = filteredParameters;
// Add the operation's own segment if present
addSegmentFragment(program, operation, segments);
// Add the operation's action segment if present
addActionFragment(program, operation, segments);
return diagnostics.wrap({
segments,
parameters: {
...parameters,
parameters: filteredParameters,
},
});
}
const autoRouteKey = createStateSymbol("autoRoute"); const autoRouteKey = createStateSymbol("autoRoute");
/** /**
* `@autoRoute` enables automatic route generation for an operation, namespace, or interface. * `@autoRoute` enables automatic route generation for an operation or interface.
* *
* When applied to an operation, it automatically generates the operation's route based on path parameter * When applied to an operation, it automatically generates the operation's route based on path parameter
* metadata. When applied to a namespace or interface, it causes all operations under that scope to have * metadata. When applied to an interface, it causes all operations under that scope to have
* auto-generated routes. * auto-generated routes.
*/ */
export function $autoRoute(context: DecoratorContext, entity: Interface | Operation) {
if (entity.kind === "Operation") {
setRouteProducer(context.program, entity, autoRouteProducer);
} else {
for (const [_, op] of entity.operations) {
// Instantly apply the decorator to the operation
context.call($autoRoute, op);
export function $autoRoute(context: DecoratorContext, entity: Namespace | Interface | Operation) { // Manually push the decorator onto the property so that it gets applied
context.program.stateSet(autoRouteKey).add(entity); // to operations which reference the operation with `is`
} op.decorators.push({
decorator: $autoRoute,
export function isAutoRoute(program: Program, target: Namespace | Interface | Operation): boolean { args: [],
// Loop up through parent scopes (interface, namespace) to see if });
// @autoRoute was used anywhere
let current: Namespace | Interface | Operation | undefined = target;
while (current !== undefined) {
if (program.stateSet(autoRouteKey).has(current)) {
return true;
}
// Navigate up to the parent scope
if (current.kind === "Namespace" || current.kind === "Interface") {
current = current.namespace;
} else if (current.kind === "Operation") {
current = current.interface || current.namespace;
} }
} }
return false; context.program.stateSet(autoRouteKey).add(entity);
}
export function isAutoRoute(program: Program, entity: Operation | Interface): boolean {
return program.stateSet(autoRouteKey).has(entity);
} }
// ------------------ @segment ------------------ // ------------------ @segment ------------------
@ -172,6 +304,31 @@ export interface ResourceOperation {
const resourceOperationsKey = createStateSymbol("resourceOperations"); const resourceOperationsKey = createStateSymbol("resourceOperations");
function resourceRouteProducer(
program: Program,
operation: Operation,
parentSegments: string[],
overloadBase: HttpOperation | undefined,
options: RouteOptions
): DiagnosticResult<RouteProducerResult> {
// NOTE: The purpose of this producer is to pass along the behavior of the
// DefaultRouteProducer while setting the appropriate HTTP verb based on any
// resource operation decorators that have been applied. This behavior will
// be overridden by the `autoRouteProducer` if `autoRoute` is also applied to
// the same operation.
// Set the OperationVerbSelector to pick verbs based on resource operation type
const paramOptions = {
...(options?.paramOptions ?? {}),
verbSelector: getResourceOperationHttpVerb,
};
return DefaultRouteProducer(program, operation, parentSegments, overloadBase, {
...options,
paramOptions,
});
}
export function setResourceOperation( export function setResourceOperation(
context: DecoratorContext, context: DecoratorContext,
entity: Operation, entity: Operation,
@ -187,6 +344,13 @@ export function setResourceOperation(
operation, operation,
resourceType, resourceType,
}); });
// Set a custom RouteProducer on the operation if one hasn't already been
// established yet. This is intended to translate lifecycle operations to
// HTTP verbs.
if (!getRouteProducer(context.program, entity)) {
setRouteProducer(context.program, entity, resourceRouteProducer);
}
} }
export function getResourceOperation( export function getResourceOperation(

Просмотреть файл

@ -7,7 +7,6 @@ import {
navigateTypesInNamespace, navigateTypesInNamespace,
Program, Program,
} from "@typespec/compiler"; } from "@typespec/compiler";
import { getAllHttpServices } from "./http/operations.js";
import { reportDiagnostic } from "./lib.js"; import { reportDiagnostic } from "./lib.js";
import { getParentResource, getResourceTypeKey, ResourceKey } from "./resource.js"; import { getParentResource, getResourceTypeKey, ResourceKey } from "./resource.js";
@ -65,10 +64,4 @@ export function $onValidate(program: Program) {
// Make sure any defined resource types don't have any conflicts with parent // Make sure any defined resource types don't have any conflicts with parent
// resource type key names // resource type key names
checkForDuplicateResourceKeyNames(program); checkForDuplicateResourceKeyNames(program);
// Pass along any diagnostics that might be returned from the HTTP library
const [, diagnostics] = getAllHttpServices(program);
if (diagnostics.length > 0) {
program.reportDiagnostics(diagnostics);
}
} }

Просмотреть файл

@ -151,14 +151,14 @@ describe("rest: resources", () => {
` `
using TypeSpec.Rest.Resource; using TypeSpec.Rest.Resource;
@autoRoute model Thing {
namespace Things { @key
model Thing { @segment("things")
@key thingId: string;
@segment("things") }
thingId: string;
}
@autoRoute
interface Things {
@post @post
@collectionAction(Thing, "export1") @collectionAction(Thing, "export1")
op exportThing(): {}; op exportThing(): {};

Просмотреть файл

@ -1,240 +1,9 @@
import { expectDiagnosticEmpty, expectDiagnostics } from "@typespec/compiler/testing"; import { ModelProperty, Operation } from "@typespec/compiler";
import { expectDiagnostics } from "@typespec/compiler/testing";
import { deepStrictEqual, strictEqual } from "assert"; import { deepStrictEqual, strictEqual } from "assert";
import { HttpOperation } from "../src/http/types.js";
import { compileOperations, getOperations, getRoutesFor } from "./test-host.js"; import { compileOperations, getOperations, getRoutesFor } from "./test-host.js";
describe("rest: routes", () => { describe("rest: routes", () => {
// Describe how routes should be included.
describe("route inclusion", () => {
function expectRouteIncluded(routes: HttpOperation[], expectedRoutePaths: string[]) {
const includedRoutes = routes.map((x) => x.path);
deepStrictEqual(includedRoutes, expectedRoutePaths);
}
describe("when there is NO service namespace", () => {
it("operations at the document root are included", async () => {
const routes = await getOperations(`
@route("/one")
@get op one(): string;
@route("/two")
@get op two(): string;
`);
expectRouteIncluded(routes, ["/one", "/two"]);
});
it("interface at the document root are included", async () => {
const routes = await getOperations(`
interface Foo {
@get index(): void;
}
`);
expectRouteIncluded(routes, ["/"]);
});
it("generic operation at the document root are NOT included", async () => {
const routes = await getOperations(`
@route("/not-included")
@get op index<T>(): T;
`);
expectRouteIncluded(routes, []);
});
it("generic interface at the document root are NOT included", async () => {
const routes = await getOperations(`
interface Foo<T> {
@route("/not-included")
@get index(): T;
}
`);
expectRouteIncluded(routes, []);
});
it("routes inside a namespace not marked as the service namespace aren't be included", async () => {
const routes = await getOperations(
`
namespace Foo {
@get op index(): void;
}
`
);
deepStrictEqual(routes, []);
});
});
describe("when there is a service namespace", () => {
it("operation in the service namespace are included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService;
@get op index(): void;
`
);
expectRouteIncluded(routes, ["/"]);
});
it("operation at the root of the document are NOT included", async () => {
const routes = await getOperations(
`
@route("/not-included")
@get op notIncluded(): void;
@service({title: "My Service"})
namespace MyService {
@route("/included")
@get op included(): void;
}
`
);
expectRouteIncluded(routes, ["/included"]);
});
it("interface in the service namespace are included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService;
interface Foo {
@get index(): void;
}`
);
expectRouteIncluded(routes, ["/"]);
});
it("operation in namespace in the service namespace are be included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService;
namespace MyArea{
@get op index(): void;
}
`
);
expectRouteIncluded(routes, ["/"]);
});
it("operation in a different namespace are not included", async () => {
const routes = await getOperations(
`
@service({title: "My Service"})
namespace MyService {
@route("/included")
@get op test(): string;
}
namespace MyLib {
@route("/not-included")
@get op notIncluded(): void;
}
`
);
expectRouteIncluded(routes, ["/included"]);
});
});
});
it("combines routes on namespaced bare operations", async () => {
const routes = await getRoutesFor(
`
@route("/things")
namespace Things {
@get op GetThing(): string;
@route("/{thingId}")
@put op CreateThing(@path thingId: string): string;
@route("/{thingId}/subthings")
namespace Subthing {
@get op GetSubthing(@path thingId: string): string;
@route("/{subthingId}")
@post op CreateSubthing(@path thingId: string, @path subthingId: string): string;
}
}
`
);
deepStrictEqual(routes, [
{ verb: "get", path: "/things", params: [] },
{ verb: "put", path: "/things/{thingId}", params: ["thingId"] },
{ verb: "get", path: "/things/{thingId}/subthings", params: ["thingId"] },
{
verb: "post",
path: "/things/{thingId}/subthings/{subthingId}",
params: ["thingId", "subthingId"],
},
]);
});
it("combines routes between namespaces and interfaces", async () => {
const routes = await getRoutesFor(
`
@route("/things")
namespace Things {
@get op GetThing(): string;
@route("/{thingId}")
@put op CreateThing(@path thingId: string): string;
@route("/{thingId}/subthings")
interface Subthing {
@get GetSubthing(@path thingId: string): string;
@route("/{subthingId}")
@post CreateSubthing(@path thingId: string, @path subthingId: string): string;
}
}
`
);
deepStrictEqual(routes, [
{ verb: "get", path: "/things", params: [] },
{ verb: "put", path: "/things/{thingId}", params: ["thingId"] },
{ verb: "get", path: "/things/{thingId}/subthings", params: ["thingId"] },
{
verb: "post",
path: "/things/{thingId}/subthings/{subthingId}",
params: ["thingId", "subthingId"],
},
]);
});
it("join empty route segments correctly", async () => {
const routes = await getRoutesFor(
`
@route("")
interface Foo {
@get @route("") index(): {};
}
`
);
deepStrictEqual(routes, [{ verb: "get", path: "/", params: [] }]);
});
it("join / route segments correctly", async () => {
const routes = await getRoutesFor(
`
@route("/")
interface Foo {
@get @route("/") index(): {};
}
`
);
deepStrictEqual(routes, [{ verb: "get", path: "/", params: [] }]);
});
it("always produces a route starting with /", async () => { it("always produces a route starting with /", async () => {
const routes = await getRoutesFor( const routes = await getRoutesFor(
` `
@ -266,7 +35,7 @@ describe("rest: routes", () => {
} }
@autoRoute @autoRoute
namespace Things { interface Things {
@get @get
@action @action
op ActionOne(...ThingId): string; op ActionOne(...ThingId): string;
@ -311,8 +80,8 @@ describe("rest: routes", () => {
@autoRoute @autoRoute
@get op GetThingWithParams(...KeysOf<Thing>): string; @get op GetThingWithParams(...KeysOf<Thing>): string;
@autoRoute
namespace SubNamespace { namespace SubNamespace {
@autoRoute
@put op CreateThing(...KeysOf<Thing>): string; @put op CreateThing(...KeysOf<Thing>): string;
} }
@ -352,7 +121,7 @@ describe("rest: routes", () => {
} }
@autoRoute @autoRoute
namespace Things { interface Things {
@get @get
op WithFilteredParam( op WithFilteredParam(
@path @path
@ -377,38 +146,6 @@ describe("rest: routes", () => {
]); ]);
}); });
it("defaults to POST when operation has a body but didn't specify the verb", async () => {
const routes = await getRoutesFor(`
@route("/test")
op get(@body body: string): string;
`);
deepStrictEqual(routes, [
{
verb: "post",
path: "/test",
params: [],
},
]);
});
it("emit diagnostics if 2 operation have the same path and verb", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
op get1(): string;
@route("/test")
op get2(): string;
`);
// Has one diagnostic per duplicate operation
strictEqual(diagnostics.length, 2);
strictEqual(diagnostics[0].code, "@typespec/rest/duplicate-operation");
strictEqual(diagnostics[0].message, `Duplicate operation "get1" routed at "get /test".`);
strictEqual(diagnostics[1].code, "@typespec/rest/duplicate-operation");
strictEqual(diagnostics[1].message, `Duplicate operation "get2" routed at "get /test".`);
});
it("emit diagnostic if passing arguments to autoroute decorators", async () => { it("emit diagnostic if passing arguments to autoroute decorators", async () => {
const [_, diagnostics] = await compileOperations(` const [_, diagnostics] = await compileOperations(`
@autoRoute("/test") op test(): string; @autoRoute("/test") op test(): string;
@ -420,253 +157,6 @@ describe("rest: routes", () => {
}); });
}); });
describe("operation parameters", () => {
it("emit diagnostic for parameters with multiple http request annotations", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@body body: string, @path @query multiParam: string): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/rest/operation-param-duplicate-type",
message: "Param multiParam has multiple types: [query, path]",
});
});
it("emit diagnostic when there is an unannotated parameter and a @body param", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(param1: string, @body param2: string): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/rest/duplicate-body",
message:
"Operation has a @body and an unannotated parameter. There can only be one representing the body",
});
});
it("emit diagnostic when there are multiple @body param", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@query select: string, @body param1: string, @body param2: string): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/rest/duplicate-body",
message: "Operation has multiple @body parameters declared",
});
});
it("emit error if using multipart/form-data contentType parameter with a body not being a model", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@header contentType: "multipart/form-data", @body body: string | int32): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/rest/multipart-model",
message: "Multipart request body must be a model.",
});
});
it("emit warning if using contentType parameter without a body", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@header contentType: "image/png"): string;
`);
expectDiagnostics(diagnostics, {
code: "@typespec/rest/content-type-ignored",
message: "`Content-Type` header ignored because there is no body.",
});
});
it("resolve body when defined with @body", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@query select: string, @body bodyParam: string): string;
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test",
params: { params: [{ type: "query", name: "select" }], body: "bodyParam" },
},
]);
});
it("resolves single unannotated parameter as request body", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test")
@get op get(@query select: string, unannotatedBodyParam: string): string;
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test",
params: {
params: [{ type: "query", name: "select" }],
body: ["unannotatedBodyParam"],
},
},
]);
});
it("resolves multiple unannotated parameters as request body", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test")
@get op get(
@query select: string,
unannotatedBodyParam1: string,
unannotatedBodyParam2: string): string;
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test",
params: {
params: [{ type: "query", name: "select" }],
body: ["unannotatedBodyParam1", "unannotatedBodyParam2"],
},
},
]);
});
it("resolves unannotated path parameters that are included in the route path", async () => {
const [routes, diagnostics] = await compileOperations(`
@route("/test/{name}/sub/{foo}")
@get op get(
name: string,
foo: string
): string;
@route("/nested/{name}")
namespace A {
@route("sub")
namespace B {
@route("{bar}")
@get op get(
name: string,
bar: string
): string;
}
}
`);
expectDiagnosticEmpty(diagnostics);
deepStrictEqual(routes, [
{
verb: "get",
path: "/test/{name}/sub/{foo}",
params: {
params: [
{ type: "path", name: "name" },
{ type: "path", name: "foo" },
],
body: undefined,
},
},
{
verb: "get",
path: "/nested/{name}/sub/{bar}",
params: {
params: [
{ type: "path", name: "name" },
{ type: "path", name: "bar" },
],
body: undefined,
},
},
]);
});
});
describe("double @route", () => {
it("emit diagnostic if specifying route twice on operation", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@route("/test")
op get(): string;
`);
expectDiagnostics(diagnostics, [
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
]);
});
it("emit diagnostic if specifying route twice on interface", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
@route("/test")
interface Foo {
get(): string
}
`);
expectDiagnostics(diagnostics, [
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
{
code: "duplicate-decorator",
message: "Decorator @route cannot be used twice on the same declaration.",
},
]);
});
it("emit diagnostic if namespace have route but different values", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test1")
namespace Foo {
@route("/get1")
op get1(): string;
}
@route("/test2")
namespace Foo {
@route("/get2")
op get2(): string;
}
`);
expectDiagnostics(diagnostics, {
code: "@typespec/rest/duplicate-route-decorator",
message: "@route was defined twice on this namespace and has different values.",
});
});
it("merge namespace if @route value is the same", async () => {
const [_, diagnostics] = await compileOperations(`
@route("/test")
namespace Foo {
@route("/get1")
op get1(): string;
}
@route("/test")
namespace Foo {
@route("/get2")
op get2(): string;
}
`);
expectDiagnosticEmpty(diagnostics);
});
});
describe("use of @route with @autoRoute", () => { describe("use of @route with @autoRoute", () => {
it("can override library operation route in service", async () => { it("can override library operation route in service", async () => {
const ops = await getOperations(` const ops = await getOperations(`
@ -745,6 +235,7 @@ describe("rest: routes", () => {
@service({title: "Test"}) @service({title: "Test"})
namespace Test { namespace Test {
op my is Lib.action; op my is Lib.action;
@route("my") @route("my")
op my2 is Lib.action; op my2 is Lib.action;
} }
@ -794,9 +285,9 @@ describe("rest: routes", () => {
} }
`); `);
strictEqual(ops[0].verb, "get"); strictEqual(ops[0].verb, "get");
strictEqual(ops[0].path, "/{id}"); strictEqual(ops[0].path, "/pets/{id}");
strictEqual(ops[1].verb, "get"); strictEqual(ops[1].verb, "get");
strictEqual(ops[1].path, "/my/{id}"); strictEqual(ops[1].path, "/my/pets/{id}");
}); });
}); });
@ -817,7 +308,7 @@ describe("rest: routes", () => {
`, `,
{ {
autoRouteOptions: { autoRouteOptions: {
routeParamFilter: (_, param) => { routeParamFilter: (_: Operation, param: ModelProperty) => {
return { return {
routeParamString: param.name === "subThingId" ? "bar" : "{foo}", routeParamString: param.name === "subThingId" ? "bar" : "{foo}",
excludeFromOperationParams: true, excludeFromOperationParams: true,
@ -834,7 +325,7 @@ describe("rest: routes", () => {
const routes = await getRoutesFor( const routes = await getRoutesFor(
` `
@autoRoute @autoRoute
namespace Things { interface Things {
@action @action
@actionSeparator(":") @actionSeparator(":")
@put op customAction( @put op customAction(
@ -854,7 +345,7 @@ describe("rest: routes", () => {
const routes = await getRoutesFor( const routes = await getRoutesFor(
` `
@autoRoute @autoRoute
namespace Things { interface Things {
@action @action
@actionSeparator(":") @actionSeparator(":")
@put op customAction1( @put op customAction1(
@ -889,7 +380,7 @@ describe("rest: routes", () => {
const [_, diagnostics] = await compileOperations( const [_, diagnostics] = await compileOperations(
` `
@autoRoute @autoRoute
namespace Things { interface Things {
@action @action
@actionSeparator("x") @actionSeparator("x")
@put op customAction( @put op customAction(

Просмотреть файл

@ -12,19 +12,15 @@ import {
HttpOperationParameter, HttpOperationParameter,
HttpVerb, HttpVerb,
RouteResolutionOptions, RouteResolutionOptions,
} from "../src/http/index.js"; } from "@typespec/http";
import { HttpTestLibrary } from "@typespec/http/testing";
import { RestTestLibrary } from "../src/testing/index.js"; import { RestTestLibrary } from "../src/testing/index.js";
export async function createRestTestHost(): Promise<TestHost> { export async function createRestTestHost(): Promise<TestHost> {
return createTestHost({ return createTestHost({
libraries: [RestTestLibrary], libraries: [HttpTestLibrary, RestTestLibrary],
}); });
} }
export async function createHttpTestRunner(): Promise<BasicTestRunner> {
const host = await createRestTestHost();
return createTestWrapper(host, { autoUsings: ["TypeSpec.Http"] });
}
export async function createRestTestRunner(): Promise<BasicTestRunner> { export async function createRestTestRunner(): Promise<BasicTestRunner> {
const host = await createRestTestHost(); const host = await createRestTestHost();
return createTestWrapper(host, { autoUsings: ["TypeSpec.Http", "TypeSpec.Rest"] }); return createTestWrapper(host, { autoUsings: ["TypeSpec.Http", "TypeSpec.Rest"] });

Просмотреть файл

@ -36,6 +36,7 @@
"dependencies": { "dependencies": {
"@typespec/versioning": "~0.40.0", "@typespec/versioning": "~0.40.0",
"@typespec/compiler": "~0.40.0", "@typespec/compiler": "~0.40.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0", "@typespec/rest": "~0.40.0",
"@typespec/openapi": "~0.40.0", "@typespec/openapi": "~0.40.0",
"@typespec/openapi3": "~0.40.0", "@typespec/openapi3": "~0.40.0",

Просмотреть файл

@ -23,7 +23,7 @@ import {
HttpOperationResponse, HttpOperationResponse,
HttpVerb, HttpVerb,
Visibility, Visibility,
} from "@typespec/rest/http"; } from "@typespec/http";
import { buildVersionProjections } from "@typespec/versioning"; import { buildVersionProjections } from "@typespec/versioning";
import assert from "assert"; import assert from "assert";

Просмотреть файл

@ -1,7 +1,6 @@
import "@typespec/rest"; import "@typespec/rest";
import "@typespec/openapi"; import "@typespec/openapi";
@autoRoute
@service({ @service({
title: "Pet Store Service", title: "Pet Store Service",
version: "2021-03-25", version: "2021-03-25",
@ -72,12 +71,14 @@ interface PetCheckups
interface PetInsurance extends SingletonResourceOperations<Insurance, Pet, PetStoreError> {} interface PetInsurance extends SingletonResourceOperations<Insurance, Pet, PetStoreError> {}
interface Toys extends ResourceRead<Toy, PetStoreError> { interface Toys extends ResourceRead<Toy, PetStoreError> {
@autoRoute
@listsResource(Toy) @listsResource(Toy)
list( list(
...ParentKeysOf<Toy>, ...ParentKeysOf<Toy>,
@query nameFilter: string @query nameFilter: string
): CollectionWithNextLink<Toy> | PetStoreError; ): CollectionWithNextLink<Toy> | PetStoreError;
} }
interface ToyInsurance extends SingletonResourceOperations<Insurance, Toy, PetStoreError> {} interface ToyInsurance extends SingletonResourceOperations<Insurance, Toy, PetStoreError> {}
interface Checkups interface Checkups

Просмотреть файл

@ -1,6 +1,8 @@
import "@typespec/http";
import "@typespec/rest"; import "@typespec/rest";
using TypeSpec.Http; using TypeSpec.Http;
using TypeSpec.Rest;
@service({ @service({
title: "Visibility sample", title: "Visibility sample",

Просмотреть файл

@ -13,10 +13,17 @@ await generateLibraryDocs(
join(repoRoot, "docs/compiler/reference") join(repoRoot, "docs/compiler/reference")
); );
// Http
await generateLibraryDocs(
join(repoRoot, "packages/http"),
["TypeSpec.Http"],
join(repoRoot, "docs/standard-library/http/reference")
);
// Rest // Rest
await generateLibraryDocs( await generateLibraryDocs(
join(repoRoot, "packages/rest"), join(repoRoot, "packages/rest"),
["TypeSpec.Http", "TypeSpec.Rest", "TypeSpec.Rest.Resource"], ["TypeSpec.Rest", "TypeSpec.Rest.Resource"],
join(repoRoot, "docs/standard-library/rest/reference") join(repoRoot, "docs/standard-library/rest/reference")
); );

Просмотреть файл

@ -33,6 +33,7 @@
"devDependencies": { "devDependencies": {
"@typespec/ref-doc": "^0.1.0", "@typespec/ref-doc": "^0.1.0",
"@typespec/spec": "0.1.0", "@typespec/spec": "0.1.0",
"@typespec/http": "~0.40.0",
"@typespec/rest": "~0.40.0", "@typespec/rest": "~0.40.0",
"@typespec/openapi": "~0.40.0", "@typespec/openapi": "~0.40.0",
"@docusaurus/module-type-aliases": "^2.2.0", "@docusaurus/module-type-aliases": "^2.2.0",

Просмотреть файл

@ -90,6 +90,12 @@
"reviewCategory": "production", "reviewCategory": "production",
"versionPolicyName": "typespec" "versionPolicyName": "typespec"
}, },
{
"packageName": "@typespec/http",
"projectFolder": "packages/http",
"reviewCategory": "production",
"versionPolicyName": "typespec"
},
{ {
"packageName": "@typespec/rest", "packageName": "@typespec/rest",
"projectFolder": "packages/rest", "projectFolder": "packages/rest",