oazapfts 4.3.3 → 4.4.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 +93 -32
- package/lib/codegen/generate.d.ts +12 -12
- package/lib/codegen/generate.js +117 -67
- package/lib/codegen/generate.js.map +1 -1
- package/lib/codegen/generate.test.js +17 -126
- package/lib/codegen/generate.test.js.map +1 -1
- package/lib/codegen/generateServers.js +10 -28
- package/lib/codegen/generateServers.js.map +1 -1
- package/lib/codegen/index.d.ts +6 -2
- package/lib/codegen/index.js +29 -16
- package/lib/codegen/index.js.map +1 -1
- package/lib/codegen/index.test.d.ts +1 -0
- package/lib/codegen/index.test.js +115 -0
- package/lib/codegen/index.test.js.map +1 -0
- package/lib/codegen/tscodegen.d.ts +12 -3
- package/lib/codegen/tscodegen.js +25 -22
- package/lib/codegen/tscodegen.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js.map +1 -1
- package/lib/runtime/index.d.ts +9 -5
- package/lib/runtime/index.js +10 -13
- package/lib/runtime/index.js.map +1 -1
- package/lib/runtime/query.d.ts +1 -0
- package/lib/runtime/query.js +12 -1
- package/lib/runtime/query.js.map +1 -1
- package/package.json +2 -2
- package/src/codegen/__fixtures__/allOf.json +80 -0
- package/src/codegen/__fixtures__/binary.json +43 -0
- package/src/codegen/__fixtures__/const.json +64 -0
- package/src/codegen/__fixtures__/contentParams.json +56 -0
- package/src/codegen/__fixtures__/duplicateIdentifiers.yaml +27 -0
- package/src/codegen/__fixtures__/geojson.json +83 -0
- package/src/codegen/__fixtures__/integer.yaml +1 -0
- package/src/codegen/__fixtures__/invalidIdentifiers.yaml +24 -0
- package/src/codegen/__fixtures__/oneOfRef.yaml +24 -0
- package/src/codegen/__fixtures__/string.yaml +1 -0
- package/src/codegen/generate.test.ts +17 -141
- package/src/codegen/generate.ts +141 -85
- package/src/codegen/generateServers.ts +19 -42
- package/src/codegen/index.test.ts +118 -0
- package/src/codegen/index.ts +27 -15
- package/src/codegen/tscodegen.ts +29 -37
- package/src/index.ts +6 -2
- package/src/runtime/index.ts +14 -15
- package/src/runtime/query.ts +14 -0
package/README.md
CHANGED
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
Generate TypeScript clients to tap into OpenAPI servers.
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
11
|
- **AST-based**:
|
|
10
12
|
Unlike other code generators `oazapfts` does not use templates to generate code but uses TypeScript's built-in API to generate and pretty-print an abstract syntax tree.
|
|
11
|
-
- **Fast**: The
|
|
13
|
+
- **Fast**: The CLI does not use any of the common Java-based tooling, so the code generation is super fast.
|
|
14
|
+
- **Single file**: All functions and types are co-located in one single self-contained file.
|
|
12
15
|
- **Tree-shakeable**: Individually exported functions allow you to bundle only the ones you actually use.
|
|
13
|
-
- **Human friendly signatures**: The generated
|
|
16
|
+
- **Human friendly signatures**: The generated API methods don't leak any HTTP-specific implementation details. For example, all optional parameters are grouped together in one object, no matter whether they end up in the headers, path or query-string.
|
|
14
17
|
|
|
15
18
|
## Installation
|
|
16
19
|
|
|
@@ -18,7 +21,8 @@ Generate TypeScript clients to tap into OpenAPI servers.
|
|
|
18
21
|
npm install oazapfts
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
**
|
|
24
|
+
> **Note**
|
|
25
|
+
> With version 3.0.0 oazapfts has become a runtime dependency and the generated code does no longer include all the fetch logic.
|
|
22
26
|
|
|
23
27
|
## Usage
|
|
24
28
|
|
|
@@ -36,36 +40,64 @@ Where `<spec>` is the URL or local path of an OpenAPI or Swagger spec (in either
|
|
|
36
40
|
|
|
37
41
|
Use the `useEnumType` option to generate typescript enums instead of union of values.
|
|
38
42
|
|
|
39
|
-
##
|
|
43
|
+
## Consuming the generated API
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
For each operation defined in the spec the generated API will export a function with a name matching the `operationId`. If no ID is specified, a reasonable name is generated from the HTTP verb and the path.
|
|
42
46
|
|
|
43
47
|
```ts
|
|
44
|
-
import * as api from "./api.ts";
|
|
45
|
-
|
|
48
|
+
import * as api from "./my-generated-api.ts";
|
|
49
|
+
const res = api.getPetById(1);
|
|
50
|
+
```
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
> **Note**
|
|
53
|
+
> If your API is large, and you want to take advantage of tree-shaking to exclude unused code, use individual named imports instead:
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
55
|
+
```ts
|
|
56
|
+
import { getPetById } from "./my-generated-api.ts";
|
|
57
|
+
```
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
## Fetch options
|
|
60
|
+
|
|
61
|
+
The **last argument** of each function is an optional [`RequestOpts`](https://github.com/oazapfts/oazapfts/blob/27b296c6fc28fec4869f1b7e1a4a5585ebbd5ee9/src/runtime/index.ts#L5) object that can be used to pass options to the `fetch` call, for example to pass additional `headers` or an `AbortSignal` to cancel the request later on.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const res = getPetById(1, {
|
|
65
|
+
credentials: "include",
|
|
66
|
+
headers: {
|
|
67
|
+
Authorization: `Bearer ${token}`,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
54
70
|
```
|
|
55
71
|
|
|
56
|
-
|
|
72
|
+
You can also use this to override the default `baseUrl` or to provide a custom `fetch` implementation.
|
|
73
|
+
|
|
74
|
+
> **Note**
|
|
75
|
+
> Instead of passing custom options to each function call, consider [overwriting the global defaults](#overriding-the-defaults).
|
|
76
|
+
|
|
77
|
+
## Optimistic vs. explicit responses
|
|
78
|
+
|
|
79
|
+
Oazapfts supports two different modes to handle results,
|
|
80
|
+
an [explicit](#explicit-mode) mode (the default) and an [optimistic](#optimistic-mode) mode, that makes the response handling less verbose.
|
|
81
|
+
|
|
82
|
+
## Explicit mode
|
|
83
|
+
|
|
84
|
+
By default, each function returns an `ApiResponse` object that exposes the `status` code, response `headers` and the `data`.
|
|
57
85
|
|
|
58
|
-
|
|
86
|
+
> **Note**
|
|
87
|
+
> This mode is best suited for APIs that return different types for different response codes or APIs where you need to access not only the response body, but also the response headers. If your API is simple, and you don't need this flexibility, consider using the [optimistic mode](#optimistic-mode) instead.
|
|
59
88
|
|
|
60
|
-
|
|
89
|
+
In explicit mode, each function returns a Promise for an `ApiResponse` which is an object with a `status` and a `data` property, holding the HTTP status code and the properly typed data from the response body.
|
|
61
90
|
|
|
62
|
-
|
|
91
|
+
Since an operation can return different types depending on the status code, the actual return type is a _union_ of all possible responses, discriminated by their status.
|
|
63
92
|
|
|
64
93
|
Consider the following code generated from the `petstore.json` example:
|
|
65
94
|
|
|
66
95
|
```ts
|
|
67
|
-
|
|
68
|
-
|
|
96
|
+
/**
|
|
97
|
+
* Find pet by ID
|
|
98
|
+
*/
|
|
99
|
+
export function getPetById(petId: number, opts?: Oazapfts.RequestOpts) {
|
|
100
|
+
return oazapfts.fetchJson<
|
|
69
101
|
| {
|
|
70
102
|
status: 200;
|
|
71
103
|
data: Pet;
|
|
@@ -76,15 +108,14 @@ export function getPetById(petId: number, opts?: RequestOpts) {
|
|
|
76
108
|
}
|
|
77
109
|
| {
|
|
78
110
|
status: 404;
|
|
79
|
-
data: string;
|
|
80
111
|
}
|
|
81
|
-
>(`/pet/${petId}`, {
|
|
112
|
+
>(`/pet/${encodeURIComponent(petId)}`, {
|
|
82
113
|
...opts,
|
|
83
114
|
});
|
|
84
115
|
}
|
|
85
116
|
```
|
|
86
117
|
|
|
87
|
-
In this case the `data` property is typed as `Pet|string`. We can use a type guard to narrow down the type to `Pet`:
|
|
118
|
+
In this case, the `data` property is typed as `Pet|string`. We can use a type guard to narrow down the type to `Pet`:
|
|
88
119
|
|
|
89
120
|
```ts
|
|
90
121
|
const res = await api.getPetById(1);
|
|
@@ -115,7 +146,7 @@ await handle(api.getPetById(1), {
|
|
|
115
146
|
});
|
|
116
147
|
```
|
|
117
148
|
|
|
118
|
-
The helper will throw an `HttpError` error for any
|
|
149
|
+
The helper will throw an `HttpError` error for any unhandled status code, unless you add a `default` handler:
|
|
119
150
|
|
|
120
151
|
```ts
|
|
121
152
|
await handle(api.getPetById(1), {
|
|
@@ -128,9 +159,26 @@ await handle(api.getPetById(1), {
|
|
|
128
159
|
});
|
|
129
160
|
```
|
|
130
161
|
|
|
131
|
-
## Optimistic
|
|
162
|
+
## Optimistic mode
|
|
163
|
+
|
|
164
|
+
You can opt into the _optimistic mode_ by using the `--optimistic` command line argument.
|
|
165
|
+
|
|
166
|
+
In this mode, each function will return a Promise for the happy path, i.e. the type specified for the first `2xx` response.
|
|
167
|
+
|
|
168
|
+
Looking back at our Pet Store example from above, consuming the response is now much easier and less verbose:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const pet = await api.getPetById(1);
|
|
172
|
+
// pet is now typed as Pet!
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
In case of a response other than `200` the promise will be rejected with a `HttpError`.
|
|
132
176
|
|
|
133
|
-
|
|
177
|
+
## Mixing both modes
|
|
178
|
+
|
|
179
|
+
Sometimes you might want to use the optimistic mode for some of your API calls, but need the full `ApiResponse` for others.
|
|
180
|
+
|
|
181
|
+
In that case, you can use the `ok`-helper function to selectively apply optimistic response handling:
|
|
134
182
|
|
|
135
183
|
```ts
|
|
136
184
|
import { ok } from "oazapfts";
|
|
@@ -138,21 +186,34 @@ import { ok } from "oazapfts";
|
|
|
138
186
|
const pet = await ok(api.getPetById(1));
|
|
139
187
|
```
|
|
140
188
|
|
|
141
|
-
|
|
189
|
+
## Overriding the defaults
|
|
142
190
|
|
|
143
|
-
|
|
191
|
+
The generated file exports a `defaults` constant that can be used to override the `basePath`, provide a custom `fetch` implementation or to send additional `headers` with each request. Basically, you can set a default for any [fetch option](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) you want.
|
|
144
192
|
|
|
145
193
|
```ts
|
|
146
|
-
import
|
|
147
|
-
import
|
|
194
|
+
import * as api from "./api.ts";
|
|
195
|
+
import nodeFetch from "node-fetch";
|
|
148
196
|
|
|
149
|
-
|
|
150
|
-
|
|
197
|
+
// Override the spec's basePath
|
|
198
|
+
api.defaults.basePath = "https://example.com/api";
|
|
199
|
+
|
|
200
|
+
// Send this header with each request
|
|
201
|
+
api.defaults.headers = {
|
|
202
|
+
access_token: "secret",
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Include credentials in CORS requests, too
|
|
206
|
+
api.defaults.credentials = "include";
|
|
207
|
+
|
|
208
|
+
// Use this instead of the global fetch
|
|
209
|
+
api.defaults.fetch = nodeFetch;
|
|
151
210
|
```
|
|
152
211
|
|
|
153
|
-
|
|
212
|
+
## Alternatives and integrations
|
|
213
|
+
|
|
214
|
+
If this library doesn't fit your needs, take a look at [openapi-typescript-codegen](https://github.com/ferdikoomen/openapi-typescript-codegen) which follows a similar philosophy but creates many individual files instead of one single self-contained file.
|
|
154
215
|
|
|
155
|
-
|
|
216
|
+
If your frontend uses React, take a look at [react-api-query](https://www.npmjs.com/package/react-api-query) which makes it easy to use an oazapfts client with React hooks in a convenient and type-safe way.
|
|
156
217
|
|
|
157
218
|
## About the name
|
|
158
219
|
|
|
@@ -2,28 +2,28 @@ import ts from "typescript";
|
|
|
2
2
|
import { OpenAPIV3 } from "openapi-types";
|
|
3
3
|
import { Opts } from ".";
|
|
4
4
|
export declare const verbs: string[];
|
|
5
|
-
|
|
6
|
-
export declare
|
|
5
|
+
export declare function isMimeType(s: unknown): boolean;
|
|
6
|
+
export declare function isJsonMimeType(mime: string): boolean;
|
|
7
7
|
type SchemaObject = OpenAPIV3.SchemaObject & {
|
|
8
8
|
const?: unknown;
|
|
9
9
|
};
|
|
10
10
|
/**
|
|
11
11
|
* Get the name of a formatter function for a given parameter.
|
|
12
12
|
*/
|
|
13
|
-
export declare function getFormatter({ style, explode, }: OpenAPIV3.ParameterObject): "form" | "deep" | "explode" | "space" | "pipe";
|
|
13
|
+
export declare function getFormatter({ style, explode, content, }: OpenAPIV3.ParameterObject): "json" | "form" | "deep" | "explode" | "space" | "pipe";
|
|
14
14
|
export declare function getOperationIdentifier(id?: string): string | undefined;
|
|
15
15
|
/**
|
|
16
16
|
* Create a method name for a given operation, either from its operationId or
|
|
17
17
|
* the HTTP verb and path.
|
|
18
18
|
*/
|
|
19
19
|
export declare function getOperationName(verb: string, path: string, operationId?: string): string;
|
|
20
|
-
export declare function isNullable(schema
|
|
21
|
-
export declare function isReference(obj:
|
|
22
|
-
export declare function getReference(spec: any, ref: string): any;
|
|
20
|
+
export declare function isNullable(schema?: SchemaObject | OpenAPIV3.ReferenceObject): boolean | undefined;
|
|
21
|
+
export declare function isReference(obj: unknown): obj is OpenAPIV3.ReferenceObject;
|
|
23
22
|
/**
|
|
24
23
|
* If the given object is a ReferenceObject, return the last part of its path.
|
|
25
24
|
*/
|
|
26
|
-
export declare function getReferenceName(obj:
|
|
25
|
+
export declare function getReferenceName(obj: unknown): string | undefined;
|
|
26
|
+
export declare function toIdentifier(s: string): string;
|
|
27
27
|
/**
|
|
28
28
|
* Create a template string literal from the given OpenAPI urlTemplate.
|
|
29
29
|
* Curly braces in the path are turned into identifier expressions,
|
|
@@ -69,12 +69,11 @@ export default class ApiGenerator {
|
|
|
69
69
|
skip(tags?: string[]): boolean;
|
|
70
70
|
getUniqueAlias(name: string): string;
|
|
71
71
|
getEnumUniqueAlias(name: string, values: string): string;
|
|
72
|
-
getRefBasename(ref: string): string;
|
|
73
72
|
/**
|
|
74
73
|
* Create a type alias for the schema referenced by the given ReferenceObject
|
|
75
74
|
*/
|
|
76
75
|
getRefAlias(obj: OpenAPIV3.ReferenceObject): ts.TypeReferenceNode;
|
|
77
|
-
getUnionType(variants: (OpenAPIV3.ReferenceObject | SchemaObject)[], discriminator?: OpenAPIV3.DiscriminatorObject): ts.
|
|
76
|
+
getUnionType(variants: (OpenAPIV3.ReferenceObject | SchemaObject)[], discriminator?: OpenAPIV3.DiscriminatorObject): ts.UnionTypeNode;
|
|
78
77
|
/**
|
|
79
78
|
* Creates a type node from a given schema.
|
|
80
79
|
* Delegates to getBaseTypeFromSchema internally and
|
|
@@ -89,7 +88,7 @@ export default class ApiGenerator {
|
|
|
89
88
|
/**
|
|
90
89
|
* Creates literal type (or union) from an array of values
|
|
91
90
|
*/
|
|
92
|
-
getTypeFromEnum(values: unknown[]): ts.LiteralTypeNode | ts.
|
|
91
|
+
getTypeFromEnum(values: unknown[]): ts.LiteralTypeNode | ts.UnionTypeNode;
|
|
93
92
|
getEnumValuesString(values: string[]): string;
|
|
94
93
|
getTrueEnum(schema: OpenAPIV3.NonArraySchemaObject, propName: string): ts.TypeReferenceNode;
|
|
95
94
|
/**
|
|
@@ -97,11 +96,12 @@ export default class ApiGenerator {
|
|
|
97
96
|
*/
|
|
98
97
|
getTypeFromProperties(props: {
|
|
99
98
|
[prop: string]: SchemaObject | OpenAPIV3.ReferenceObject;
|
|
100
|
-
}, required?: string[], additionalProperties?: boolean | SchemaObject | OpenAPIV3.ReferenceObject): ts.TypeLiteralNode;
|
|
99
|
+
}, required?: string[], additionalProperties?: boolean | OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject): ts.TypeLiteralNode;
|
|
101
100
|
getTypeFromResponses(responses: OpenAPIV3.ResponsesObject): ts.UnionTypeNode;
|
|
102
101
|
getTypeFromResponse(resOrRef: OpenAPIV3.ResponseObject | OpenAPIV3.ReferenceObject): ts.TypeNode;
|
|
103
102
|
getResponseType(responses?: OpenAPIV3.ResponsesObject): "json" | "text" | "blob";
|
|
104
|
-
getSchemaFromContent(content:
|
|
103
|
+
getSchemaFromContent(content: Record<string, OpenAPIV3.MediaTypeObject>): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
|
|
104
|
+
getTypeFromParameter(p: OpenAPIV3.ParameterObject): ts.TypeNode;
|
|
105
105
|
wrapResult(ex: ts.Expression): ts.Expression;
|
|
106
106
|
generateApi(): ts.SourceFile;
|
|
107
107
|
}
|
package/lib/codegen/generate.js
CHANGED
|
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
26
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
27
|
};
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
-
exports.supportDeepObjects = exports.callOazapftsFunction = exports.callQsFunction = exports.createUrlExpression = exports.
|
|
29
|
+
exports.supportDeepObjects = exports.callOazapftsFunction = exports.callQsFunction = exports.createUrlExpression = exports.toIdentifier = exports.getReferenceName = exports.isReference = exports.isNullable = exports.getOperationName = exports.getOperationIdentifier = exports.getFormatter = exports.isJsonMimeType = exports.isMimeType = exports.verbs = void 0;
|
|
30
30
|
const lodash_1 = __importDefault(require("lodash"));
|
|
31
31
|
const typescript_1 = __importStar(require("typescript"));
|
|
32
32
|
const path_1 = __importDefault(require("path"));
|
|
@@ -42,20 +42,34 @@ exports.verbs = [
|
|
|
42
42
|
"PATCH",
|
|
43
43
|
"TRACE",
|
|
44
44
|
];
|
|
45
|
-
|
|
45
|
+
const contentTypes = {
|
|
46
46
|
"*/*": "json",
|
|
47
47
|
"application/json": "json",
|
|
48
|
-
"application/hal+json": "json",
|
|
49
|
-
"application/merge-patch+json": "json",
|
|
50
|
-
"application/problem+json": "json",
|
|
51
|
-
"application/geo+json": "json",
|
|
52
48
|
"application/x-www-form-urlencoded": "form",
|
|
53
49
|
"multipart/form-data": "multipart",
|
|
54
50
|
};
|
|
51
|
+
function isMimeType(s) {
|
|
52
|
+
return typeof s === "string" && /^[^/]+\/[^/]+$/.test(s);
|
|
53
|
+
}
|
|
54
|
+
exports.isMimeType = isMimeType;
|
|
55
|
+
function isJsonMimeType(mime) {
|
|
56
|
+
return contentTypes[mime] === "json" || /\bjson\b/i.test(mime);
|
|
57
|
+
}
|
|
58
|
+
exports.isJsonMimeType = isJsonMimeType;
|
|
55
59
|
/**
|
|
56
60
|
* Get the name of a formatter function for a given parameter.
|
|
57
61
|
*/
|
|
58
|
-
function getFormatter({ style = "form", explode = true, }) {
|
|
62
|
+
function getFormatter({ style = "form", explode = true, content, }) {
|
|
63
|
+
if (content) {
|
|
64
|
+
const medias = Object.keys(content);
|
|
65
|
+
if (medias.length !== 1) {
|
|
66
|
+
throw new Error("Parameters with content property must specify one media type");
|
|
67
|
+
}
|
|
68
|
+
if (!isJsonMimeType(medias[0])) {
|
|
69
|
+
throw new Error("Parameters with content property must specify a JSON compatible media type");
|
|
70
|
+
}
|
|
71
|
+
return "json";
|
|
72
|
+
}
|
|
59
73
|
if (explode && style === "deepObject")
|
|
60
74
|
return "deep";
|
|
61
75
|
if (explode)
|
|
@@ -86,39 +100,52 @@ function getOperationName(verb, path, operationId) {
|
|
|
86
100
|
if (id)
|
|
87
101
|
return id;
|
|
88
102
|
path = path.replace(/\{(.+?)\}/, "by $1").replace(/\{(.+?)\}/, "and $1");
|
|
89
|
-
return
|
|
103
|
+
return toIdentifier(`${verb} ${path}`);
|
|
90
104
|
}
|
|
91
105
|
exports.getOperationName = getOperationName;
|
|
92
106
|
function isNullable(schema) {
|
|
93
|
-
return
|
|
107
|
+
return schema && !isReference(schema) && schema.nullable;
|
|
94
108
|
}
|
|
95
109
|
exports.isNullable = isNullable;
|
|
96
110
|
function isReference(obj) {
|
|
97
|
-
return obj && "$ref" in obj;
|
|
111
|
+
return typeof obj === "object" && obj !== null && "$ref" in obj;
|
|
98
112
|
}
|
|
99
113
|
exports.isReference = isReference;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Get the last path component of the given ref.
|
|
116
|
+
*/
|
|
117
|
+
function getRefBasename(ref) {
|
|
118
|
+
return ref.replace(/.+\//, "");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns a name for the given ref that can be used as basis for a type
|
|
122
|
+
* alias. This usually is the baseName, unless the ref ends with a number,
|
|
123
|
+
* in which case the whole ref is returned, with leading non-word characters
|
|
124
|
+
* being stripped.
|
|
125
|
+
*/
|
|
126
|
+
function getRefName(ref) {
|
|
127
|
+
const base = getRefBasename(ref);
|
|
128
|
+
if (/^\d+/.test(base)) {
|
|
129
|
+
return ref.replace(/^\W+/, "");
|
|
109
130
|
}
|
|
110
|
-
return
|
|
131
|
+
return base;
|
|
111
132
|
}
|
|
112
|
-
exports.getReference = getReference;
|
|
113
133
|
/**
|
|
114
134
|
* If the given object is a ReferenceObject, return the last part of its path.
|
|
115
135
|
*/
|
|
116
136
|
function getReferenceName(obj) {
|
|
117
137
|
if (isReference(obj)) {
|
|
118
|
-
return
|
|
138
|
+
return getRefBasename(obj.$ref);
|
|
119
139
|
}
|
|
120
140
|
}
|
|
121
141
|
exports.getReferenceName = getReferenceName;
|
|
142
|
+
function toIdentifier(s) {
|
|
143
|
+
const cc = lodash_1.default.camelCase(s);
|
|
144
|
+
if (cg.isValidIdentifier(cc))
|
|
145
|
+
return cc;
|
|
146
|
+
return "$" + cc;
|
|
147
|
+
}
|
|
148
|
+
exports.toIdentifier = toIdentifier;
|
|
122
149
|
/**
|
|
123
150
|
* Create a template string literal from the given OpenAPI urlTemplate.
|
|
124
151
|
* Curly braces in the path are turned into identifier expressions,
|
|
@@ -128,7 +155,7 @@ function createUrlExpression(path, qs) {
|
|
|
128
155
|
const spans = [];
|
|
129
156
|
// Use a replacer function to collect spans as a side effect:
|
|
130
157
|
const head = path.replace(/(.*?)\{(.+?)\}(.*?)(?=\{|$)/g, (_substr, head, name, literal) => {
|
|
131
|
-
const expression =
|
|
158
|
+
const expression = toIdentifier(name);
|
|
132
159
|
spans.push({
|
|
133
160
|
expression: cg.createCall(typescript_1.factory.createIdentifier("encodeURIComponent"), { args: [typescript_1.factory.createIdentifier(expression)] }),
|
|
134
161
|
literal,
|
|
@@ -220,7 +247,15 @@ class ApiGenerator {
|
|
|
220
247
|
if (!ref.startsWith("#/")) {
|
|
221
248
|
throw new Error(`External refs are not supported (${ref}). Make sure to call SwaggerParser.bundle() first.`);
|
|
222
249
|
}
|
|
223
|
-
|
|
250
|
+
const path = ref
|
|
251
|
+
.slice(2)
|
|
252
|
+
.split("/")
|
|
253
|
+
.map((s) => decodeURI(s.replace(/~1/g, "/").replace(/~0/g, "~")));
|
|
254
|
+
const resolved = lodash_1.default.get(this.spec, path);
|
|
255
|
+
if (typeof resolved === "undefined") {
|
|
256
|
+
throw new Error(`Can't find ${path}`);
|
|
257
|
+
}
|
|
258
|
+
return resolved;
|
|
224
259
|
}
|
|
225
260
|
resolveArray(array) {
|
|
226
261
|
return array ? array.map((el) => this.resolve(el)) : [];
|
|
@@ -253,9 +288,6 @@ class ApiGenerator {
|
|
|
253
288
|
}
|
|
254
289
|
return this.getUniqueAlias(name);
|
|
255
290
|
}
|
|
256
|
-
getRefBasename(ref) {
|
|
257
|
-
return ref.replace(/.+\//, "");
|
|
258
|
-
}
|
|
259
291
|
/**
|
|
260
292
|
* Create a type alias for the schema referenced by the given ReferenceObject
|
|
261
293
|
*/
|
|
@@ -264,12 +296,14 @@ class ApiGenerator {
|
|
|
264
296
|
let ref = this.refs[$ref];
|
|
265
297
|
if (!ref) {
|
|
266
298
|
const schema = this.resolve(obj);
|
|
267
|
-
const name =
|
|
268
|
-
|
|
299
|
+
const name = schema.title || getRefName($ref);
|
|
300
|
+
const identifier = lodash_1.default.upperFirst(toIdentifier(name));
|
|
301
|
+
const alias = this.getUniqueAlias(identifier);
|
|
302
|
+
ref = this.refs[$ref] = typescript_1.factory.createTypeReferenceNode(alias, undefined);
|
|
269
303
|
const type = this.getTypeFromSchema(schema);
|
|
270
304
|
this.aliases.push(cg.createTypeAliasDeclaration({
|
|
271
305
|
modifiers: [cg.modifier.export],
|
|
272
|
-
name,
|
|
306
|
+
name: alias,
|
|
273
307
|
type,
|
|
274
308
|
}));
|
|
275
309
|
}
|
|
@@ -284,7 +318,7 @@ class ApiGenerator {
|
|
|
284
318
|
// By default, the last component of the ref name (i.e., after the last trailing slash) is
|
|
285
319
|
// used as the discriminator value for each variant. This can be overridden using the
|
|
286
320
|
// discriminator.mapping property.
|
|
287
|
-
const mappedValues = new Set(Object.values(discriminator.mapping || {}).map(
|
|
321
|
+
const mappedValues = new Set(Object.values(discriminator.mapping || {}).map(getRefBasename));
|
|
288
322
|
return typescript_1.factory.createUnionTypeNode([
|
|
289
323
|
...Object.entries(discriminator.mapping || {}).map(([discriminatorValue, variantRef]) => [
|
|
290
324
|
discriminatorValue,
|
|
@@ -297,10 +331,10 @@ class ApiGenerator {
|
|
|
297
331
|
// considered."
|
|
298
332
|
throw new Error("Discriminators require references, not inline schemas");
|
|
299
333
|
}
|
|
300
|
-
return !mappedValues.has(
|
|
334
|
+
return !mappedValues.has(getRefBasename(variant.$ref));
|
|
301
335
|
})
|
|
302
336
|
.map((schema) => [
|
|
303
|
-
|
|
337
|
+
getRefBasename(schema.$ref),
|
|
304
338
|
schema,
|
|
305
339
|
]),
|
|
306
340
|
].map(([discriminatorValue, variant]) =>
|
|
@@ -367,9 +401,10 @@ class ApiGenerator {
|
|
|
367
401
|
return this.getTypeFromProperties(schema.properties || {}, schema.required, schema.additionalProperties);
|
|
368
402
|
}
|
|
369
403
|
if (schema.enum) {
|
|
370
|
-
|
|
404
|
+
// enum -> enum or union
|
|
405
|
+
return this.opts.useEnumType && name && schema.type !== "boolean"
|
|
371
406
|
? this.getTrueEnum(schema, name)
|
|
372
|
-
:
|
|
407
|
+
: cg.createEnumTypeNode(schema.enum);
|
|
373
408
|
}
|
|
374
409
|
if (schema.format == "binary") {
|
|
375
410
|
return typescript_1.factory.createTypeReferenceNode("Blob", []);
|
|
@@ -379,10 +414,10 @@ class ApiGenerator {
|
|
|
379
414
|
}
|
|
380
415
|
if (schema.type) {
|
|
381
416
|
// string, boolean, null, number
|
|
382
|
-
if (schema.type in cg.keywordType)
|
|
383
|
-
return cg.keywordType[schema.type];
|
|
384
417
|
if (schema.type === "integer")
|
|
385
418
|
return cg.keywordType.number;
|
|
419
|
+
if (schema.type in cg.keywordType)
|
|
420
|
+
return cg.keywordType[schema.type];
|
|
386
421
|
}
|
|
387
422
|
return cg.keywordType.any;
|
|
388
423
|
}
|
|
@@ -497,13 +532,13 @@ class ApiGenerator {
|
|
|
497
532
|
return "text";
|
|
498
533
|
const resolvedResponses = Object.values(responses).map((response) => this.resolve(response));
|
|
499
534
|
// if no content is specified, assume `text` (backwards-compatibility)
|
|
500
|
-
if (!resolvedResponses.some((res) => { var _a; return Object.keys((_a = res.content) !== null && _a !== void 0 ? _a :
|
|
535
|
+
if (!resolvedResponses.some((res) => { var _a; return Object.keys((_a = res.content) !== null && _a !== void 0 ? _a : {}).length > 0; })) {
|
|
501
536
|
return "text";
|
|
502
537
|
}
|
|
503
538
|
const isJson = resolvedResponses.some((response) => {
|
|
504
539
|
var _a;
|
|
505
540
|
const responseMimeTypes = Object.keys((_a = response.content) !== null && _a !== void 0 ? _a : {});
|
|
506
|
-
return responseMimeTypes.some(
|
|
541
|
+
return responseMimeTypes.some(isJsonMimeType);
|
|
507
542
|
});
|
|
508
543
|
// if there’s `application/json` or `*/*`, assume `json`
|
|
509
544
|
if (isJson) {
|
|
@@ -517,13 +552,12 @@ class ApiGenerator {
|
|
|
517
552
|
return "blob";
|
|
518
553
|
}
|
|
519
554
|
getSchemaFromContent(content) {
|
|
520
|
-
const contentType = Object.keys(
|
|
521
|
-
let schema;
|
|
555
|
+
const contentType = Object.keys(content).find(isMimeType);
|
|
522
556
|
if (contentType) {
|
|
523
|
-
schema =
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
557
|
+
const { schema } = content[contentType];
|
|
558
|
+
if (schema) {
|
|
559
|
+
return schema;
|
|
560
|
+
}
|
|
527
561
|
}
|
|
528
562
|
// if no content is specified -> string
|
|
529
563
|
// `text/*` -> string
|
|
@@ -534,6 +568,13 @@ class ApiGenerator {
|
|
|
534
568
|
// rest (e.g. `application/octet-stream`, `application/gzip`, …) -> binary
|
|
535
569
|
return { type: "string", format: "binary" };
|
|
536
570
|
}
|
|
571
|
+
getTypeFromParameter(p) {
|
|
572
|
+
if (p.content) {
|
|
573
|
+
const schema = this.getSchemaFromContent(p.content);
|
|
574
|
+
return this.getTypeFromSchema(schema);
|
|
575
|
+
}
|
|
576
|
+
return this.getTypeFromSchema(isReference(p) ? p : p.schema);
|
|
577
|
+
}
|
|
537
578
|
wrapResult(ex) {
|
|
538
579
|
var _a;
|
|
539
580
|
return ((_a = this.opts) === null || _a === void 0 ? void 0 : _a.optimistic) ? callOazapftsFunction("ok", [ex]) : ex;
|
|
@@ -581,10 +622,13 @@ class ApiGenerator {
|
|
|
581
622
|
name += count;
|
|
582
623
|
}
|
|
583
624
|
// merge item and op parameters
|
|
584
|
-
const resolvedParameters =
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
625
|
+
const resolvedParameters = this.resolveArray(item.parameters);
|
|
626
|
+
for (const p of this.resolveArray(op.parameters)) {
|
|
627
|
+
const existing = resolvedParameters.find((r) => r.name === p.name && r.in === p.in);
|
|
628
|
+
if (!existing) {
|
|
629
|
+
resolvedParameters.push(p);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
588
632
|
// expand older OpenAPI parameters into deepObject style where needed
|
|
589
633
|
const parameters = this.isConverted
|
|
590
634
|
? supportDeepObjects(resolvedParameters)
|
|
@@ -592,16 +636,24 @@ class ApiGenerator {
|
|
|
592
636
|
// split into required/optional
|
|
593
637
|
const [required, optional] = lodash_1.default.partition(parameters, "required");
|
|
594
638
|
// convert parameter names to argument names ...
|
|
595
|
-
const argNames =
|
|
596
|
-
parameters
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
.
|
|
600
|
-
|
|
639
|
+
const argNames = new Map();
|
|
640
|
+
lodash_1.default.sortBy(parameters, "name.length").forEach((p) => {
|
|
641
|
+
const identifier = toIdentifier(p.name);
|
|
642
|
+
const existing = [...argNames.values()];
|
|
643
|
+
const suffix = existing.includes(identifier)
|
|
644
|
+
? lodash_1.default.upperFirst(p.in)
|
|
645
|
+
: "";
|
|
646
|
+
argNames.set(p, identifier + suffix);
|
|
601
647
|
});
|
|
648
|
+
const getArgName = (param) => {
|
|
649
|
+
const name = argNames.get(param);
|
|
650
|
+
if (!name)
|
|
651
|
+
throw new Error(`Can't find parameter: ${param.name}`);
|
|
652
|
+
return name;
|
|
653
|
+
};
|
|
602
654
|
// build the method signature - first all the required parameters
|
|
603
|
-
const methodParams = required.map((p) => cg.createParameter(
|
|
604
|
-
type: this.
|
|
655
|
+
const methodParams = required.map((p) => cg.createParameter(getArgName(this.resolve(p)), {
|
|
656
|
+
type: this.getTypeFromParameter(p),
|
|
605
657
|
}));
|
|
606
658
|
let body;
|
|
607
659
|
let bodyVar;
|
|
@@ -610,7 +662,7 @@ class ApiGenerator {
|
|
|
610
662
|
body = this.resolve(requestBody);
|
|
611
663
|
const schema = this.getSchemaFromContent(body.content);
|
|
612
664
|
const type = this.getTypeFromSchema(schema);
|
|
613
|
-
bodyVar =
|
|
665
|
+
bodyVar = toIdentifier(type.name || getReferenceName(schema) || "body");
|
|
614
666
|
methodParams.push(cg.createParameter(bodyVar, {
|
|
615
667
|
type,
|
|
616
668
|
questionToken: !body.required,
|
|
@@ -620,12 +672,12 @@ class ApiGenerator {
|
|
|
620
672
|
if (optional.length) {
|
|
621
673
|
methodParams.push(cg.createParameter(cg.createObjectBinding(optional
|
|
622
674
|
.map((param) => this.resolve(param))
|
|
623
|
-
.map((
|
|
675
|
+
.map((param) => ({ name: getArgName(param) }))), {
|
|
624
676
|
initializer: typescript_1.factory.createObjectLiteralExpression(),
|
|
625
677
|
type: typescript_1.factory.createTypeLiteralNode(optional.map((p) => cg.createPropertySignature({
|
|
626
|
-
name:
|
|
678
|
+
name: getArgName(this.resolve(p)),
|
|
627
679
|
questionToken: true,
|
|
628
|
-
type: this.
|
|
680
|
+
type: this.getTypeFromParameter(p),
|
|
629
681
|
}))),
|
|
630
682
|
}));
|
|
631
683
|
}
|
|
@@ -636,16 +688,14 @@ class ApiGenerator {
|
|
|
636
688
|
// Next, build the method body...
|
|
637
689
|
const returnType = this.getResponseType(responses);
|
|
638
690
|
const query = parameters.filter((p) => p.in === "query");
|
|
639
|
-
const header = parameters
|
|
640
|
-
.filter((p) => p.in === "header")
|
|
641
|
-
.map((p) => p.name);
|
|
691
|
+
const header = parameters.filter((p) => p.in === "header");
|
|
642
692
|
let qs;
|
|
643
693
|
if (query.length) {
|
|
644
694
|
const paramsByFormatter = lodash_1.default.groupBy(query, getFormatter);
|
|
645
695
|
qs = callQsFunction("query", Object.entries(paramsByFormatter).map(([format, params]) => {
|
|
646
696
|
//const [allowReserved, encodeReserved] = _.partition(params, "allowReserved");
|
|
647
697
|
return callQsFunction(format, [
|
|
648
|
-
cg.createObjectLiteral(params.map((p) => [p.name,
|
|
698
|
+
cg.createObjectLiteral(params.map((p) => [p.name, getArgName(p)])),
|
|
649
699
|
]);
|
|
650
700
|
}));
|
|
651
701
|
}
|
|
@@ -662,12 +712,12 @@ class ApiGenerator {
|
|
|
662
712
|
if (header.length) {
|
|
663
713
|
init.push(typescript_1.factory.createPropertyAssignment("headers", typescript_1.factory.createObjectLiteralExpression([
|
|
664
714
|
typescript_1.factory.createSpreadAssignment(typescript_1.factory.createLogicalAnd(typescript_1.factory.createIdentifier("opts"), typescript_1.factory.createPropertyAccessExpression(typescript_1.factory.createIdentifier("opts"), "headers"))),
|
|
665
|
-
...header.map((
|
|
715
|
+
...header.map((param) => cg.createPropertyAssignment(param.name, typescript_1.factory.createIdentifier(getArgName(param)))),
|
|
666
716
|
], true)));
|
|
667
717
|
}
|
|
668
718
|
const args = [url];
|
|
669
719
|
if (init.length) {
|
|
670
|
-
const m = Object.entries(
|
|
720
|
+
const m = Object.entries(contentTypes).find(([type]) => {
|
|
671
721
|
return !!lodash_1.default.get(body, ["content", type]);
|
|
672
722
|
});
|
|
673
723
|
const initObj = typescript_1.factory.createObjectLiteralExpression(init, true);
|