@travetto/openapi 6.0.0-rc.1 → 6.0.0-rc.3
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 +7 -7
- package/__index__.ts +3 -3
- package/package.json +7 -7
- package/src/config.ts +3 -3
- package/src/controller.ts +2 -2
- package/src/service.ts +6 -6
- package/src/spec-generate.ts +25 -44
- package/support/bin/help.ts +1 -1
- package/support/cli.openapi_client.ts +1 -1
- package/support/cli.openapi_spec.ts +1 -1
package/README.md
CHANGED
|
@@ -13,9 +13,9 @@ npm install @travetto/openapi
|
|
|
13
13
|
yarn add @travetto/openapi
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
In the [
|
|
16
|
+
In the [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative api for Web Applications with support for the dependency injection.") module, the controllers and endpoints can be described via decorators, comments, or typings. This only provides the general metadata internally. This is not sufficient to generate a usable API doc, and so this module exists to bridge that gap.
|
|
17
17
|
|
|
18
|
-
The module is provides an [OpenAPI](https://github.com/OAI/OpenAPI-Specification) v3.x representation of the API metadata provided via the [
|
|
18
|
+
The module is provides an [OpenAPI](https://github.com/OAI/OpenAPI-Specification) v3.x representation of the API metadata provided via the [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative api for Web Applications with support for the dependency injection.") and [Schema](https://github.com/travetto/travetto/tree/main/module/schema#readme "Data type registry for runtime validation, reflection and binding.") modules.
|
|
19
19
|
|
|
20
20
|
## Configuration
|
|
21
21
|
By installing the dependency, the [OpenAPI](https://github.com/OAI/OpenAPI-Specification) endpoint is automatically generated and exposed at the root of the application as `/openapi.yml` or `/openapi.json` (by default).
|
|
@@ -58,7 +58,7 @@ export class ApiInfoConfig {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
|
-
* The API host, infers from
|
|
61
|
+
* The API host, infers from web host configuration
|
|
62
62
|
*/
|
|
63
63
|
@Config('api.host')
|
|
64
64
|
export class ApiHostConfig {
|
|
@@ -86,9 +86,9 @@ export class ApiSpecConfig {
|
|
|
86
86
|
*/
|
|
87
87
|
persist?: boolean;
|
|
88
88
|
/**
|
|
89
|
-
* Skip emitting all
|
|
89
|
+
* Skip emitting all endpoints
|
|
90
90
|
*/
|
|
91
|
-
|
|
91
|
+
skipEndpoints: boolean = false;
|
|
92
92
|
/**
|
|
93
93
|
* Expose all schemas, even if not referenced
|
|
94
94
|
*/
|
|
@@ -128,9 +128,9 @@ Options:
|
|
|
128
128
|
-h, --help display help for command
|
|
129
129
|
```
|
|
130
130
|
|
|
131
|
-
The command will run your application, in non-server mode, to collect all the
|
|
131
|
+
The command will run your application, in non-server mode, to collect all the endpoints and model information, to produce the `openapi.yml`. Once produced, the code will store the output in the specified location.
|
|
132
132
|
|
|
133
|
-
**Note**: The module supports generating the OpenAPI spec in real-time while listening for changes to
|
|
133
|
+
**Note**: The module supports generating the OpenAPI spec in real-time while listening for changes to endpoints and models.
|
|
134
134
|
|
|
135
135
|
## CLI - openapi:client
|
|
136
136
|
The module provides a command for the [Command Line Interface](https://github.com/travetto/travetto/tree/main/module/cli#readme "CLI infrastructure for Travetto framework") to allow client generation from the API structure.
|
package/__index__.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export * from './src/spec-generate';
|
|
2
|
-
export * from './src/service';
|
|
3
|
-
export * from './src/config';
|
|
1
|
+
export * from './src/spec-generate.ts';
|
|
2
|
+
export * from './src/service.ts';
|
|
3
|
+
export * from './src/config.ts';
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/openapi",
|
|
3
|
-
"version": "6.0.0-rc.
|
|
3
|
+
"version": "6.0.0-rc.3",
|
|
4
4
|
"description": "OpenAPI integration support for the Travetto framework",
|
|
5
5
|
"keywords": [
|
|
6
|
-
"
|
|
6
|
+
"web",
|
|
7
7
|
"travetto",
|
|
8
8
|
"decorators",
|
|
9
9
|
"schema",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"directory": "module/openapi"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^6.0.0-rc.
|
|
30
|
-
"@travetto/
|
|
31
|
-
"@travetto/
|
|
29
|
+
"@travetto/config": "^6.0.0-rc.2",
|
|
30
|
+
"@travetto/schema": "^6.0.0-rc.2",
|
|
31
|
+
"@travetto/web": "^6.0.0-rc.3",
|
|
32
32
|
"openapi3-ts": "^4.4.0",
|
|
33
|
-
"yaml": "^2.7.
|
|
33
|
+
"yaml": "^2.7.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/cli": "^6.0.0-rc.
|
|
36
|
+
"@travetto/cli": "^6.0.0-rc.3"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
package/src/config.ts
CHANGED
|
@@ -32,7 +32,7 @@ export class ApiInfoConfig {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* The API host, infers from
|
|
35
|
+
* The API host, infers from web host configuration
|
|
36
36
|
*/
|
|
37
37
|
@Config('api.host')
|
|
38
38
|
export class ApiHostConfig {
|
|
@@ -60,9 +60,9 @@ export class ApiSpecConfig {
|
|
|
60
60
|
*/
|
|
61
61
|
persist?: boolean;
|
|
62
62
|
/**
|
|
63
|
-
* Skip emitting all
|
|
63
|
+
* Skip emitting all endpoints
|
|
64
64
|
*/
|
|
65
|
-
|
|
65
|
+
skipEndpoints: boolean = false;
|
|
66
66
|
/**
|
|
67
67
|
* Expose all schemas, even if not referenced
|
|
68
68
|
*/
|
package/src/controller.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { stringify } from 'yaml';
|
|
2
2
|
|
|
3
|
-
import { ConfigureInterceptor, Controller, CorsInterceptor, Get, SetHeaders, Undocumented } from '@travetto/
|
|
3
|
+
import { ConfigureInterceptor, Controller, CorsInterceptor, Get, SetHeaders, Undocumented } from '@travetto/web';
|
|
4
4
|
import { Inject } from '@travetto/di';
|
|
5
5
|
|
|
6
|
-
import { OpenApiService } from './service';
|
|
6
|
+
import { OpenApiService } from './service.ts';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Basic controller for surfacing the api spec
|
package/src/service.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { stringify } from 'yaml';
|
|
|
3
3
|
|
|
4
4
|
import { BinaryUtil } from '@travetto/runtime';
|
|
5
5
|
import { Injectable, Inject } from '@travetto/di';
|
|
6
|
-
import { ControllerRegistry, ControllerVisitUtil,
|
|
6
|
+
import { ControllerRegistry, ControllerVisitUtil, WebConfig } from '@travetto/web';
|
|
7
7
|
import { SchemaRegistry } from '@travetto/schema';
|
|
8
8
|
|
|
9
|
-
import { ApiHostConfig, ApiInfoConfig, ApiSpecConfig } from './config';
|
|
10
|
-
import { OpenapiVisitor } from './spec-generate';
|
|
9
|
+
import { ApiHostConfig, ApiInfoConfig, ApiSpecConfig } from './config.ts';
|
|
10
|
+
import { OpenapiVisitor } from './spec-generate.ts';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Open API generation service
|
|
@@ -25,7 +25,7 @@ export class OpenApiService {
|
|
|
25
25
|
apiSpecConfig: ApiSpecConfig;
|
|
26
26
|
|
|
27
27
|
@Inject()
|
|
28
|
-
|
|
28
|
+
webConfig: WebConfig;
|
|
29
29
|
|
|
30
30
|
#spec: OpenAPIObject | undefined;
|
|
31
31
|
|
|
@@ -46,8 +46,8 @@ export class OpenApiService {
|
|
|
46
46
|
ControllerRegistry.on(() => this.resetSpec());
|
|
47
47
|
SchemaRegistry.on(() => this.resetSpec());
|
|
48
48
|
|
|
49
|
-
if (!this.apiHostConfig.servers) {
|
|
50
|
-
this.apiHostConfig.servers = [{ url: this.
|
|
49
|
+
if (!this.apiHostConfig.servers && this.webConfig.baseUrl) {
|
|
50
|
+
this.apiHostConfig.servers = [{ url: this.webConfig.baseUrl }];
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
await this.resetSpec();
|
package/src/spec-generate.ts
CHANGED
|
@@ -4,12 +4,11 @@ import type {
|
|
|
4
4
|
RequestBodyObject, TagObject, PathsObject, PathItemObject
|
|
5
5
|
} from 'openapi3-ts/oas31';
|
|
6
6
|
|
|
7
|
-
import { EndpointConfig, ControllerConfig,
|
|
7
|
+
import { EndpointConfig, ControllerConfig, EndpointParamConfig, EndpointIOType, ControllerVisitor, HTTP_METHODS } from '@travetto/web';
|
|
8
8
|
import { Class, describeFunction } from '@travetto/runtime';
|
|
9
9
|
import { SchemaRegistry, FieldConfig, ClassConfig, SchemaNameResolver } from '@travetto/schema';
|
|
10
|
-
import { AllViewSymbol } from '@travetto/schema/src/internal/types';
|
|
11
10
|
|
|
12
|
-
import { ApiSpecConfig } from './config';
|
|
11
|
+
import { ApiSpecConfig } from './config.ts';
|
|
13
12
|
|
|
14
13
|
const DEFINITION = '#/components/schemas';
|
|
15
14
|
|
|
@@ -42,17 +41,6 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
42
41
|
this.#config = config;
|
|
43
42
|
}
|
|
44
43
|
|
|
45
|
-
/**
|
|
46
|
-
* Build response object
|
|
47
|
-
*/
|
|
48
|
-
#getHeaderValue(ep: EndpointConfig, header: string): string | undefined {
|
|
49
|
-
let cType = ep.headers?.[header];
|
|
50
|
-
if (cType && typeof cType !== 'string') {
|
|
51
|
-
cType = cType();
|
|
52
|
-
}
|
|
53
|
-
return cType;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
44
|
/**
|
|
57
45
|
* Convert schema to a set of dotted parameters
|
|
58
46
|
*/
|
|
@@ -206,7 +194,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
206
194
|
};
|
|
207
195
|
|
|
208
196
|
const properties: Record<string, SchemaObject> = {};
|
|
209
|
-
const def = config.
|
|
197
|
+
const def = config.totalView;
|
|
210
198
|
const required: string[] = [];
|
|
211
199
|
|
|
212
200
|
for (const fieldName of def.fields) {
|
|
@@ -243,7 +231,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
243
231
|
/**
|
|
244
232
|
* Standard payload structure
|
|
245
233
|
*/
|
|
246
|
-
#getEndpointBody(body?: EndpointIOType, mime?: string): RequestBodyObject {
|
|
234
|
+
#getEndpointBody(body?: EndpointIOType, mime?: string | null): RequestBodyObject {
|
|
247
235
|
if (!body) {
|
|
248
236
|
return { content: {}, description: '' };
|
|
249
237
|
} else if (body.type === Readable || body.type === Buffer) {
|
|
@@ -271,7 +259,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
271
259
|
/**
|
|
272
260
|
* Process endpoint parameter
|
|
273
261
|
*/
|
|
274
|
-
#processEndpointParam(ep: EndpointConfig, param:
|
|
262
|
+
#processEndpointParam(ep: EndpointConfig, param: EndpointParamConfig, field: FieldConfig): (
|
|
275
263
|
{ requestBody: RequestBodyObject } |
|
|
276
264
|
{ parameters: ParameterObject[] } |
|
|
277
265
|
undefined
|
|
@@ -279,12 +267,13 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
279
267
|
const complex = field.type && SchemaRegistry.has(field.type);
|
|
280
268
|
if (param.location) {
|
|
281
269
|
if (param.location === 'body') {
|
|
270
|
+
const acceptsMime = ep.responseHeaderMap.get('accepts');
|
|
282
271
|
return {
|
|
283
|
-
requestBody: field.specifiers?.includes('file') ? this.#buildUploadBody() : this.#getEndpointBody(field,
|
|
272
|
+
requestBody: field.specifiers?.includes('file') ? this.#buildUploadBody() : this.#getEndpointBody(field, acceptsMime)
|
|
284
273
|
};
|
|
285
274
|
} else if (complex && (param.location === 'query' || param.location === 'header')) {
|
|
286
275
|
return { parameters: this.#schemaToDotParams(param.location, field, param.prefix ? `${param.prefix}.` : '') };
|
|
287
|
-
} else
|
|
276
|
+
} else {
|
|
288
277
|
const epParam: ParameterObject = {
|
|
289
278
|
in: param.location,
|
|
290
279
|
name: param.name || param.location,
|
|
@@ -293,8 +282,6 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
293
282
|
schema: field.array ? { type: 'array', items: this.#getType(field) } : this.#getType(field)
|
|
294
283
|
};
|
|
295
284
|
return { parameters: [epParam] };
|
|
296
|
-
} else if (field.specifiers?.includes('file')) {
|
|
297
|
-
return { requestBody: this.#buildUploadBody() };
|
|
298
285
|
}
|
|
299
286
|
}
|
|
300
287
|
}
|
|
@@ -303,7 +290,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
303
290
|
* Process controller endpoint
|
|
304
291
|
*/
|
|
305
292
|
onEndpointEnd(ep: EndpointConfig, ctrl: ControllerConfig): void {
|
|
306
|
-
if (this.#config.
|
|
293
|
+
if (this.#config.skipEndpoints || !ep.httpMethod) {
|
|
307
294
|
return;
|
|
308
295
|
}
|
|
309
296
|
|
|
@@ -314,43 +301,37 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
314
301
|
responses: {},
|
|
315
302
|
summary: ep.title,
|
|
316
303
|
description: ep.description || ep.title,
|
|
317
|
-
operationId: `${ep.class.name}_${ep.
|
|
304
|
+
operationId: `${ep.class.name}_${ep.name}`,
|
|
318
305
|
parameters: []
|
|
319
306
|
};
|
|
320
307
|
|
|
321
|
-
const
|
|
308
|
+
const contentTypeMime = ep.responseHeaderMap.get('content-type');
|
|
309
|
+
const pConf = this.#getEndpointBody(ep.responseType, contentTypeMime);
|
|
322
310
|
const code = Object.keys(pConf.content).length ? 200 : 201;
|
|
323
311
|
op.responses![code] = pConf;
|
|
324
312
|
|
|
325
|
-
const schema = SchemaRegistry.getMethodSchema(ep.class, ep.
|
|
313
|
+
const schema = SchemaRegistry.getMethodSchema(ep.class, ep.name);
|
|
326
314
|
for (const field of schema) {
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
329
|
-
if ('parameters' in
|
|
330
|
-
(op.parameters ??= []).push(...
|
|
315
|
+
const result = this.#processEndpointParam(ep, ep.params[field.index!], field);
|
|
316
|
+
if (result) {
|
|
317
|
+
if ('parameters' in result) {
|
|
318
|
+
(op.parameters ??= []).push(...result.parameters);
|
|
331
319
|
} else {
|
|
332
|
-
op.requestBody ??=
|
|
320
|
+
op.requestBody ??= result.requestBody;
|
|
333
321
|
}
|
|
334
322
|
}
|
|
335
323
|
}
|
|
336
324
|
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
const key = `${ctrl.basePath}${epPath}`.replace(/[\/]+/g, '/');
|
|
340
|
-
|
|
341
|
-
const toAdd = ep.method === 'all' ?
|
|
342
|
-
['get', 'post', 'put', 'delete', 'patch'].reduce((acc, v) =>
|
|
343
|
-
({ ...acc, [v]: { ...op, operationId: `${op.operationId}_${v}` } }), {}) :
|
|
344
|
-
{ [ep.method]: op };
|
|
325
|
+
const key = ep.fullPath.replace(/:([A-Za-z0-9_]+)\b/g, (__, param) => `{${param}}`);
|
|
345
326
|
|
|
346
327
|
this.#paths[key] = {
|
|
347
328
|
...(this.#paths[key] ?? {}),
|
|
348
|
-
|
|
329
|
+
[HTTP_METHODS[ep.httpMethod].lower]: op
|
|
349
330
|
};
|
|
350
331
|
}
|
|
351
332
|
|
|
352
333
|
onControllerEnd(controller: ControllerConfig): void {
|
|
353
|
-
if (this.#config.
|
|
334
|
+
if (this.#config.skipEndpoints) {
|
|
354
335
|
return;
|
|
355
336
|
}
|
|
356
337
|
this.#tags.push({
|
|
@@ -365,19 +346,19 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
365
346
|
}
|
|
366
347
|
|
|
367
348
|
return {
|
|
368
|
-
tags: this.#tags.
|
|
349
|
+
tags: this.#tags.toSorted((a, b) => a.name.localeCompare(b.name)),
|
|
369
350
|
paths: Object.fromEntries(
|
|
370
351
|
Object.entries(this.#paths)
|
|
371
|
-
.
|
|
352
|
+
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
372
353
|
.map(([k, v]) => [k, Object.fromEntries(
|
|
373
354
|
Object.entries(v)
|
|
374
|
-
.
|
|
355
|
+
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
375
356
|
)])
|
|
376
357
|
),
|
|
377
358
|
components: {
|
|
378
359
|
schemas: Object.fromEntries(
|
|
379
360
|
Object.entries(this.#schemas)
|
|
380
|
-
.
|
|
361
|
+
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
381
362
|
)
|
|
382
363
|
}
|
|
383
364
|
};
|
package/support/bin/help.ts
CHANGED
|
@@ -22,7 +22,7 @@ export class OpenApiClientHelp {
|
|
|
22
22
|
.map(x => x.replace(/^\s+-\s+/, '').trim());
|
|
23
23
|
|
|
24
24
|
await fs.mkdir(path.dirname(formatCache), { recursive: true });
|
|
25
|
-
await fs.writeFile(formatCache, JSON.stringify([...lines.
|
|
25
|
+
await fs.writeFile(formatCache, JSON.stringify([...lines.toSorted(),]));
|
|
26
26
|
}
|
|
27
27
|
const list: string[] = JSON.parse(await fs.readFile(formatCache, 'utf8'));
|
|
28
28
|
return list;
|
|
@@ -4,7 +4,7 @@ import cp from 'node:child_process';
|
|
|
4
4
|
import { CliCommandShape, CliCommand, CliFlag } from '@travetto/cli';
|
|
5
5
|
import { ExecUtil } from '@travetto/runtime';
|
|
6
6
|
|
|
7
|
-
import { OpenApiClientHelp } from './bin/help';
|
|
7
|
+
import { OpenApiClientHelp } from './bin/help.ts';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* CLI for generating the cli client
|
|
@@ -20,7 +20,7 @@ export class OpenApiSpecCommand implements CliCommandShape {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
async main(): Promise<void> {
|
|
23
|
-
const { OpenApiService } = await import('../src/service');
|
|
23
|
+
const { OpenApiService } = await import('../src/service.ts');
|
|
24
24
|
|
|
25
25
|
await RootRegistry.init();
|
|
26
26
|
|