@mittwald/api-code-generator 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -0
- package/bin/cli.js +8 -0
- package/dist/esm/commands/generate/index.js +70 -0
- package/dist/esm/commands/validate/index.js +21 -0
- package/dist/esm/generation/asyncStringJoin.js +4 -0
- package/dist/esm/generation/asyncStringJoin.test.js +5 -0
- package/dist/esm/generation/compileJsonSchema.js +10 -0
- package/dist/esm/generation/format.js +17 -0
- package/dist/esm/generation/model/CodeGenerationModel.js +43 -0
- package/dist/esm/generation/model/components/Components.js +61 -0
- package/dist/esm/generation/model/components/Parameters.js +30 -0
- package/dist/esm/generation/model/components/RequestBodies.js +25 -0
- package/dist/esm/generation/model/components/Response.js +27 -0
- package/dist/esm/generation/model/components/ResponseContent.js +12 -0
- package/dist/esm/generation/model/components/Responses.js +29 -0
- package/dist/esm/generation/model/components/Schemas.js +25 -0
- package/dist/esm/generation/model/components/SecurityScheme.js +21 -0
- package/dist/esm/generation/model/components/SecuritySchemes.js +43 -0
- package/dist/esm/generation/model/global/JSONSchema.js +44 -0
- package/dist/esm/generation/model/global/Name.js +20 -0
- package/dist/esm/generation/model/global/Name.test.js +10 -0
- package/dist/esm/generation/model/paths/Path.js +35 -0
- package/dist/esm/generation/model/paths/Paths.js +192 -0
- package/dist/esm/generation/model/paths/operation/Operation.js +108 -0
- package/dist/esm/generation/model/paths/operation/RequestParameters.js +98 -0
- package/dist/esm/generation/model/paths/operation/responses/Response.js +25 -0
- package/dist/esm/generation/model/paths/operation/responses/ResponseContent.js +31 -0
- package/dist/esm/generation/model/paths/operation/responses/ResponseContentTypes.js +47 -0
- package/dist/esm/generation/model/paths/operation/responses/Responses.js +31 -0
- package/dist/esm/generation/model/tags/Tag.js +12 -0
- package/dist/esm/generation/prepareTypeScriptOutput.js +10 -0
- package/dist/esm/generation/refs/assertNoRefs.js +6 -0
- package/dist/esm/generation/refs/componentRefsToCustomTypes.js +33 -0
- package/dist/esm/generation/refs/componentRefsToCustomTypes.test.js +44 -0
- package/dist/esm/generation/refs/extractComponentRefName.js +8 -0
- package/dist/esm/generation/refs/isRef.js +3 -0
- package/dist/esm/generation/refs/refNameToTSName.js +3 -0
- package/dist/esm/generation/refs/refNameToTSName.test.js +9 -0
- package/dist/esm/generation/refs/splitRefNamespaces.js +1 -0
- package/dist/esm/generation/refs/splitRefNamespaces.test.js +10 -0
- package/dist/esm/generation/tsNamespaceName.js +2 -0
- package/dist/esm/generation/tsTypeName.js +6 -0
- package/dist/esm/generation/tsTypeName.test.js +12 -0
- package/dist/esm/index.js +7 -0
- package/dist/esm/lib/makeError.js +7 -0
- package/dist/esm/lib/relativePath.js +3 -0
- package/dist/esm/lib/writeIfChangedAsync.js +8 -0
- package/dist/esm/loading/UniversalContentLoader.js +65 -0
- package/dist/esm/loading/UniversalContentLoader.test.js +40 -0
- package/dist/esm/loading/UniversalFileLoader.js +42 -0
- package/dist/esm/loading/types.js +1 -0
- package/dist/esm/openapi/OpenAPISchemaValidationError.js +17 -0
- package/dist/esm/openapi/OpenApiSpec.js +44 -0
- package/dist/types/commands/generate/index.d.ts +18 -0
- package/dist/types/commands/validate/index.d.ts +8 -0
- package/dist/types/generation/asyncStringJoin.d.ts +1 -0
- package/dist/types/generation/asyncStringJoin.test.d.ts +1 -0
- package/dist/types/generation/compileJsonSchema.d.ts +2 -0
- package/dist/types/generation/format.d.ts +1 -0
- package/dist/types/generation/model/CodeGenerationModel.d.ts +19 -0
- package/dist/types/generation/model/components/Components.d.ts +24 -0
- package/dist/types/generation/model/components/Parameters.d.ts +11 -0
- package/dist/types/generation/model/components/RequestBodies.d.ts +13 -0
- package/dist/types/generation/model/components/Response.d.ts +12 -0
- package/dist/types/generation/model/components/ResponseContent.d.ts +10 -0
- package/dist/types/generation/model/components/Responses.d.ts +13 -0
- package/dist/types/generation/model/components/Schemas.d.ts +13 -0
- package/dist/types/generation/model/components/SecurityScheme.d.ts +10 -0
- package/dist/types/generation/model/components/SecuritySchemes.d.ts +14 -0
- package/dist/types/generation/model/global/JSONSchema.d.ts +13 -0
- package/dist/types/generation/model/global/Name.d.ts +8 -0
- package/dist/types/generation/model/global/Name.test.d.ts +1 -0
- package/dist/types/generation/model/paths/Path.d.ts +13 -0
- package/dist/types/generation/model/paths/Paths.d.ts +25 -0
- package/dist/types/generation/model/paths/operation/Operation.d.ts +24 -0
- package/dist/types/generation/model/paths/operation/RequestParameters.d.ts +17 -0
- package/dist/types/generation/model/paths/operation/responses/Response.d.ts +16 -0
- package/dist/types/generation/model/paths/operation/responses/ResponseContent.d.ts +12 -0
- package/dist/types/generation/model/paths/operation/responses/ResponseContentTypes.d.ts +16 -0
- package/dist/types/generation/model/paths/operation/responses/Responses.d.ts +14 -0
- package/dist/types/generation/model/tags/Tag.d.ts +8 -0
- package/dist/types/generation/prepareTypeScriptOutput.d.ts +1 -0
- package/dist/types/generation/refs/assertNoRefs.d.ts +2 -0
- package/dist/types/generation/refs/componentRefsToCustomTypes.d.ts +1 -0
- package/dist/types/generation/refs/componentRefsToCustomTypes.test.d.ts +1 -0
- package/dist/types/generation/refs/extractComponentRefName.d.ts +2 -0
- package/dist/types/generation/refs/isRef.d.ts +2 -0
- package/dist/types/generation/refs/refNameToTSName.d.ts +1 -0
- package/dist/types/generation/refs/refNameToTSName.test.d.ts +1 -0
- package/dist/types/generation/refs/splitRefNamespaces.d.ts +1 -0
- package/dist/types/generation/refs/splitRefNamespaces.test.d.ts +1 -0
- package/dist/types/generation/tsNamespaceName.d.ts +1 -0
- package/dist/types/generation/tsTypeName.d.ts +1 -0
- package/dist/types/generation/tsTypeName.test.d.ts +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/lib/makeError.d.ts +1 -0
- package/dist/types/lib/relativePath.d.ts +1 -0
- package/dist/types/lib/writeIfChangedAsync.d.ts +1 -0
- package/dist/types/loading/UniversalContentLoader.d.ts +10 -0
- package/dist/types/loading/UniversalContentLoader.test.d.ts +1 -0
- package/dist/types/loading/UniversalFileLoader.d.ts +9 -0
- package/dist/types/loading/types.d.ts +4 -0
- package/dist/types/openapi/OpenAPISchemaValidationError.d.ts +7 -0
- package/dist/types/openapi/OpenApiSpec.d.ts +11 -0
- package/package.json +86 -0
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
Common code base used by `@mittwald/api-client-*` package.
|
|
2
|
+
|
|
3
|
+
## CLI
|
|
4
|
+
|
|
5
|
+
Use the CLI commands provided by this package to build your API client and
|
|
6
|
+
validate the OpenAPI-Spec:
|
|
7
|
+
|
|
8
|
+
```shell
|
|
9
|
+
acg validate spec/openapi.json
|
|
10
|
+
acg generate --name MittwaldAPIV2 spec/openapi.json src/generated --optionalHeader x-access-token
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## TypeScript API
|
|
14
|
+
|
|
15
|
+
If you need to generate the client in code, you can use the TypeScript API
|
|
16
|
+
demonstrated in this template:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
import * as path from "path";
|
|
20
|
+
import { UniversalContentLoader } from "@mittwald/api-code-generator/js";
|
|
21
|
+
import { UniversalFileLoader } from "@mittwald/api-code-generator/js";
|
|
22
|
+
import { OpenApiSpec } from "@mittwald/api-code-generator/js";
|
|
23
|
+
import { CodeGenerationModel } from "@mittwald/api-code-generator/js";
|
|
24
|
+
import { prepareTypeScriptOutput } from "@mittwald/api-code-generator/js";
|
|
25
|
+
import { writeIfChangedAsync } from "@mittwald/api-code-generator/js";
|
|
26
|
+
|
|
27
|
+
const name = "MittwaldAPIV2";
|
|
28
|
+
const inout = `${process.cwd()}/spec/openapi.json`;
|
|
29
|
+
const output = `${process.cwd()}/src/generated`;
|
|
30
|
+
|
|
31
|
+
// Loading OpenAPI spec
|
|
32
|
+
const loader = new UniversalContentLoader(new UniversalFileLoader(input));
|
|
33
|
+
const openApiDoc = await loader.load();
|
|
34
|
+
|
|
35
|
+
// Parsing OpenAPI spec
|
|
36
|
+
const spec = await OpenApiSpec.parse(openApiDoc, {
|
|
37
|
+
skipValidation: flags.skipValidation,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Building transformation model
|
|
41
|
+
const model = CodeGenerationModel.fromDoc(name, spec.document);
|
|
42
|
+
|
|
43
|
+
// Generating descriptors
|
|
44
|
+
const descriptorsFileContent = model.paths.compileDescriptors();
|
|
45
|
+
await writeIfChangedAsync(
|
|
46
|
+
path.join(output, "descriptors.ts"),
|
|
47
|
+
await prepareTypeScriptOutput(descriptorsFileContent),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Generating types
|
|
51
|
+
const typesFileContent = await model.compileTypes({
|
|
52
|
+
rootNamespace: flags.name,
|
|
53
|
+
optionalHeaders: ["x-access-token"],
|
|
54
|
+
});
|
|
55
|
+
await writeIfChangedAsync(
|
|
56
|
+
path.join(output, "types.ts"),
|
|
57
|
+
await prepareTypeScriptOutput(typesFileContent),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Generating client
|
|
61
|
+
const clientFileContent = model.paths.compileClient();
|
|
62
|
+
await writeIfChangedAsync(
|
|
63
|
+
path.join(output, "client.ts"),
|
|
64
|
+
await prepareTypeScriptOutput(clientFileContent),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
// if needed: Generating React client
|
|
68
|
+
const reactClientFileContent = model.paths.compileReactClient();
|
|
69
|
+
await writeIfChangedAsync(
|
|
70
|
+
path.join(output, "client-react.ts"),
|
|
71
|
+
await prepareTypeScriptOutput(reactClientFileContent),
|
|
72
|
+
);
|
|
73
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { Args, Command, Flags, ux } from "@oclif/core";
|
|
2
|
+
import { UniversalContentLoader } from "../../loading/UniversalContentLoader.js";
|
|
3
|
+
import { OpenApiSpec } from "../../openapi/OpenApiSpec.js";
|
|
4
|
+
import { CodeGenerationModel } from "../../generation/model/CodeGenerationModel.js";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { prepareTypeScriptOutput } from "../../generation/prepareTypeScriptOutput.js";
|
|
7
|
+
import { UniversalFileLoader } from "../../loading/UniversalFileLoader.js";
|
|
8
|
+
import { writeIfChangedAsync } from "../../lib/writeIfChangedAsync.js";
|
|
9
|
+
export default class Generate extends Command {
|
|
10
|
+
static description = "Generate code from the provided OpenAPI spec.";
|
|
11
|
+
static flags = {
|
|
12
|
+
skipValidation: Flags.boolean({
|
|
13
|
+
required: false,
|
|
14
|
+
default: false,
|
|
15
|
+
description: "Skip validation of input OpenAPI spec",
|
|
16
|
+
}),
|
|
17
|
+
name: Flags.string({
|
|
18
|
+
required: false,
|
|
19
|
+
default: "Api",
|
|
20
|
+
}),
|
|
21
|
+
optionalHeader: Flags.string({
|
|
22
|
+
required: false,
|
|
23
|
+
multiple: true,
|
|
24
|
+
description: "Makes the given header optional in generated types",
|
|
25
|
+
default: [],
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
static args = {
|
|
29
|
+
input: Args.directory({
|
|
30
|
+
required: true,
|
|
31
|
+
}),
|
|
32
|
+
output: Args.directory({
|
|
33
|
+
required: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
async run() {
|
|
37
|
+
const { args, flags } = await this.parse(Generate);
|
|
38
|
+
const loader = new UniversalContentLoader(new UniversalFileLoader(args.input));
|
|
39
|
+
ux.action.start("Loading OpenAPI spec");
|
|
40
|
+
const openApiDoc = await loader.load();
|
|
41
|
+
ux.action.stop();
|
|
42
|
+
ux.action.start("Parsing OpenAPI spec");
|
|
43
|
+
const spec = await OpenApiSpec.parse(openApiDoc, {
|
|
44
|
+
skipValidation: flags.skipValidation,
|
|
45
|
+
});
|
|
46
|
+
ux.action.stop();
|
|
47
|
+
ux.action.start("Building transformation model");
|
|
48
|
+
const model = CodeGenerationModel.fromDoc(flags.name, spec.document);
|
|
49
|
+
ux.action.stop();
|
|
50
|
+
ux.action.start("Generating descriptors");
|
|
51
|
+
const descriptorsFileContent = model.paths.compileDescriptors();
|
|
52
|
+
await writeIfChangedAsync(path.join(args.output, "descriptors.ts"), await prepareTypeScriptOutput(descriptorsFileContent));
|
|
53
|
+
ux.action.stop();
|
|
54
|
+
ux.action.start("Generating types");
|
|
55
|
+
const typesFileContent = await model.compileTypes({
|
|
56
|
+
rootNamespace: flags.name,
|
|
57
|
+
optionalHeaders: flags.optionalHeader,
|
|
58
|
+
});
|
|
59
|
+
await writeIfChangedAsync(path.join(args.output, "types.ts"), await prepareTypeScriptOutput(typesFileContent));
|
|
60
|
+
ux.action.stop();
|
|
61
|
+
ux.action.start("Generating client");
|
|
62
|
+
const clientFileContent = model.paths.compileClient();
|
|
63
|
+
await writeIfChangedAsync(path.join(args.output, "client.ts"), await prepareTypeScriptOutput(clientFileContent));
|
|
64
|
+
ux.action.stop();
|
|
65
|
+
ux.action.start("Generating React client");
|
|
66
|
+
const reactClientFileContent = model.paths.compileReactClient();
|
|
67
|
+
await writeIfChangedAsync(path.join(args.output, "client-react.ts"), await prepareTypeScriptOutput(reactClientFileContent));
|
|
68
|
+
ux.action.stop();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Args, Command } from "@oclif/core";
|
|
2
|
+
import { UniversalContentLoader } from "../../loading/UniversalContentLoader.js";
|
|
3
|
+
import { OpenApiSpec } from "../../openapi/OpenApiSpec.js";
|
|
4
|
+
import { UniversalFileLoader } from "../../loading/UniversalFileLoader.js";
|
|
5
|
+
export default class Validate extends Command {
|
|
6
|
+
static description = "Validate the provided OpenAPI spec.";
|
|
7
|
+
static args = {
|
|
8
|
+
input: Args.string({
|
|
9
|
+
required: true,
|
|
10
|
+
}),
|
|
11
|
+
};
|
|
12
|
+
async run() {
|
|
13
|
+
const { args } = await this.parse(Validate);
|
|
14
|
+
const loader = new UniversalContentLoader(new UniversalFileLoader(args.input));
|
|
15
|
+
const openApiDoc = await loader.load();
|
|
16
|
+
await OpenApiSpec.parse(openApiDoc, {
|
|
17
|
+
skipValidation: false,
|
|
18
|
+
});
|
|
19
|
+
this.log("OK");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { asyncStringJoin } from "./asyncStringJoin.js";
|
|
2
|
+
test("asyncStringJoin() joins each item with an async generator function", async () => {
|
|
3
|
+
const result = await asyncStringJoin(["Foo", "Bar"], (item) => Promise.resolve(`Async${item}`));
|
|
4
|
+
expect(result).toMatch("AsyncFoo\r\nAsyncBar");
|
|
5
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { compile } from "json-schema-to-typescript";
|
|
2
|
+
const defaultCompileOptions = {
|
|
3
|
+
bannerComment: "",
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
maxItems: 5,
|
|
6
|
+
};
|
|
7
|
+
export const compileJsonSchema = (schema, name, options = {}) => compile(schema, name, {
|
|
8
|
+
...defaultCompileOptions,
|
|
9
|
+
...options,
|
|
10
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import prettier from "prettier";
|
|
2
|
+
import VError from "verror";
|
|
3
|
+
import { makeError } from "../lib/makeError.js";
|
|
4
|
+
export const format = async (ts) => {
|
|
5
|
+
try {
|
|
6
|
+
return await prettier.format(ts, {
|
|
7
|
+
plugins: [],
|
|
8
|
+
parser: "typescript",
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
throw new VError({
|
|
13
|
+
cause: makeError(error),
|
|
14
|
+
name: "CodeFormattingError",
|
|
15
|
+
}, "Failed to format the generated code. This usually happens, when the generated code has syntax errors. Please file an issue.");
|
|
16
|
+
}
|
|
17
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Name } from "./global/Name.js";
|
|
2
|
+
import { Components } from "./components/Components.js";
|
|
3
|
+
import { Paths } from "./paths/Paths.js";
|
|
4
|
+
import { Tag } from "./tags/Tag.js";
|
|
5
|
+
export class CodeGenerationModel {
|
|
6
|
+
rootNamespace;
|
|
7
|
+
paths;
|
|
8
|
+
components;
|
|
9
|
+
tags;
|
|
10
|
+
doc;
|
|
11
|
+
constructor(rootNamespace, doc) {
|
|
12
|
+
this.rootNamespace = new Name(rootNamespace);
|
|
13
|
+
this.doc = doc;
|
|
14
|
+
this.components = new Components(this);
|
|
15
|
+
this.tags = this.doc.tags?.map((doc) => Tag.fromDoc(doc)) ?? [];
|
|
16
|
+
this.paths = new Paths(this, this.doc.paths);
|
|
17
|
+
}
|
|
18
|
+
static fromDoc(rootNamespace, doc) {
|
|
19
|
+
return new CodeGenerationModel(rootNamespace, doc);
|
|
20
|
+
}
|
|
21
|
+
async compileTypes(opts) {
|
|
22
|
+
const t = {
|
|
23
|
+
ns: this.rootNamespace.tsType,
|
|
24
|
+
components: await this.components.compileTypes(opts),
|
|
25
|
+
operationTypes: await this.paths.compileOperationTypes(),
|
|
26
|
+
paths: await this.paths.compileTypes(opts),
|
|
27
|
+
};
|
|
28
|
+
return `\
|
|
29
|
+
import * as descriptors from "./descriptors.js";
|
|
30
|
+
import {
|
|
31
|
+
InferredRequestData,
|
|
32
|
+
InferredResponseData,
|
|
33
|
+
HttpStatus
|
|
34
|
+
} from "@mittwald/api-client-commons";
|
|
35
|
+
|
|
36
|
+
export declare module ${t.ns} {
|
|
37
|
+
${t.operationTypes}
|
|
38
|
+
${t.components}
|
|
39
|
+
${t.paths}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Name } from "../global/Name.js";
|
|
2
|
+
import { Schemas } from "./Schemas.js";
|
|
3
|
+
import { Parameters } from "./Parameters.js";
|
|
4
|
+
import { RequestBodies } from "./RequestBodies.js";
|
|
5
|
+
import { Responses } from "./Responses.js";
|
|
6
|
+
import { SecuritySchemes } from "./SecuritySchemes.js";
|
|
7
|
+
import { extractComponentRefName } from "../../refs/extractComponentRefName.js";
|
|
8
|
+
import { assertNoRefs } from "../../refs/assertNoRefs.js";
|
|
9
|
+
import invariant from "invariant";
|
|
10
|
+
export class Components {
|
|
11
|
+
static ns = "Components";
|
|
12
|
+
name;
|
|
13
|
+
schemas;
|
|
14
|
+
securitySchemes;
|
|
15
|
+
parameters;
|
|
16
|
+
requestBodies;
|
|
17
|
+
responses;
|
|
18
|
+
model;
|
|
19
|
+
constructor(model) {
|
|
20
|
+
this.model = model;
|
|
21
|
+
this.name = new Name(Components.ns, model.rootNamespace);
|
|
22
|
+
this.schemas = new Schemas(this, model.doc.components?.schemas ?? {});
|
|
23
|
+
this.securitySchemes = new SecuritySchemes(this, model.doc.components?.securitySchemes ?? {});
|
|
24
|
+
this.parameters = new Parameters(this, model.doc.components?.parameters ?? {});
|
|
25
|
+
this.requestBodies = new RequestBodies(this, model.doc.components?.requestBodies ?? {});
|
|
26
|
+
this.responses = new Responses(this, model.doc.components?.responses ?? {});
|
|
27
|
+
}
|
|
28
|
+
async compileTypes(opts) {
|
|
29
|
+
const t = {
|
|
30
|
+
ns: Components.ns,
|
|
31
|
+
schemas: await this.schemas.compileTypes(opts),
|
|
32
|
+
parameters: await this.parameters.compileTypes(opts),
|
|
33
|
+
requestBodies: await this.requestBodies.compileTypes(opts),
|
|
34
|
+
responses: await this.responses.compileTypes(opts),
|
|
35
|
+
securitySchemes: await this.securitySchemes.compileTypes(opts),
|
|
36
|
+
};
|
|
37
|
+
return `\
|
|
38
|
+
namespace ${t.ns} {
|
|
39
|
+
${t.schemas}
|
|
40
|
+
${t.parameters}
|
|
41
|
+
${t.requestBodies}
|
|
42
|
+
${t.responses}
|
|
43
|
+
${t.securitySchemes}
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
resolveRef(componentType, obj) {
|
|
48
|
+
if ("$ref" in obj) {
|
|
49
|
+
invariant(typeof obj.$ref === "string", "PathItemRefs are not supported by now");
|
|
50
|
+
const refName = extractComponentRefName(obj.$ref, componentType);
|
|
51
|
+
const resolved = this.model.doc.components?.[componentType]?.[refName];
|
|
52
|
+
if (resolved === undefined) {
|
|
53
|
+
throw new Error(`Could not find ref ${obj.$ref} in components.${componentType}`);
|
|
54
|
+
}
|
|
55
|
+
assertNoRefs(resolved);
|
|
56
|
+
return resolved;
|
|
57
|
+
}
|
|
58
|
+
assertNoRefs(obj);
|
|
59
|
+
return obj;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { JSONSchema } from "../global/JSONSchema.js";
|
|
2
|
+
import { Name } from "../global/Name.js";
|
|
3
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
4
|
+
import { assertNoRefs } from "../../refs/assertNoRefs.js";
|
|
5
|
+
export class Parameters {
|
|
6
|
+
static ns = "Parameters";
|
|
7
|
+
components;
|
|
8
|
+
name;
|
|
9
|
+
parameters;
|
|
10
|
+
constructor(components, parametersObject) {
|
|
11
|
+
this.components = components;
|
|
12
|
+
this.name = new Name(Parameters.ns, components.name);
|
|
13
|
+
this.parameters = parametersObject;
|
|
14
|
+
}
|
|
15
|
+
async compileTypes(opts) {
|
|
16
|
+
const schemas = Object.entries(this.parameters).map(([name, param]) => {
|
|
17
|
+
assertNoRefs(param);
|
|
18
|
+
return new JSONSchema(new Name(name, this.name), param.schema);
|
|
19
|
+
});
|
|
20
|
+
const t = {
|
|
21
|
+
ns: Parameters.ns,
|
|
22
|
+
types: await asyncStringJoin(schemas, (schema) => schema.compile(opts)),
|
|
23
|
+
};
|
|
24
|
+
return `\
|
|
25
|
+
namespace ${t.ns} {
|
|
26
|
+
${t.types}
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { JSONSchema } from "../global/JSONSchema.js";
|
|
2
|
+
import { Name } from "../global/Name.js";
|
|
3
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
4
|
+
export class RequestBodies {
|
|
5
|
+
static ns = "RequestBodies";
|
|
6
|
+
schemas;
|
|
7
|
+
components;
|
|
8
|
+
name;
|
|
9
|
+
constructor(components, schemas) {
|
|
10
|
+
this.components = components;
|
|
11
|
+
this.name = new Name(RequestBodies.ns, components.name);
|
|
12
|
+
this.schemas = Object.entries(schemas ?? {}).map(([schemaName, schema]) => new JSONSchema(new Name(schemaName, this.name), schema));
|
|
13
|
+
}
|
|
14
|
+
async compileTypes(opts) {
|
|
15
|
+
const t = {
|
|
16
|
+
ns: RequestBodies.ns,
|
|
17
|
+
types: await asyncStringJoin(this.schemas, (schema) => schema.compile(opts)),
|
|
18
|
+
};
|
|
19
|
+
return `\
|
|
20
|
+
namespace ${t.ns} {
|
|
21
|
+
${t.types}
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ResponseContent } from "./ResponseContent.js";
|
|
2
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
3
|
+
import invariant from "invariant";
|
|
4
|
+
export class Response {
|
|
5
|
+
name;
|
|
6
|
+
responses;
|
|
7
|
+
contents;
|
|
8
|
+
constructor(name, responses, mediaTypesDoc) {
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.responses = responses;
|
|
11
|
+
this.contents = Object.entries(mediaTypesDoc).map(([mediaType, mediaTypeObj]) => {
|
|
12
|
+
invariant(!!mediaTypeObj?.schema, "No schema set");
|
|
13
|
+
return new ResponseContent(this, mediaType, mediaTypeObj?.schema);
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async compileTypes(opts) {
|
|
17
|
+
const t = {
|
|
18
|
+
ns: this.name.tsType,
|
|
19
|
+
types: await asyncStringJoin(this.contents, (content) => content.schema.compile(opts)),
|
|
20
|
+
};
|
|
21
|
+
return `\
|
|
22
|
+
namespace ${t.ns} {
|
|
23
|
+
${t.types}
|
|
24
|
+
}
|
|
25
|
+
`;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Name } from "../global/Name.js";
|
|
2
|
+
import { JSONSchema } from "../global/JSONSchema.js";
|
|
3
|
+
export class ResponseContent {
|
|
4
|
+
mediaType;
|
|
5
|
+
schema;
|
|
6
|
+
response;
|
|
7
|
+
constructor(response, mediaType, schema) {
|
|
8
|
+
this.mediaType = new Name(mediaType, response.name);
|
|
9
|
+
this.response = response;
|
|
10
|
+
this.schema = new JSONSchema(this.mediaType, schema);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Name } from "../global/Name.js";
|
|
2
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
3
|
+
import { Response } from "./Response.js";
|
|
4
|
+
import { assertNoRefs } from "../../refs/assertNoRefs.js";
|
|
5
|
+
export class Responses {
|
|
6
|
+
static ns = "Responses";
|
|
7
|
+
responses;
|
|
8
|
+
components;
|
|
9
|
+
name;
|
|
10
|
+
constructor(components, responses = {}) {
|
|
11
|
+
this.components = components;
|
|
12
|
+
this.name = new Name(Responses.ns, components.name);
|
|
13
|
+
this.responses = Object.entries(responses).map(([name, response]) => {
|
|
14
|
+
assertNoRefs(response);
|
|
15
|
+
return new Response(new Name(name, this.name), this, response.content ?? {});
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
async compileTypes(opts) {
|
|
19
|
+
const t = {
|
|
20
|
+
ns: Responses.ns,
|
|
21
|
+
types: await asyncStringJoin(this.responses, (response) => response.compileTypes(opts)),
|
|
22
|
+
};
|
|
23
|
+
return `\
|
|
24
|
+
namespace ${t.ns} {
|
|
25
|
+
${t.types}
|
|
26
|
+
}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { JSONSchema } from "../global/JSONSchema.js";
|
|
2
|
+
import { Name } from "../global/Name.js";
|
|
3
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
4
|
+
export class Schemas {
|
|
5
|
+
static ns = "Schemas";
|
|
6
|
+
schemas;
|
|
7
|
+
components;
|
|
8
|
+
name;
|
|
9
|
+
constructor(components, schemas) {
|
|
10
|
+
this.components = components;
|
|
11
|
+
this.name = new Name(Schemas.ns, components.name);
|
|
12
|
+
this.schemas = Object.entries(schemas ?? {}).map(([schemaName, schema]) => new JSONSchema(new Name(schemaName, this.name), schema));
|
|
13
|
+
}
|
|
14
|
+
async compileTypes(opts) {
|
|
15
|
+
const t = {
|
|
16
|
+
ns: Schemas.ns,
|
|
17
|
+
types: await asyncStringJoin(this.schemas, (schema) => schema.compile(opts)),
|
|
18
|
+
};
|
|
19
|
+
return `\
|
|
20
|
+
namespace ${t.ns} {
|
|
21
|
+
${t.types}
|
|
22
|
+
}
|
|
23
|
+
`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Name } from "../global/Name.js";
|
|
2
|
+
import { JSONSchema } from "../global/JSONSchema.js";
|
|
3
|
+
export class SecurityScheme {
|
|
4
|
+
in;
|
|
5
|
+
name;
|
|
6
|
+
jsonSchema;
|
|
7
|
+
constructor(schemes, name, doc) {
|
|
8
|
+
this.name = new Name(name, schemes.name);
|
|
9
|
+
this.in = doc.in;
|
|
10
|
+
this.jsonSchema = new JSONSchema(this.name, {
|
|
11
|
+
type: "object",
|
|
12
|
+
description: doc.description,
|
|
13
|
+
properties: {
|
|
14
|
+
[doc.name]: {
|
|
15
|
+
type: "string",
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: [doc.name],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Name } from "../global/Name.js";
|
|
2
|
+
import invariant from "invariant";
|
|
3
|
+
import { assertNoRefs } from "../../refs/assertNoRefs.js";
|
|
4
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
5
|
+
import { SecurityScheme } from "./SecurityScheme.js";
|
|
6
|
+
export class SecuritySchemes {
|
|
7
|
+
static ns = "SecuritySchemes";
|
|
8
|
+
schemes;
|
|
9
|
+
components;
|
|
10
|
+
name;
|
|
11
|
+
constructor(components, doc = {}) {
|
|
12
|
+
this.components = components;
|
|
13
|
+
this.name = new Name(SecuritySchemes.ns, components.name);
|
|
14
|
+
Object.values(doc).forEach((scheme) => {
|
|
15
|
+
assertNoRefs(scheme);
|
|
16
|
+
invariant(scheme.type === "apiKey", `Security scheme type '${scheme.type}' is not supported (allowed types: 'apiKey')`);
|
|
17
|
+
});
|
|
18
|
+
this.schemes = Object.entries(doc).map(([name, scheme]) => new SecurityScheme(this, name, scheme));
|
|
19
|
+
}
|
|
20
|
+
requireScheme(name) {
|
|
21
|
+
const scheme = this.schemes.find((s) => s.name.raw === name);
|
|
22
|
+
invariant(!!scheme, `Required security scheme '${name}' not found`);
|
|
23
|
+
return scheme;
|
|
24
|
+
}
|
|
25
|
+
async compileTypes(opts) {
|
|
26
|
+
const t = {
|
|
27
|
+
ns: SecuritySchemes.ns,
|
|
28
|
+
types: await asyncStringJoin(this.schemes, (scheme) => {
|
|
29
|
+
const { optionalHeaders } = opts;
|
|
30
|
+
const mustSetOptionalHeaders = scheme.in === "header" && !!optionalHeaders;
|
|
31
|
+
const jsonSchema = mustSetOptionalHeaders
|
|
32
|
+
? scheme.jsonSchema.cloneWithOptionalProperties(optionalHeaders)
|
|
33
|
+
: scheme.jsonSchema;
|
|
34
|
+
return jsonSchema.compile(opts);
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
return `\
|
|
38
|
+
namespace ${t.ns} {
|
|
39
|
+
${t.types}
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { compileJsonSchema } from "../../compileJsonSchema.js";
|
|
2
|
+
import { Name } from "./Name.js";
|
|
3
|
+
import cloneDeep from "clone-deep";
|
|
4
|
+
import { componentRefsToCustomTypes } from "../../refs/componentRefsToCustomTypes.js";
|
|
5
|
+
export class JSONSchema {
|
|
6
|
+
schemaObject;
|
|
7
|
+
name;
|
|
8
|
+
constructor(name, data = { type: "any" }) {
|
|
9
|
+
this.name = name;
|
|
10
|
+
this.schemaObject = data;
|
|
11
|
+
}
|
|
12
|
+
async compile(opts) {
|
|
13
|
+
const withCustomRefTypes = componentRefsToCustomTypes(opts.rootNamespace, this.schemaObject);
|
|
14
|
+
return compileJsonSchema(withCustomRefTypes, this.name.tsType);
|
|
15
|
+
}
|
|
16
|
+
clone() {
|
|
17
|
+
return new JSONSchema(new Name(this.name.raw), cloneDeep(this.schemaObject));
|
|
18
|
+
}
|
|
19
|
+
cloneWithOptionalProperties(optionalProperties) {
|
|
20
|
+
const withOptionalProperties = this.clone();
|
|
21
|
+
this.setOptionalPropertiesInSchema(optionalProperties, withOptionalProperties.schemaObject);
|
|
22
|
+
return withOptionalProperties;
|
|
23
|
+
}
|
|
24
|
+
setOptionalPropertiesInSchema(optionalProperties, schema) {
|
|
25
|
+
for (const optionalProp of optionalProperties) {
|
|
26
|
+
const requiredProperties = schema.required;
|
|
27
|
+
if (Array.isArray(requiredProperties)) {
|
|
28
|
+
schema.required = requiredProperties.filter((p) => p !== optionalProp);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const subSchema of schema.allOf ?? []) {
|
|
32
|
+
this.setOptionalPropertiesInSchema(optionalProperties, subSchema);
|
|
33
|
+
}
|
|
34
|
+
for (const subSchema of schema.anyOf ?? []) {
|
|
35
|
+
this.setOptionalPropertiesInSchema(optionalProperties, subSchema);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
asCustomTypeRef() {
|
|
39
|
+
return {
|
|
40
|
+
tsType: this.name.tsTypeWithNamespace,
|
|
41
|
+
type: "object",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { tsTypeName } from "../../tsTypeName.js";
|
|
2
|
+
import camelcase from "camelcase";
|
|
3
|
+
export class Name {
|
|
4
|
+
raw;
|
|
5
|
+
tsType;
|
|
6
|
+
tsVar;
|
|
7
|
+
parent;
|
|
8
|
+
constructor(raw, parent) {
|
|
9
|
+
this.raw = raw;
|
|
10
|
+
this.tsType = tsTypeName(raw);
|
|
11
|
+
this.tsVar = camelcase(this.tsType);
|
|
12
|
+
this.parent = parent;
|
|
13
|
+
}
|
|
14
|
+
get tsTypeWithNamespace() {
|
|
15
|
+
if (this.parent) {
|
|
16
|
+
return `${this.parent.tsTypeWithNamespace}.${this.tsType}`;
|
|
17
|
+
}
|
|
18
|
+
return this.tsType;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Name } from "./Name.js";
|
|
2
|
+
const parentName = new Name("Parent");
|
|
3
|
+
test("TS variable name is camelcased", () => {
|
|
4
|
+
const testName = new Name("ChildName");
|
|
5
|
+
expect(testName.tsVar).toEqual("childName");
|
|
6
|
+
});
|
|
7
|
+
test("Namespaced name includes parent name", () => {
|
|
8
|
+
const testName = new Name("ChildName", parentName);
|
|
9
|
+
expect(testName.tsTypeWithNamespace).toEqual("Parent.ChildName");
|
|
10
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Operation } from "./operation/Operation.js";
|
|
2
|
+
import { Name } from "../global/Name.js";
|
|
3
|
+
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
4
|
+
import { OpenAPIV3 } from "openapi-types";
|
|
5
|
+
export class Path {
|
|
6
|
+
paths;
|
|
7
|
+
name;
|
|
8
|
+
operations;
|
|
9
|
+
constructor(paths, name, operationsDoc) {
|
|
10
|
+
this.paths = paths;
|
|
11
|
+
this.name = name;
|
|
12
|
+
this.operations = Object.values(OpenAPIV3.HttpMethods)
|
|
13
|
+
.map((method) => {
|
|
14
|
+
const operationDoc = operationsDoc[method];
|
|
15
|
+
if (operationDoc && operationDoc.deprecated !== true) {
|
|
16
|
+
return Operation.fromDoc(this, method, operationDoc);
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
.filter((o) => !!o);
|
|
20
|
+
}
|
|
21
|
+
static fromDoc(paths, name, operationsDoc) {
|
|
22
|
+
return new Path(paths, new Name(name, paths.name), operationsDoc);
|
|
23
|
+
}
|
|
24
|
+
async compileTypes(options) {
|
|
25
|
+
const t = {
|
|
26
|
+
ns: this.name.tsType,
|
|
27
|
+
types: await asyncStringJoin(this.operations, (operation) => operation.compileTypes(options)),
|
|
28
|
+
};
|
|
29
|
+
return `\
|
|
30
|
+
namespace ${t.ns} {
|
|
31
|
+
${t.types}
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
}
|
|
35
|
+
}
|