@mittwald/api-code-generator 0.0.0-development-fd38fc6-20261012 → 0.0.0-development-34bc8ae-20261013
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/dist/esm/generation/deprecation.js +40 -0
- package/dist/esm/generation/deprecation.test.js +43 -0
- package/dist/esm/generation/model/CodeGenerationModel.test.js +89 -0
- package/dist/esm/generation/model/components/Parameters.js +7 -1
- package/dist/esm/generation/model/global/JSONSchema.js +9 -2
- package/dist/esm/generation/model/paths/operation/RequestParameters.js +4 -1
- package/dist/esm/generation/refs/componentRefsToCustomTypes.js +14 -0
- package/dist/esm/generation/refs/componentRefsToCustomTypes.test.js +11 -0
- package/dist/esm/loading/UniversalContentLoader.js +4 -2
- package/dist/esm/openapi/OpenApiSpec.test.js +82 -0
- package/dist/types/generation/deprecation.d.ts +27 -0
- package/dist/types/generation/deprecation.test.d.ts +1 -0
- package/package.json +10 -9
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const isDeprecated = (doc) => doc?.deprecated === true;
|
|
2
|
+
/**
|
|
3
|
+
* Copies a `deprecated: true` annotation from an OpenAPI object onto the JSON
|
|
4
|
+
* schema that is generated for it. Used where the annotation lives on the
|
|
5
|
+
* OpenAPI object (e.g. a Parameter Object) rather than on its `schema`, because
|
|
6
|
+
* only the schema is handed to the TypeScript compiler.
|
|
7
|
+
*/
|
|
8
|
+
export const withDeprecation = (schema, source) => isDeprecated(source) && !isDeprecated(schema)
|
|
9
|
+
? { ...schema, deprecated: true }
|
|
10
|
+
: schema;
|
|
11
|
+
const jsDocStart = "/**";
|
|
12
|
+
const jsDocEnd = "*/";
|
|
13
|
+
const deprecatedTag = "@deprecated";
|
|
14
|
+
/**
|
|
15
|
+
* `json-schema-to-typescript` emits an `@deprecated` JSDoc tag for interface
|
|
16
|
+
* members and for standalone interfaces, but drops it for standalone type
|
|
17
|
+
* aliases: its `generateStandaloneType` calls the comment generator without the
|
|
18
|
+
* deprecation flag, so `export type Foo = string` ends up with an empty `/**
|
|
19
|
+
* *\/` block. We add the tag back so every generated declaration carries the
|
|
20
|
+
* deprecation.
|
|
21
|
+
*/
|
|
22
|
+
export const ensureDeprecatedTypeAliasComment = (compiled, tsTypeName) => {
|
|
23
|
+
const declaration = `export type ${tsTypeName} `;
|
|
24
|
+
const declarationIndex = compiled.indexOf(declaration);
|
|
25
|
+
if (declarationIndex < 0) {
|
|
26
|
+
return compiled;
|
|
27
|
+
}
|
|
28
|
+
const before = compiled.slice(0, declarationIndex);
|
|
29
|
+
const trimmedBefore = before.trimEnd();
|
|
30
|
+
if (!trimmedBefore.endsWith(jsDocEnd)) {
|
|
31
|
+
return `${before}/**\n * ${deprecatedTag}\n */\n${compiled.slice(declarationIndex)}`;
|
|
32
|
+
}
|
|
33
|
+
const commentStart = trimmedBefore.lastIndexOf(jsDocStart);
|
|
34
|
+
if (commentStart < 0 ||
|
|
35
|
+
trimmedBefore.slice(commentStart).includes(deprecatedTag)) {
|
|
36
|
+
return compiled;
|
|
37
|
+
}
|
|
38
|
+
const insertAt = commentStart + jsDocStart.length;
|
|
39
|
+
return `${compiled.slice(0, insertAt)}\n * ${deprecatedTag}${compiled.slice(insertAt)}`;
|
|
40
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ensureDeprecatedTypeAliasComment, isDeprecated, withDeprecation, } from "./deprecation.js";
|
|
2
|
+
test("isDeprecated only reports an explicit true", () => {
|
|
3
|
+
expect(isDeprecated({ deprecated: true })).toBe(true);
|
|
4
|
+
expect(isDeprecated({ deprecated: false })).toBe(false);
|
|
5
|
+
expect(isDeprecated({})).toBe(false);
|
|
6
|
+
expect(isDeprecated(undefined)).toBe(false);
|
|
7
|
+
});
|
|
8
|
+
test("withDeprecation copies the annotation onto the schema", () => {
|
|
9
|
+
const schema = { type: "string" };
|
|
10
|
+
expect(withDeprecation(schema, { deprecated: true })).toEqual({
|
|
11
|
+
type: "string",
|
|
12
|
+
deprecated: true,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
test("withDeprecation leaves the schema untouched when not deprecated", () => {
|
|
16
|
+
const schema = { type: "string" };
|
|
17
|
+
expect(withDeprecation(schema, { deprecated: false })).toBe(schema);
|
|
18
|
+
expect(withDeprecation(schema, undefined)).toBe(schema);
|
|
19
|
+
});
|
|
20
|
+
test("withDeprecation keeps an annotation that is already on the schema", () => {
|
|
21
|
+
const schema = { type: "string", deprecated: true };
|
|
22
|
+
expect(withDeprecation(schema, { deprecated: true })).toBe(schema);
|
|
23
|
+
});
|
|
24
|
+
test("ensureDeprecatedTypeAliasComment adds the tag to an empty comment block", () => {
|
|
25
|
+
const compiled = "/**\n */\nexport type LegacySort = string;\n";
|
|
26
|
+
expect(ensureDeprecatedTypeAliasComment(compiled, "LegacySort")).toBe("/**\n * @deprecated\n */\nexport type LegacySort = string;\n");
|
|
27
|
+
});
|
|
28
|
+
test("ensureDeprecatedTypeAliasComment keeps an existing description", () => {
|
|
29
|
+
const compiled = "/**\n * Old thing\n */\nexport type LegacySort = string;\n";
|
|
30
|
+
expect(ensureDeprecatedTypeAliasComment(compiled, "LegacySort")).toBe("/**\n * @deprecated\n * Old thing\n */\nexport type LegacySort = string;\n");
|
|
31
|
+
});
|
|
32
|
+
test("ensureDeprecatedTypeAliasComment adds a comment block when there is none", () => {
|
|
33
|
+
const compiled = "export type LegacySort = string;\n";
|
|
34
|
+
expect(ensureDeprecatedTypeAliasComment(compiled, "LegacySort")).toBe("/**\n * @deprecated\n */\nexport type LegacySort = string;\n");
|
|
35
|
+
});
|
|
36
|
+
test("ensureDeprecatedTypeAliasComment is idempotent", () => {
|
|
37
|
+
const compiled = "/**\n * @deprecated\n */\nexport type LegacySort = string;\n";
|
|
38
|
+
expect(ensureDeprecatedTypeAliasComment(compiled, "LegacySort")).toBe(compiled);
|
|
39
|
+
});
|
|
40
|
+
test("ensureDeprecatedTypeAliasComment ignores output without a matching alias", () => {
|
|
41
|
+
const compiled = "export interface LegacySort {\n a?: string;\n}\n";
|
|
42
|
+
expect(ensureDeprecatedTypeAliasComment(compiled, "LegacySort")).toBe(compiled);
|
|
43
|
+
});
|
|
@@ -65,6 +65,95 @@ test("const keywords survive from the spec into the generated types", async () =
|
|
|
65
65
|
| Api.Components.Schemas.DoneEvent;
|
|
66
66
|
`));
|
|
67
67
|
});
|
|
68
|
+
const doc = {
|
|
69
|
+
openapi: "3.0.3",
|
|
70
|
+
info: { title: "Test API", version: "1.0.0" },
|
|
71
|
+
paths: {
|
|
72
|
+
"/conversations": {
|
|
73
|
+
get: {
|
|
74
|
+
operationId: "conversation-list",
|
|
75
|
+
tags: ["conversation"],
|
|
76
|
+
parameters: [
|
|
77
|
+
{
|
|
78
|
+
name: "legacyFilter",
|
|
79
|
+
in: "query",
|
|
80
|
+
deprecated: true,
|
|
81
|
+
schema: { type: "string" },
|
|
82
|
+
},
|
|
83
|
+
{ $ref: "#/components/parameters/LegacySort" },
|
|
84
|
+
],
|
|
85
|
+
responses: {
|
|
86
|
+
"200": {
|
|
87
|
+
description: "OK",
|
|
88
|
+
content: {
|
|
89
|
+
"application/json": {
|
|
90
|
+
schema: { $ref: "#/components/schemas/Conversation" },
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
components: {
|
|
99
|
+
parameters: {
|
|
100
|
+
LegacySort: {
|
|
101
|
+
name: "legacySort",
|
|
102
|
+
in: "query",
|
|
103
|
+
deprecated: true,
|
|
104
|
+
schema: { type: "string" },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
schemas: {
|
|
108
|
+
Conversation: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
lastMessageAt: {
|
|
112
|
+
type: "string",
|
|
113
|
+
format: "date-time",
|
|
114
|
+
deprecated: true,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
LegacyState: {
|
|
119
|
+
type: "string",
|
|
120
|
+
enum: ["enabled", "disabled"],
|
|
121
|
+
deprecated: true,
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const compileTypes = () => CodeGenerationModel.fromDoc("Api", doc).compileTypes({
|
|
127
|
+
rootNamespace: "Api",
|
|
128
|
+
});
|
|
129
|
+
/** Matches an `@deprecated` JSDoc tag directly in front of `declaration`. */
|
|
130
|
+
const deprecatedBefore = (declaration) => new RegExp(`@deprecated[^;{}]*?\\*/\\s*${declaration.replace(/[$?]/g, "\\$&")}`);
|
|
131
|
+
test("a deprecated schema property is emitted with an @deprecated tag", async () => {
|
|
132
|
+
const types = await compileTypes();
|
|
133
|
+
expect(types).toMatch(deprecatedBefore("lastMessageAt?"));
|
|
134
|
+
});
|
|
135
|
+
test("a deprecated standalone type gets an @deprecated tag", async () => {
|
|
136
|
+
const types = await compileTypes();
|
|
137
|
+
expect(types).toMatch(deprecatedBefore("export type LegacyState"));
|
|
138
|
+
});
|
|
139
|
+
test("a deprecated request parameter is emitted with an @deprecated tag", async () => {
|
|
140
|
+
const types = await compileTypes();
|
|
141
|
+
expect(types).toMatch(deprecatedBefore("legacyFilter?"));
|
|
142
|
+
});
|
|
143
|
+
test("a deprecated referenced request parameter is emitted with an @deprecated tag", async () => {
|
|
144
|
+
const types = await compileTypes();
|
|
145
|
+
expect(types).toMatch(deprecatedBefore("legacySort?"));
|
|
146
|
+
});
|
|
147
|
+
/**
|
|
148
|
+
* Component parameters are request-only, so they are compiled through
|
|
149
|
+
* `compileAsRequestInput` rather than `compile`. Both paths have to apply the
|
|
150
|
+
* type alias workaround, otherwise the standalone alias ends up with an empty
|
|
151
|
+
* JSDoc block.
|
|
152
|
+
*/
|
|
153
|
+
test("a deprecated component parameter alias gets an @deprecated tag", async () => {
|
|
154
|
+
const types = await compileTypes();
|
|
155
|
+
expect(types).toMatch(deprecatedBefore("export type LegacySort"));
|
|
156
|
+
});
|
|
68
157
|
const buildModel = (doc = makePetstoreDoc()) => CodeGenerationModel.fromDoc("PetstoreAPI", doc);
|
|
69
158
|
const compile = (doc = makePetstoreDoc()) => buildModel(doc).compileTypes({ rootNamespace: "PetstoreAPI" });
|
|
70
159
|
/** Collapses whitespace so assertions do not depend on generated indentation. */
|
|
@@ -2,6 +2,7 @@ import { JSONSchema } from "../global/JSONSchema.js";
|
|
|
2
2
|
import { Name } from "../global/Name.js";
|
|
3
3
|
import { asyncStringJoin } from "../../asyncStringJoin.js";
|
|
4
4
|
import { assertNoRefs } from "../../refs/assertNoRefs.js";
|
|
5
|
+
import { withDeprecation } from "../../deprecation.js";
|
|
5
6
|
export class Parameters {
|
|
6
7
|
static ns = "Parameters";
|
|
7
8
|
components;
|
|
@@ -15,7 +16,12 @@ export class Parameters {
|
|
|
15
16
|
async compileTypes(opts) {
|
|
16
17
|
const schemas = Object.entries(this.parameters).map(([name, param]) => {
|
|
17
18
|
assertNoRefs(param);
|
|
18
|
-
return new JSONSchema(new Name(name, this.name),
|
|
19
|
+
return new JSONSchema(new Name(name, this.name),
|
|
20
|
+
// `deprecated` lives on the Parameter Object, not on its schema, so it
|
|
21
|
+
// has to be carried over explicitly to end up in the generated JSDoc.
|
|
22
|
+
param.schema === undefined
|
|
23
|
+
? undefined
|
|
24
|
+
: withDeprecation(param.schema, param));
|
|
19
25
|
});
|
|
20
26
|
const t = {
|
|
21
27
|
ns: Parameters.ns,
|
|
@@ -2,6 +2,7 @@ import { compileJsonSchema } from "../../compileJsonSchema.js";
|
|
|
2
2
|
import { Name } from "./Name.js";
|
|
3
3
|
import cloneDeep from "clone-deep";
|
|
4
4
|
import { componentRefsToCustomTypes } from "../../refs/componentRefsToCustomTypes.js";
|
|
5
|
+
import { ensureDeprecatedTypeAliasComment, isDeprecated, } from "../../deprecation.js";
|
|
5
6
|
import { widenDateTimeInputs } from "../../dateTime/dateTimeInput.js";
|
|
6
7
|
import { dateTimeInputRefTSNameResolver } from "../../dateTime/dateTimeInputRefs.js";
|
|
7
8
|
export class JSONSchema {
|
|
@@ -13,7 +14,10 @@ export class JSONSchema {
|
|
|
13
14
|
}
|
|
14
15
|
async compile(opts) {
|
|
15
16
|
const withCustomRefTypes = componentRefsToCustomTypes(opts.rootNamespace, this.schemaObject);
|
|
16
|
-
|
|
17
|
+
const compiled = await compileJsonSchema(withCustomRefTypes, this.name.tsType);
|
|
18
|
+
return isDeprecated(this.schemaObject)
|
|
19
|
+
? ensureDeprecatedTypeAliasComment(compiled, this.name.tsType)
|
|
20
|
+
: compiled;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
23
|
* Compiles the schema for usage in _request_ position.
|
|
@@ -29,7 +33,10 @@ export class JSONSchema {
|
|
|
29
33
|
async compileAsRequestInput(opts, dateTimeInputSchemaNames) {
|
|
30
34
|
const widened = widenDateTimeInputs(this.schemaObject);
|
|
31
35
|
const withCustomRefTypes = componentRefsToCustomTypes(opts.rootNamespace, widened, false, dateTimeInputRefTSNameResolver(dateTimeInputSchemaNames));
|
|
32
|
-
|
|
36
|
+
const compiled = await compileJsonSchema(withCustomRefTypes, this.name.tsType);
|
|
37
|
+
return isDeprecated(this.schemaObject)
|
|
38
|
+
? ensureDeprecatedTypeAliasComment(compiled, this.name.tsType)
|
|
39
|
+
: compiled;
|
|
33
40
|
}
|
|
34
41
|
clone() {
|
|
35
42
|
return new JSONSchema(new Name(this.name.raw), cloneDeep(this.schemaObject));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { JSONSchema } from "../../global/JSONSchema.js";
|
|
2
2
|
import { Name } from "../../global/Name.js";
|
|
3
3
|
import { isRef } from "../../../refs/isRef.js";
|
|
4
|
+
import { withDeprecation } from "../../../deprecation.js";
|
|
4
5
|
import invariant from "invariant";
|
|
5
6
|
export class RequestParameters {
|
|
6
7
|
static ns = "Parameters";
|
|
@@ -26,7 +27,9 @@ export class RequestParameters {
|
|
|
26
27
|
? parameter
|
|
27
28
|
: parameter.schema;
|
|
28
29
|
invariant(!!jsonSchemaObject, `Could not find schema for request parameter ${name.raw}`);
|
|
29
|
-
|
|
30
|
+
// `deprecated` lives on the Parameter Object, not on its schema, so it
|
|
31
|
+
// has to be carried over explicitly to end up in the generated JSDoc.
|
|
32
|
+
properties[resolvedParameter.name] = withDeprecation(jsonSchemaObject, resolvedParameter);
|
|
30
33
|
if (resolvedParameter.required) {
|
|
31
34
|
required.push(resolvedParameter.name);
|
|
32
35
|
}
|
|
@@ -8,6 +8,19 @@ const getComponentRef = (something) => {
|
|
|
8
8
|
return something.$ref;
|
|
9
9
|
}
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* A `deprecated` annotation next to a `$ref` is put there by our own code
|
|
13
|
+
* model, not by the input document: the OpenAPI 3.0 meta schema rejects any
|
|
14
|
+
* sibling of a `$ref`, so a document that carries one never reaches this point.
|
|
15
|
+
* `RequestParameters` and `Parameters` copy the flag from a Parameter Object
|
|
16
|
+
* onto the referencing schema, because only the schema is handed to the
|
|
17
|
+
* TypeScript compiler. It describes the referencing property rather than the
|
|
18
|
+
* referenced component, so it has to survive the conversion into a custom
|
|
19
|
+
* `tsType` reference.
|
|
20
|
+
*/
|
|
21
|
+
const getRefSiblingAnnotations = (something) => something["deprecated"] === undefined
|
|
22
|
+
? {}
|
|
23
|
+
: { deprecated: something["deprecated"] };
|
|
11
24
|
export const componentRefsToCustomTypes = (rootNamespace, something, clone = true, resolveTSName = refNameToTSName) => {
|
|
12
25
|
if (clone) {
|
|
13
26
|
something = cloneDeep(something);
|
|
@@ -22,6 +35,7 @@ export const componentRefsToCustomTypes = (rootNamespace, something, clone = tru
|
|
|
22
35
|
if (componentRef !== undefined) {
|
|
23
36
|
// see https://github.com/bcherny/json-schema-to-typescript#custom-schema-properties
|
|
24
37
|
return {
|
|
38
|
+
...getRefSiblingAnnotations(something),
|
|
25
39
|
tsType: resolveTSName(rootNamespace, componentRef),
|
|
26
40
|
type: "object",
|
|
27
41
|
};
|
|
@@ -38,6 +38,17 @@ test.each([
|
|
|
38
38
|
},
|
|
39
39
|
},
|
|
40
40
|
],
|
|
41
|
+
[
|
|
42
|
+
{
|
|
43
|
+
$ref: "#/components/foo/bar",
|
|
44
|
+
deprecated: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: "object",
|
|
48
|
+
tsType: "Test.Components.Foo.Bar",
|
|
49
|
+
deprecated: true,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
41
52
|
])("componentRefsToCustomTypes works for test %#", (input, expected) => {
|
|
42
53
|
const result = componentRefsToCustomTypes("test", input);
|
|
43
54
|
expect(result).toMatchObject(expected);
|
|
@@ -40,12 +40,14 @@ export class UniversalContentLoader {
|
|
|
40
40
|
try {
|
|
41
41
|
return await UniversalContentLoader.parseJson(content);
|
|
42
42
|
}
|
|
43
|
-
catch
|
|
43
|
+
catch {
|
|
44
44
|
try {
|
|
45
45
|
return await UniversalContentLoader.parseYaml(content);
|
|
46
46
|
}
|
|
47
47
|
catch (error) {
|
|
48
|
-
throw new Error("Content is not of supported format JSON/YAML."
|
|
48
|
+
throw new Error("Content is not of supported format JSON/YAML.", {
|
|
49
|
+
cause: error,
|
|
50
|
+
});
|
|
49
51
|
}
|
|
50
52
|
}
|
|
51
53
|
}
|
|
@@ -49,6 +49,88 @@ test("unknown schema keywords are still rejected", async () => {
|
|
|
49
49
|
Broken: { type: "string", definitelyNotAKeyword: true },
|
|
50
50
|
}))).rejects.toThrow(/Failed parsing OpenAPISpec/);
|
|
51
51
|
});
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
|
+
const buildDoc = (overrides = {}) => ({
|
|
54
|
+
openapi: "3.0.3",
|
|
55
|
+
info: { title: "Test API", version: "1.0.0" },
|
|
56
|
+
paths: {},
|
|
57
|
+
...overrides,
|
|
58
|
+
});
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
60
|
+
const docWithSchema = (schema) => buildDoc({
|
|
61
|
+
components: {
|
|
62
|
+
schemas: {
|
|
63
|
+
Conversation: schema,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
const docWithOperation = (operation) => buildDoc({
|
|
69
|
+
paths: {
|
|
70
|
+
"/conversations": {
|
|
71
|
+
get: {
|
|
72
|
+
operationId: "conversation-list",
|
|
73
|
+
responses: { "200": { description: "OK" } },
|
|
74
|
+
...operation,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
test("a deprecated schema property does not invalidate the spec", async () => {
|
|
80
|
+
const doc = docWithSchema({
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
lastMessageAt: {
|
|
84
|
+
type: "string",
|
|
85
|
+
format: "date-time",
|
|
86
|
+
deprecated: true,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
await expect(OpenApiSpec.parse(doc)).resolves.toBeInstanceOf(OpenApiSpec);
|
|
91
|
+
});
|
|
92
|
+
test("a deprecated schema object does not invalidate the spec", async () => {
|
|
93
|
+
const doc = docWithSchema({
|
|
94
|
+
type: "object",
|
|
95
|
+
deprecated: true,
|
|
96
|
+
properties: { id: { type: "string" } },
|
|
97
|
+
});
|
|
98
|
+
await expect(OpenApiSpec.parse(doc)).resolves.toBeInstanceOf(OpenApiSpec);
|
|
99
|
+
});
|
|
100
|
+
test("a deprecated operation does not invalidate the spec", async () => {
|
|
101
|
+
const doc = docWithOperation({ deprecated: true });
|
|
102
|
+
await expect(OpenApiSpec.parse(doc)).resolves.toBeInstanceOf(OpenApiSpec);
|
|
103
|
+
});
|
|
104
|
+
test("a deprecated parameter does not invalidate the spec", async () => {
|
|
105
|
+
const doc = docWithOperation({
|
|
106
|
+
parameters: [
|
|
107
|
+
{
|
|
108
|
+
name: "legacyFilter",
|
|
109
|
+
in: "query",
|
|
110
|
+
deprecated: true,
|
|
111
|
+
schema: { type: "string" },
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
});
|
|
115
|
+
await expect(OpenApiSpec.parse(doc)).resolves.toBeInstanceOf(OpenApiSpec);
|
|
116
|
+
});
|
|
117
|
+
test("a deprecated response header does not invalidate the spec", async () => {
|
|
118
|
+
const doc = docWithOperation({
|
|
119
|
+
responses: {
|
|
120
|
+
"200": {
|
|
121
|
+
description: "OK",
|
|
122
|
+
headers: {
|
|
123
|
+
"X-Legacy": { deprecated: true, schema: { type: "string" } },
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
await expect(OpenApiSpec.parse(doc)).resolves.toBeInstanceOf(OpenApiSpec);
|
|
129
|
+
});
|
|
130
|
+
test("unknown keywords are still rejected", async () => {
|
|
131
|
+
const doc = docWithOperation({ bogusUnknownKey: true });
|
|
132
|
+
await expect(OpenApiSpec.parse(doc)).rejects.toThrow(/OpenAPI is invalid/);
|
|
133
|
+
});
|
|
52
134
|
describe("parsing a valid v3 document", () => {
|
|
53
135
|
test("keeps the document as-is", async () => {
|
|
54
136
|
const doc = makePetstoreDoc();
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { JSONSchema as JSONSchemaObject } from "json-schema-to-typescript";
|
|
2
|
+
/**
|
|
3
|
+
* Anything that may carry the OpenAPI/JSON Schema `deprecated` annotation:
|
|
4
|
+
* schema objects, parameter objects, header objects and operation objects.
|
|
5
|
+
*/
|
|
6
|
+
export interface MaybeDeprecated {
|
|
7
|
+
deprecated?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const isDeprecated: (doc: MaybeDeprecated | undefined) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Copies a `deprecated: true` annotation from an OpenAPI object onto the JSON
|
|
12
|
+
* schema that is generated for it. Used where the annotation lives on the
|
|
13
|
+
* OpenAPI object (e.g. a Parameter Object) rather than on its `schema`, because
|
|
14
|
+
* only the schema is handed to the TypeScript compiler.
|
|
15
|
+
*/
|
|
16
|
+
export declare const withDeprecation: <T extends JSONSchemaObject>(schema: T, source: MaybeDeprecated | undefined) => T | (T & {
|
|
17
|
+
deprecated: true;
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* `json-schema-to-typescript` emits an `@deprecated` JSDoc tag for interface
|
|
21
|
+
* members and for standalone interfaces, but drops it for standalone type
|
|
22
|
+
* aliases: its `generateStandaloneType` calls the comment generator without the
|
|
23
|
+
* deprecation flag, so `export type Foo = string` ends up with an empty `/**
|
|
24
|
+
* *\/` block. We add the tag back so every generated declaration carries the
|
|
25
|
+
* deprecation.
|
|
26
|
+
*/
|
|
27
|
+
export declare const ensureDeprecatedTypeAliasComment: (compiled: string, tsTypeName: string) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mittwald/api-code-generator",
|
|
3
|
-
"version": "0.0.0-development-
|
|
3
|
+
"version": "0.0.0-development-34bc8ae-20261013",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": "https://github.com/mittwald/api-client-js.git",
|
|
6
6
|
"license": "MIT",
|
|
@@ -58,15 +58,15 @@
|
|
|
58
58
|
"zod-validation-error": "^3.5.3"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
+
"@eslint/js": "^10.0.1",
|
|
61
62
|
"@jest/globals": "^29.7.0",
|
|
62
63
|
"@types/jest": "^29.5.14",
|
|
63
64
|
"@types/swagger2openapi": "^7.0.4",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"eslint": "^
|
|
67
|
-
"eslint-
|
|
68
|
-
"
|
|
69
|
-
"eslint-plugin-prettier": "^5.5.4",
|
|
65
|
+
"eslint": "^10.9.1",
|
|
66
|
+
"eslint-config-prettier": "^10.1.8",
|
|
67
|
+
"eslint-plugin-json": "^5.0.0",
|
|
68
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
69
|
+
"globals": "^17.11.0",
|
|
70
70
|
"jest": "^29.7.0",
|
|
71
71
|
"prettier": "^3.6.2",
|
|
72
72
|
"prettier-plugin-jsdoc": "^1.3.3",
|
|
@@ -74,7 +74,8 @@
|
|
|
74
74
|
"prettier-plugin-sort-json": "^3.1.0",
|
|
75
75
|
"rimraf": "^5.0.10",
|
|
76
76
|
"ts-jest": "^29.2.5",
|
|
77
|
-
"typescript": "^5.7.2"
|
|
77
|
+
"typescript": "^5.7.2",
|
|
78
|
+
"typescript-eslint": "^8.68.0"
|
|
78
79
|
},
|
|
79
80
|
"oclif": {
|
|
80
81
|
"bin": "acg",
|
|
@@ -85,5 +86,5 @@
|
|
|
85
86
|
"@oclif/plugin-plugins"
|
|
86
87
|
]
|
|
87
88
|
},
|
|
88
|
-
"gitHead": "
|
|
89
|
+
"gitHead": "b4795a8b89bf28874a654c71cbb70d9f61f81516"
|
|
89
90
|
}
|