This commit is contained in:
Timothee Guerin 2022-09-07 15:46:42 -04:00 коммит произвёл GitHub
Родитель 840867dd83
Коммит 4d0242c3d2
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
5 изменённых файлов: 57 добавлений и 118 удалений

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

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@cadl-lang/rest",
"comment": "**Deprecation**: Mark `@produces` and `@consumes` as deprecated",
"type": "minor"
}
],
"packageName": "@cadl-lang/rest"
}

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

@ -824,8 +824,6 @@ A definition for a service is the namespace that contains all the operations for
- @serviceTitle - the title of the service
- @serviceVersion - the version of the service. Can be any string, but later version should lexicographically sort after earlier versions
- @server - the host of the service. Can accept parameters.
- @produces - the content types the service may produce
- @consumes - the content types that may be sent to the service
Here's an example that uses these to define a Pet Store service:
@ -834,8 +832,6 @@ Here's an example that uses these to define a Pet Store service:
@serviceVersion("2021-03-25")
@server("https://example.com", "Single server endpoint")
@doc("This is a sample server Petstore server.")
@Cadl.Rest.produces("application/json", "image/png")
@Cadl.Rest.consumes("application/json")
namespace PetStore;
```

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

@ -98,8 +98,6 @@ The `@cadl-lang/rest` library defines the following decorators in `Cadl.Rest` na
| Declarator | Scope | Syntax |
| ------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| @produces | namespace, operations | Syntax:<br> `@produces(mimetypeString)` <br><br>Note:<br> The `@produces` decorator is used to specify the MIME media types or representations a resource can produce and send back to the client. |
| @consumes | namespace, operations | Syntax:<br> `@consumes(mimetypeString)` <br><br>Note:<br> The `@consumes` decorator is used to specify which MIME media types of representations a resource can accept, or consume, from the client. |
| @discriminator | models | Syntax:<br> `@discriminator(kindString)` <br><br>Note:<br> `@discriminator` allows defining polymorphic models to be used by API as parameters and return types. In many strongly typed languages, they are expressed as inheritance. |
| @resource | Model | Syntax:<br> `@resource(collectionName)` <br><br>Note:<br> This decorator is to used to mark a model as a resource type with a name for the type's collection. |
| @readsResource | operations | Syntax:<br> `@readsResource(modelType)` <br><br>Note:<br> This decorator is to used to signal the operation that is the Read operation for a particular resource. |
@ -174,6 +172,27 @@ interface WidgetService
| 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. |
## 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
```cadl
op getPng(): {
@header contentType: "application/png";
@body _: bytes;
};
```
Example: expect `application/png` byte body
```cadl
op getPng(@header contentType: "application/png", @body _: bytes): void;
```
## See also
- [HTTP example](https://cadlplayground.z22.web.core.windows.net/?c=aW1wb3J0ICJAY2FkbC1sYW5nL3Jlc3QiOwoKQHNlcnZpY2VUaXRsZSgiV2lkZ2V0IFPGFSIpCm5hbWVzcGFjZSBEZW1vxxg7CnVzaW5nIENhZGwuSHR0cDsKCm1vZGVsIMdAewogIEBrZXkgaWQ6IHN0cmluZzsKICB3ZWlnaHQ6IGludDMyxBFjb2xvcjogInJlZCIgfCAiYmx1ZSI7Cn0KCkBlcnJvcsdWRcQMxVVjb2Rly0BtZXNzYWdlymR9CgppbnRlcmbkALLmAI3nALTFP0DkAJ1saXN0KCk6xx9bXSB8xmHEUUByb3V0ZSgid8Uccy97aWR9IinGOHJlYWQoQHBhdGjrANfJSM1GcG9zdCBjcmVhdGUoQGJvZHkgxAXIK9Y0x3pjdXN0b21HZXTId8kR6gC0yjh9Cg%3D%3D):

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

@ -9,6 +9,7 @@ import {
Namespace,
Operation,
Program,
reportDeprecated,
setCadlNamespace,
Type,
Union,
@ -27,7 +28,15 @@ const producesDecorator = createDecoratorDefinition({
},
} as const);
/**
* @deprecated Use return type `@header contentType` property instead
*/
export function $produces(context: DecoratorContext, entity: Namespace, ...contentTypes: string[]) {
reportDeprecated(
context.program,
"@produces is deprecated. It has no effect. Use @header contentType: <ContentType> instead in operation return type.",
context.decoratorTarget
);
if (!producesDecorator.validate(context, entity, contentTypes)) {
return;
}
@ -36,6 +45,9 @@ export function $produces(context: DecoratorContext, entity: Namespace, ...conte
context.program.stateMap(producesTypesKey).set(entity, values.concat(contentTypes));
}
/**
* @deprecated Check return type `@header contentType` property instead
*/
export function getProduces(program: Program, entity: Type): string[] {
return program.stateMap(producesTypesKey).get(entity) || [];
}
@ -49,7 +61,16 @@ const consumeDefinition = createDecoratorDefinition({
kind: "String",
},
} as const);
/**
* @deprecated Use parameters `@header contentType` instead
*/
export function $consumes(context: DecoratorContext, entity: Namespace, ...contentTypes: string[]) {
reportDeprecated(
context.program,
"@produces is deprecated. It has no effect. Use @header contentType: <ContentType> instead in operation parameters.",
context.decoratorTarget
);
if (!consumeDefinition.validate(context, entity, contentTypes)) {
return;
}
@ -58,6 +79,9 @@ export function $consumes(context: DecoratorContext, entity: Namespace, ...conte
context.program.stateMap(consumesTypesKey).set(entity, values.concat(contentTypes));
}
/**
* @deprecated Check parameters `@header contentType` instead
*/
export function getConsumes(program: Program, entity: Type): string[] {
return program.stateMap(consumesTypesKey).get(entity) || [];
}

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

@ -1,7 +1,7 @@
import { Model } from "@cadl-lang/compiler";
import { BasicTestRunner, expectDiagnostics } from "@cadl-lang/compiler/testing";
import { deepStrictEqual, ok, strictEqual } from "assert";
import { getConsumes, getProduces, getResourceLocationType } from "../src/rest.js";
import { ok, strictEqual } from "assert";
import { getResourceLocationType } from "../src/rest.js";
import { createRestTestRunner } from "./test-host.js";
describe("rest: http decorators", () => {
@ -11,116 +11,6 @@ describe("rest: http decorators", () => {
runner = await createRestTestRunner();
});
describe("@consumes", () => {
it("emit diagnostics when @consumes is not used on namespace", async () => {
const diagnostics = await runner.diagnose(`
@consumes op test(): string;
@consumes model Foo {}
`);
expectDiagnostics(diagnostics, [
{
code: "decorator-wrong-target",
message: "Cannot apply @consumes decorator to Operation",
},
{
code: "decorator-wrong-target",
message: "Cannot apply @consumes decorator to Model",
},
]);
});
it("emit diagnostics when parameter is not a string", async () => {
const diagnostics = await runner.diagnose(`
@consumes(123)
namespace Foo {}
`);
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' of type 'Number' is not assignable to parameter of type 'String'",
});
});
it("allows a single content type", async () => {
const { Foo } = await runner.compile(`
@consumes("application/json")
@test namespace Foo {}
`);
deepStrictEqual(getConsumes(runner.program, Foo), ["application/json"]);
});
it("allows a multiple content type", async () => {
const { Foo } = await runner.compile(`
@consumes("application/json", "application/xml", "application/yaml")
@test namespace Foo {}
`);
deepStrictEqual(getConsumes(runner.program, Foo), [
"application/json",
"application/xml",
"application/yaml",
]);
});
});
describe("@produces", () => {
it("emit diagnostics when @produces is not used on namespace", async () => {
const diagnostics = await runner.diagnose(`
@produces op test(): string;
@produces model Foo {}
`);
expectDiagnostics(diagnostics, [
{
code: "decorator-wrong-target",
message: "Cannot apply @produces decorator to Operation",
},
{
code: "decorator-wrong-target",
message: "Cannot apply @produces decorator to Model",
},
]);
});
it("emit diagnostics when parameter is not a string", async () => {
const diagnostics = await runner.diagnose(`
@produces(123)
namespace Foo {}
`);
expectDiagnostics(diagnostics, {
code: "invalid-argument",
message: "Argument '123' of type 'Number' is not assignable to parameter of type 'String'",
});
});
it("allows a single content type", async () => {
const { Foo } = await runner.compile(`
@produces("application/json")
@test namespace Foo {}
`);
deepStrictEqual(getProduces(runner.program, Foo), ["application/json"]);
});
it("allows a multiple content type", async () => {
const { Foo } = await runner.compile(`
@produces("application/json", "application/xml", "application/yaml")
@test namespace Foo {}
`);
deepStrictEqual(getProduces(runner.program, Foo), [
"application/json",
"application/xml",
"application/yaml",
]);
});
});
describe("@resourceLocation", () => {
it("emit diagnostic when used on non-model", async () => {
const diagnostics = await runner.diagnose(`