Fix doc for route and move autoRoute to rest library

This commit is contained in:
Mike Kistler 2022-09-03 17:50:58 -05:00
Родитель 9b087aebc8
Коммит 840867dd83
8 изменённых файлов: 105 добавлений и 60 удалений

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

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cadl-lang/rest",
"comment": "Fix doc for route and move autoRoute to rest library",
"type": "minor"
}
],
"packageName": "@cadl-lang/rest"
}

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

@ -296,9 +296,9 @@ In Cadl this information is specified with [decorators on the namespace][cadl-se
| OpenAPI `info` field | Cadl decorator | Notes |
| -------------------- | ----------------- | ------------------------ |
| `title` | `@serviceTitle` | |
| `version` | `@serviceVersion` | |
| `description` | | Not currently supported. |
| `title` | `@serviceTitle` | Cadl built-in decorator |
| `version` | `@serviceVersion` | Cadl built-in decorator |
| `description` | `@doc` | Cadl built-in decorator |
| `license` | | Not currently supported. |
| `contact` | | Not currently supported. |

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

@ -75,21 +75,22 @@ See [Rest section in the tutorial](https://github.com/microsoft/cadl/blob/main/d
The `@cadl-lang/rest` library defines the following decorators in `Cadl.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. |
| @useAuth | namespace | Configure the service authentication. |
| 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
@ -111,7 +112,6 @@ The `@cadl-lang/rest` library defines the following decorators in `Cadl.Rest` na
| @segment | model properties, operation parameters | Syntax:<br> `@segment(segmentString)` <br><br>Note:<br>`@segment` defines the preceding path segment for a `@path` parameter in auto-generated routes. The first argument should be a string that will be inserted into the operation route before the path parameter's name field. For exmaple: <br> `op getUser(@path @segment("users") userId: string): User` <br> will produce the route `/users/{userId}`. |
| @segmentOf | models | Syntax:<br> `@segment(segmentString)` <br><br>Note:<br>`@segmentOf` returns the URL segment of a given model if it has `@segment` and `@key` decorator. |
| @segmentSeparator | model properties, operation parameters, or operations | Syntax:<br> `@segmentSeparator(separatorString)` <br><br>Note:<br> `@segmentSeparator` defines the separator string that is inserted between the target's `@segment` and the preceding route path in auto-generated routes. <br> The first argument should be a string that will be inserted into the operation route before the target's `@segment` value. Can be a string of any length. Defaults to `/`. |
| @route | operations, namespaces, interfaces | Syntax:<br> `@route(routeString)` <br><br>Note:<br> `@route` defines the relative route URI for the target operation The first 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. |
| @autoRoute | operations | Syntax:<br> `@autoRoute()` <br><br>Note:<br>`@autoRoute` enables automatic route generation for an operation, namespace, or interface. <br> 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 auto-generated routes. |
## Interfaces

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

@ -23,6 +23,7 @@ import {
getResourceOperation,
getSegment,
getSegmentSeparator,
isAutoRoute,
} from "../rest.js";
import { extractParamsFromPath } from "../utils.js";
import {
@ -597,42 +598,3 @@ const resourceOperationToVerb: any = {
delete: "delete",
list: "get",
};
const autoRouteKey = createStateSymbol("autoRoute");
/**
* `@autoRoute` enables automatic route generation for an operation, namespace, or interface.
*
* 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
* auto-generated routes.
*/
export function $autoRoute(context: DecoratorContext, entity: Type) {
if (
!validateDecoratorTarget(context, entity, "@autoRoute", ["Namespace", "Interface", "Operation"])
) {
return;
}
context.program.stateSet(autoRouteKey).add(entity);
}
export function isAutoRoute(program: Program, target: Namespace | Interface | Operation): boolean {
// 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;
}

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

@ -3,6 +3,7 @@ import {
createDecoratorDefinition,
DecoratorContext,
DecoratorValidator,
Interface,
Model,
ModelProperty,
Namespace,
@ -158,6 +159,58 @@ export function getDiscriminator(program: Program, entity: Type): Discriminator
return undefined;
}
// ----------------- @autoRoute -----------------
const autoRouteDecorator = createDecoratorDefinition({
name: "@autoRoute",
target: ["Namespace", "Interface", "Operation"],
args: [],
} as const);
const autoRouteKey = createStateSymbol("autoRoute");
/**
* `@autoRoute` enables automatic route generation for an operation, namespace, or interface.
*
* 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
* auto-generated routes.
*/
export function $autoRoute(
context: DecoratorContext,
entity: Namespace | Interface | Operation,
...args: readonly []
) {
if (!autoRouteDecorator.validate(context, entity, args)) {
return;
}
context.program.stateSet(autoRouteKey).add(entity);
}
export function isAutoRoute(program: Program, target: Namespace | Interface | Operation): boolean {
// 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;
}
// ------------------ @segment ------------------
const segmentDecorator = createDecoratorDefinition({
name: "@segment",
target: ["Model", "ModelProperty", "Operation"],

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

@ -17,13 +17,13 @@ import {
isQueryParam,
isStatusCode,
} from "../src/http/decorators.js";
import { createRestTestRunner } from "./test-host.js";
import { createHttpTestRunner } from "./test-host.js";
describe("rest: http decorators", () => {
let runner: BasicTestRunner;
beforeEach(async () => {
runner = await createRestTestRunner();
runner = await createHttpTestRunner();
});
describe("emit diagnostic if passing arguments to verb decorators", () => {

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

@ -409,6 +409,17 @@ describe("rest: routes", () => {
strictEqual(diagnostics[1].message, `Duplicate operation "get2" routed at "get /test".`);
});
it("emit diagnostic if passing arguments to autoroute decorators", async () => {
const [_, diagnostics] = await compileOperations(`
@autoRoute("/test") op test(): string;
`);
expectDiagnostics(diagnostics, {
code: "invalid-argument-count",
message: "Expected 0 arguments, but got 1.",
});
});
describe("operation parameters", () => {
it("emit diagnostic for parameters with multiple http request annotations", async () => {
const [_, diagnostics] = await compileOperations(`

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

@ -20,6 +20,15 @@ export async function createRestTestHost(): Promise<TestHost> {
libraries: [RestTestLibrary],
});
}
export async function createHttpTestRunner(): Promise<BasicTestRunner> {
const host = await createRestTestHost();
return createTestWrapper(
host,
(code) =>
`import "@cadl-lang/rest"; using Cadl.Http;
${code}`
);
}
export async function createRestTestRunner(): Promise<BasicTestRunner> {
const host = await createRestTestHost();