This commit is contained in:
Mike Kistler 2021-08-27 10:25:58 -05:00 коммит произвёл GitHub
Родитель d28a30a7a9
Коммит 0b024d674c
9 изменённых файлов: 1635 добавлений и 4 удалений

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

@ -400,14 +400,14 @@ The first step in using a library is to install it via `npm`. You can get `npm`
If you haven't already intiialized your Cadl project's package.json file, now would be a good time to do so. The package.json file lets you track the dependencies your project depends on, and is a best practice to check in along with any Cadl files you create. Run `npm init` create your package.json file. If you haven't already intiialized your Cadl project's package.json file, now would be a good time to do so. The package.json file lets you track the dependencies your project depends on, and is a best practice to check in along with any Cadl files you create. Run `npm init` create your package.json file.
Then, in your Cadl project directory, type `npm install libraryName` to install a library. For example, to install the official Cadl REST API bindings and OpenAPI generator, you would type `npm install @cadl-lang/rest @azure-tools/cadl-autorest`. Then, in your Cadl project directory, type `npm install libraryName` to install a library. For example, to install the official Cadl REST API bindings and OpenAPI generator, you would type `npm install @cadl-lang/rest @cadl-lang/openapi3`.
Lastly, you need to import the libraries into your Cadl program. By convention, all external dependencies are imported in your `main.cadl` file, but can be in any Cadl file imported into your program. Importing the two libraries we installed above would look like this: Lastly, you need to import the libraries into your Cadl program. By convention, all external dependencies are imported in your `main.cadl` file, but can be in any Cadl file imported into your program. Importing the two libraries we installed above would look like this:
``` ```
// in main.cadl // in main.cadl
import "@cadl-lang/rest"; import "@cadl-lang/rest";
import "@azure-tools/cadl-autorest"; import "@cadl-lang/openapi3";
``` ```
#### Creating libraries #### Creating libraries
@ -420,9 +420,9 @@ The package.json file for an Cadl library requires one additional field: `cadlMa
With the language building blocks we've covered so far we're ready to author our first REST API. Cadl has an official REST API "binding" called `@cadl-lang/rest`. It's a set of Cadl declarations and decorators that describe REST APIs and can be used by code generators to generate OpenAPI descriptions, implementation code, and the like. With the language building blocks we've covered so far we're ready to author our first REST API. Cadl has an official REST API "binding" called `@cadl-lang/rest`. It's a set of Cadl declarations and decorators that describe REST APIs and can be used by code generators to generate OpenAPI descriptions, implementation code, and the like.
Cadl also has an official OpenAPI emitter called `@azure-tools/cadl-autorest` that consumes the REST API bindings and emits standard OpenAPI descriptions. This can then be fed in to any OpenAPI code generation pipeline. Cadl also has an official OpenAPI emitter called `@cadl-lang/openapi3` that consumes the REST API bindings and emits standard OpenAPI descriptions. This can then be fed in to any OpenAPI code generation pipeline.
The following examples assume you have imported both `@azure-tools/cadl-autorest` and `@cadl-lang/rest` somewhere in your Cadl program (though importing them in `main.cadl` is the standard convention). The following examples assume you have imported both `@cadl-lang/openapi3` and `@cadl-lang/rest` somewhere in your Cadl program (though importing them in `main.cadl` is the standard convention).
#### Service definition and metadata #### Service definition and metadata

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

@ -0,0 +1 @@
{}

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

@ -0,0 +1 @@
# Change Log - @cadl-lang/cadl-openapi3

21
packages/openapi3/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

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

@ -0,0 +1,46 @@
{
"name": "@cadl-lang/openapi3",
"version": "0.1.0",
"author": "Microsoft Corporation",
"description": "Cadl library for emitting OpenAPI 3.0 from the Cadl REST protocol binding",
"homepage": "https://github.com/Azure/adl",
"readme": "https://github.com/Azure/adl/blob/master/README.md",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/Azure/adl.git"
},
"bugs": {
"url": "https://github.com/Azure/adl/issues"
},
"keywords": [
"cadl"
],
"type": "module",
"main": "dist/src/openapi.js",
"cadlMain": "dist/src/openapi.js",
"engines": {
"node": ">=14.0.0"
},
"scripts": {
"build": "tsc -p .",
"watch": "tsc -p . --watch",
"test": "mocha --timeout 5000 --require source-map-support/register --ignore 'dist/test/manual/**/*.js' 'dist/test/**/*.js'",
"test-official": "mocha --forbid-only --timeout 5000 --require source-map-support/register --ignore 'dist/test/manual/**/*.js' 'dist/test/**/*.js'"
},
"files": [
"lib/*.cadl",
"dist/**",
"!dist/test/**"
],
"dependencies": {
"@cadl-lang/compiler": "0.19.0",
"@cadl-lang/rest": "0.6.0"
},
"devDependencies": {
"@types/mocha": "~7.0.2",
"@types/node": "~14.0.27",
"mocha": "~8.3.2",
"typescript": "~4.3.2"
}
}

Разница между файлами не показана из-за своего большого размера Загрузить разницу

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

@ -0,0 +1,410 @@
import { deepStrictEqual, ok, strictEqual } from "assert";
import { openApiFor } from "./testHost.js";
describe("openapi3: definitions", () => {
it("defines models", async () => {
const res = await oapiForModel(
"Foo",
`model Foo {
x: int32;
};`
);
ok(res.isRef);
deepStrictEqual(res.schemas.Foo, {
type: "object",
properties: {
x: { type: "integer", format: "int32" },
},
required: ["x"],
});
});
it("doesn't define anonymous or unconnected models", async () => {
const res = await oapiForModel(
"{ ... Foo }",
`model Foo {
x: int32;
};`
);
ok(!res.isRef);
strictEqual(Object.keys(res.schemas).length, 0);
deepStrictEqual(res.useSchema, {
type: "object",
properties: {
x: { type: "integer", format: "int32" },
},
required: ["x"],
"x-cadl-name": "(anonymous model)",
});
});
it("defines templated models", async () => {
const res = await oapiForModel(
"Foo<int32>",
`model Foo<T> {
x: T;
};`
);
ok(res.isRef);
ok(res.schemas.Foo_int32, "expected definition named Foo_int32");
deepStrictEqual(res.schemas.Foo_int32, {
type: "object",
properties: {
x: { type: "integer", format: "int32" },
},
required: ["x"],
});
});
it("defines templated models when template param is in a namespace", async () => {
const res = await oapiForModel(
"Foo<Test.M>",
`
namespace Test {
model M {}
}
model Foo<T> {
x: T;
};`
);
ok(res.isRef);
ok(res.schemas["Foo_Test.M"], "expected definition named Foo_Test.M");
deepStrictEqual(res.schemas["Foo_Test.M"], {
type: "object",
properties: {
x: { $ref: "#/components/schemas/Test.M" },
},
required: ["x"],
});
});
it("defines models extended from models", async () => {
const res = await oapiForModel(
"Bar",
`
model Foo {
y: int32;
};
model Bar extends Foo {}`
);
ok(res.isRef);
ok(res.schemas.Foo, "expected definition named Foo");
ok(res.schemas.Bar, "expected definition named Bar");
deepStrictEqual(res.schemas.Bar, {
type: "object",
properties: {},
allOf: [{ $ref: "#/components/schemas/Foo" }],
});
deepStrictEqual(res.schemas.Foo, {
type: "object",
properties: { y: { type: "integer", format: "int32" } },
required: ["y"],
});
});
it("defines models with properties extended from models", async () => {
const res = await oapiForModel(
"Bar",
`
model Foo {
y: int32;
};
model Bar extends Foo {
x: int32;
}`
);
ok(res.isRef);
ok(res.schemas.Foo, "expected definition named Foo");
ok(res.schemas.Bar, "expected definition named Bar");
deepStrictEqual(res.schemas.Bar, {
type: "object",
properties: { x: { type: "integer", format: "int32" } },
allOf: [{ $ref: "#/components/schemas/Foo" }],
required: ["x"],
});
deepStrictEqual(res.schemas.Foo, {
type: "object",
properties: { y: { type: "integer", format: "int32" } },
required: ["y"],
});
});
it("defines models extended from templated models", async () => {
const res = await oapiForModel(
"Bar",
`
model Foo<T> {
y: T;
};
model Bar extends Foo<int32> {}`
);
ok(res.isRef);
ok(res.schemas["Foo_int32"] === undefined, "no definition named Foo_int32");
ok(res.schemas.Bar, "expected definition named Bar");
deepStrictEqual(res.schemas.Bar, {
type: "object",
properties: { y: { type: "integer", format: "int32" } },
required: ["y"],
});
});
it("defines models with properties extended from templated models", async () => {
const res = await oapiForModel(
"Bar",
`
model Foo<T> {
y: T;
};
model Bar extends Foo<int32> {
x: int32
}`
);
ok(res.isRef);
ok(res.schemas.Foo_int32, "expected definition named Foo_int32");
ok(res.schemas.Bar, "expected definition named Bar");
deepStrictEqual(res.schemas.Bar, {
type: "object",
properties: { x: { type: "integer", format: "int32" } },
allOf: [{ $ref: "#/components/schemas/Foo_int32" }],
required: ["x"],
});
deepStrictEqual(res.schemas.Foo_int32, {
type: "object",
properties: { y: { type: "integer", format: "int32" } },
required: ["y"],
});
});
it("defines templated models with properties extended from templated models", async () => {
const res = await oapiForModel(
"Bar<int32>",
`
model Foo<T> {
y: T;
};
model Bar<T> extends Foo<T> {
x: T
}`
);
ok(res.isRef);
ok(res.schemas.Foo_int32, "expected definition named Foo_int32");
ok(res.schemas.Bar_int32, "expected definition named Bar_int32");
deepStrictEqual(res.schemas.Bar_int32, {
type: "object",
properties: { x: { type: "integer", format: "int32" } },
allOf: [{ $ref: "#/components/schemas/Foo_int32" }],
required: ["x"],
});
deepStrictEqual(res.schemas.Foo_int32, {
type: "object",
properties: { y: { type: "integer", format: "int32" } },
required: ["y"],
});
});
it("defines models with no properties extended", async () => {
const res = await oapiForModel(
"Bar",
`
model Foo {};
model Bar extends Foo {};`
);
ok(res.isRef);
ok(res.schemas.Foo, "expected definition named Foo");
ok(res.schemas.Bar, "expected definition named Bar");
deepStrictEqual(res.schemas.Bar, {
type: "object",
properties: {},
allOf: [{ $ref: "#/components/schemas/Foo" }],
});
deepStrictEqual(res.schemas.Foo, {
type: "object",
properties: {},
});
});
it("defines models with no properties extended twice", async () => {
const res = await oapiForModel(
"Baz",
`
model Foo { x: int32 };
model Bar extends Foo {};
model Baz extends Bar {};`
);
ok(res.isRef);
ok(res.schemas.Foo, "expected definition named Foo");
ok(res.schemas.Bar, "expected definition named Bar");
ok(res.schemas.Baz, "expected definition named Baz");
deepStrictEqual(res.schemas.Baz, {
type: "object",
properties: {},
allOf: [{ $ref: "#/components/schemas/Bar" }],
});
deepStrictEqual(res.schemas.Bar, {
type: "object",
properties: {},
allOf: [{ $ref: "#/components/schemas/Foo" }],
});
deepStrictEqual(res.schemas.Foo, {
type: "object",
properties: {
x: {
format: "int32",
type: "integer",
},
},
required: ["x"],
});
});
it("defines models extended from primitives", async () => {
const res = await oapiForModel(
"Pet",
`
model shortString extends string {}
model Pet { name: shortString };
`
);
ok(res.isRef);
ok(res.schemas.shortString, "expected definition named shortString");
ok(res.schemas.Pet, "expected definition named Pet");
deepStrictEqual(res.schemas.shortString, {
type: "string",
});
});
it("defines models extended from primitives with attrs", async () => {
const res = await oapiForModel(
"Pet",
`
@maxLength(10) @minLength(10)
model shortString extends string {}
model Pet { name: shortString };
`
);
ok(res.isRef);
ok(res.schemas.shortString, "expected definition named shortString");
ok(res.schemas.Pet, "expected definition named Pet");
deepStrictEqual(res.schemas.shortString, {
type: "string",
minLength: 10,
maxLength: 10,
});
});
it("defines models extended from primitives with new attrs", async () => {
const res = await oapiForModel(
"Pet",
`
@maxLength(10)
model shortString extends string {}
@minLength(1)
model shortButNotEmptyString extends shortString {};
model Pet { name: shortButNotEmptyString, breed: shortString };
`
);
ok(res.isRef);
ok(res.schemas.shortString, "expected definition named shortString");
ok(res.schemas.shortButNotEmptyString, "expected definition named shortButNotEmptyString");
ok(res.schemas.Pet, "expected definition named Pet");
deepStrictEqual(res.schemas.shortString, {
type: "string",
maxLength: 10,
});
deepStrictEqual(res.schemas.shortButNotEmptyString, {
type: "string",
minLength: 1,
maxLength: 10,
});
});
});
describe("openapi3: primitives", () => {
const cases = [
["int32", { type: "integer", format: "int32" }],
["int64", { type: "integer", format: "int64" }],
["float32", { type: "number", format: "float" }],
["float64", { type: "number", format: "double" }],
["string", { type: "string" }],
["boolean", { type: "boolean" }],
["plainDate", { type: "string", format: "date" }],
["zonedDateTime", { type: "string", format: "date-time" }],
["plainTime", { type: "string", format: "time" }],
];
for (const test of cases) {
it("knows schema for " + test[0], async () => {
const res = await oapiForModel(
"Pet",
`
model Pet { name: ${test[0]} };
`
);
const schema = res.schemas.Pet.properties.name;
deepStrictEqual(schema, test[1]);
});
}
});
describe("openapi3: literals", () => {
const cases = [
["1", { type: "number", enum: [1] }],
['"hello"', { type: "string", enum: ["hello"] }],
["false", { type: "boolean", enum: [false] }],
["true", { type: "boolean", enum: [true] }],
];
for (const test of cases) {
it("knows schema for " + test[0], async () => {
const res = await oapiForModel(
"Pet",
`
model Pet { name: ${test[0]} };
`
);
const schema = res.schemas.Pet.properties.name;
deepStrictEqual(schema, test[1]);
});
}
});
async function oapiForModel(name: string, modelDef: string) {
const oapi = await openApiFor(`
${modelDef};
@resource("/")
namespace root {
op read(): ${name};
}
`);
const useSchema = oapi.paths["/"].get.responses[200].content["application/json"].schema;
return {
isRef: !!useSchema.$ref,
useSchema,
schemas: oapi.components.schemas || {},
};
}

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

@ -0,0 +1,42 @@
import { createTestHost } from "@cadl-lang/compiler/dist/test/test-host.js";
import { resolve } from "path";
import { fileURLToPath } from "url";
export async function createOpenAPITestHost() {
const host = await createTestHost();
const root = resolve(fileURLToPath(import.meta.url), "../../../");
// load rest
await host.addRealCadlFile(
"./node_modules/rest/package.json",
resolve(root, "../rest/package.json")
);
await host.addRealCadlFile(
"./node_modules/rest/lib/rest.cadl",
resolve(root, "../rest/lib/rest.cadl")
);
await host.addRealJsFile(
"./node_modules/rest/dist/rest.js",
resolve(root, "../rest/dist/rest.js")
);
// load openapi
await host.addRealCadlFile(
"./node_modules/openapi3/package.json",
resolve(root, "../openapi3/package.json")
);
await host.addRealJsFile(
"./node_modules/openapi3/dist/src/openapi.js",
resolve(root, "../openapi3/dist/src/openapi.js")
);
return host;
}
export async function openApiFor(code: string) {
const host = await createOpenAPITestHost();
const outPath = resolve("/openapi.json");
host.addCadlFile("./main.cadl", `import "rest"; import "openapi3";${code}`);
await host.compile("./main.cadl", { noEmit: false, swaggerOutputFile: outPath });
return JSON.parse(host.fs.get(outPath)!);
}

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

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