@travetto/openapi 7.0.0-rc.1 → 7.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/__index__.ts +1 -1
- package/package.json +5 -5
- package/src/{spec-generate.ts → generate.ts} +52 -52
- package/src/service.ts +3 -3
- package/support/bin/help.ts +3 -3
- package/support/cli.openapi_client.ts +4 -4
package/__index__.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/openapi",
|
|
3
|
-
"version": "7.0.0-rc.
|
|
3
|
+
"version": "7.0.0-rc.3",
|
|
4
4
|
"description": "OpenAPI integration support for the Travetto framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"web",
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
"directory": "module/openapi"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/config": "^7.0.0-rc.
|
|
30
|
-
"@travetto/schema": "^7.0.0-rc.
|
|
31
|
-
"@travetto/web": "^7.0.0-rc.
|
|
29
|
+
"@travetto/config": "^7.0.0-rc.2",
|
|
30
|
+
"@travetto/schema": "^7.0.0-rc.2",
|
|
31
|
+
"@travetto/web": "^7.0.0-rc.3",
|
|
32
32
|
"openapi3-ts": "^4.5.0",
|
|
33
33
|
"yaml": "^2.8.1"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
|
-
"@travetto/cli": "^7.0.0-rc.
|
|
36
|
+
"@travetto/cli": "^7.0.0-rc.2"
|
|
37
37
|
},
|
|
38
38
|
"peerDependenciesMeta": {
|
|
39
39
|
"@travetto/cli": {
|
|
@@ -12,8 +12,8 @@ import { ApiSpecConfig } from './config.ts';
|
|
|
12
12
|
|
|
13
13
|
const DEFINITION = '#/components/schemas';
|
|
14
14
|
|
|
15
|
-
const isInputConfig = (
|
|
16
|
-
const isFieldConfig = (
|
|
15
|
+
const isInputConfig = (value: object): value is SchemaInputConfig => !!value && 'owner' in value && 'type' in value;
|
|
16
|
+
const isFieldConfig = (value: object): value is SchemaFieldConfig => isInputConfig(value) && 'name' in value;
|
|
17
17
|
|
|
18
18
|
type GeneratedSpec = {
|
|
19
19
|
tags: TagObject[];
|
|
@@ -48,10 +48,10 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
48
48
|
throw new AppError(`Unknown class, not registered as a schema: ${input.type.Ⲑid}`);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const fields = SchemaRegistryIndex.
|
|
51
|
+
const fields = SchemaRegistryIndex.get(input.type).getFields(input.view);
|
|
52
52
|
const params: ParameterObject[] = [];
|
|
53
53
|
for (const sub of Object.values(fields)) {
|
|
54
|
-
const name = sub.name
|
|
54
|
+
const name = sub.name;
|
|
55
55
|
if (SchemaRegistryIndex.has(sub.type)) {
|
|
56
56
|
const suffix = (sub.array) ? '[]' : '';
|
|
57
57
|
params.push(...this.#schemaToDotParams(location, sub, prefix ? `${prefix}.${name}${suffix}` : `${name}${suffix}.`, rootField));
|
|
@@ -131,48 +131,48 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
131
131
|
* Process schema field
|
|
132
132
|
*/
|
|
133
133
|
#processSchemaField(input: SchemaInputConfig, required: string[]): SchemaObject {
|
|
134
|
-
let
|
|
134
|
+
let config: SchemaObject = this.#getType(input);
|
|
135
135
|
|
|
136
136
|
if (input.examples) {
|
|
137
|
-
|
|
137
|
+
config.example = input.examples;
|
|
138
138
|
}
|
|
139
|
-
|
|
139
|
+
config.description = input.description;
|
|
140
140
|
if (input.match) {
|
|
141
|
-
|
|
141
|
+
config.pattern = input.match.re!.source;
|
|
142
142
|
}
|
|
143
143
|
if (input.maxlength) {
|
|
144
|
-
|
|
144
|
+
config.maxLength = input.maxlength.n;
|
|
145
145
|
}
|
|
146
146
|
if (input.minlength) {
|
|
147
|
-
|
|
147
|
+
config.minLength = input.minlength.n;
|
|
148
148
|
}
|
|
149
149
|
if (input.min) {
|
|
150
|
-
|
|
150
|
+
config.minimum = typeof input.min.n === 'number' ? input.min.n : input.min.n.getTime();
|
|
151
151
|
}
|
|
152
152
|
if (input.max) {
|
|
153
|
-
|
|
153
|
+
config.maximum = typeof input.max.n === 'number' ? input.max.n : input.max.n.getTime();
|
|
154
154
|
}
|
|
155
155
|
if (input.enum) {
|
|
156
|
-
|
|
156
|
+
config.enum = input.enum.values;
|
|
157
157
|
}
|
|
158
158
|
if (isFieldConfig(input)) {
|
|
159
159
|
if (input.required?.active !== false) {
|
|
160
|
-
required.push(input.name
|
|
160
|
+
required.push(input.name);
|
|
161
161
|
}
|
|
162
162
|
if (input.access === 'readonly') {
|
|
163
|
-
|
|
163
|
+
config.readOnly = true;
|
|
164
164
|
} else if (input.access === 'writeonly') {
|
|
165
|
-
|
|
165
|
+
config.writeOnly = true;
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
if (input.array) {
|
|
169
|
-
|
|
169
|
+
config = {
|
|
170
170
|
type: 'array',
|
|
171
|
-
items:
|
|
171
|
+
items: config
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
-
return
|
|
175
|
+
return config;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
/**
|
|
@@ -195,25 +195,25 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
195
195
|
};
|
|
196
196
|
|
|
197
197
|
const properties: Record<string, SchemaObject> = {};
|
|
198
|
-
const
|
|
198
|
+
const base = config;
|
|
199
199
|
const required: string[] = [];
|
|
200
200
|
|
|
201
|
-
for (const fieldName of Object.keys(
|
|
202
|
-
if (SchemaRegistryIndex.has(
|
|
203
|
-
this.onSchema(SchemaRegistryIndex.getConfig(
|
|
201
|
+
for (const fieldName of Object.keys(base.fields)) {
|
|
202
|
+
if (SchemaRegistryIndex.has(base.fields[fieldName].type)) {
|
|
203
|
+
this.onSchema(SchemaRegistryIndex.getConfig(base.fields[fieldName].type));
|
|
204
204
|
}
|
|
205
|
-
properties[fieldName] = this.#processSchemaField(
|
|
205
|
+
properties[fieldName] = this.#processSchemaField(base.fields[fieldName], required);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
208
|
const extra: Record<string, unknown> = {};
|
|
209
209
|
if (config.discriminatedBase) {
|
|
210
|
-
const
|
|
211
|
-
if (
|
|
212
|
-
extra.oneOf =
|
|
213
|
-
.filter(
|
|
214
|
-
.map(
|
|
215
|
-
this.onSchema(SchemaRegistryIndex.getConfig(
|
|
216
|
-
return this.#getType(
|
|
210
|
+
const subClasses = SchemaRegistryIndex.getDiscriminatedClasses(cls);
|
|
211
|
+
if (subClasses) {
|
|
212
|
+
extra.oneOf = subClasses
|
|
213
|
+
.filter(subCls => !describeFunction(subCls)?.abstract)
|
|
214
|
+
.map(subCls => {
|
|
215
|
+
this.onSchema(SchemaRegistryIndex.getConfig(subCls));
|
|
216
|
+
return this.#getType(subCls);
|
|
217
217
|
});
|
|
218
218
|
}
|
|
219
219
|
}
|
|
@@ -243,7 +243,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
243
243
|
description: 'Raw binary data'
|
|
244
244
|
};
|
|
245
245
|
} else {
|
|
246
|
-
const schemaConfig = SchemaRegistryIndex.
|
|
246
|
+
const schemaConfig = SchemaRegistryIndex.getOptional(body.type)?.get();
|
|
247
247
|
const typeId = schemaConfig ? this.#nameResolver.getName(schemaConfig) : body.type.name;
|
|
248
248
|
const typeRef = schemaConfig ? this.#getType(body.type) : { type: body.type.name.toLowerCase() };
|
|
249
249
|
return {
|
|
@@ -260,7 +260,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
260
260
|
/**
|
|
261
261
|
* Process endpoint parameter
|
|
262
262
|
*/
|
|
263
|
-
#processEndpointParam(
|
|
263
|
+
#processEndpointParam(endpoint: EndpointConfig, param: EndpointParameterConfig, input: SchemaParameterConfig): (
|
|
264
264
|
{ requestBody: RequestBodyObject } |
|
|
265
265
|
{ parameters: ParameterObject[] } |
|
|
266
266
|
undefined
|
|
@@ -269,7 +269,7 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
269
269
|
|
|
270
270
|
if (param.location) {
|
|
271
271
|
if (param.location === 'body') {
|
|
272
|
-
const acceptsMime =
|
|
272
|
+
const acceptsMime = endpoint.finalizedResponseHeaders.get('accepts');
|
|
273
273
|
return {
|
|
274
274
|
requestBody: input.specifiers?.includes('file') ? this.#buildUploadBody() : this.#getEndpointBody(input, acceptsMime)
|
|
275
275
|
};
|
|
@@ -291,47 +291,47 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
291
291
|
/**
|
|
292
292
|
* Process controller endpoint
|
|
293
293
|
*/
|
|
294
|
-
onEndpointEnd(
|
|
295
|
-
if (this.#config.skipEndpoints || !
|
|
294
|
+
onEndpointEnd(endpoint: EndpointConfig, controller: ControllerConfig): void {
|
|
295
|
+
if (this.#config.skipEndpoints || !endpoint.httpMethod) {
|
|
296
296
|
return;
|
|
297
297
|
}
|
|
298
298
|
|
|
299
|
-
const tagName =
|
|
299
|
+
const tagName = controller.externalName;
|
|
300
300
|
|
|
301
|
-
const schema = SchemaRegistryIndex.
|
|
301
|
+
const schema = SchemaRegistryIndex.get(endpoint.class).getMethod(endpoint.methodName);
|
|
302
302
|
|
|
303
|
-
const
|
|
303
|
+
const apiConfig: OperationObject = {
|
|
304
304
|
tags: [tagName],
|
|
305
305
|
responses: {},
|
|
306
306
|
summary: schema.description,
|
|
307
307
|
description: schema.description,
|
|
308
|
-
operationId: `${
|
|
308
|
+
operationId: `${endpoint.class.name}_${endpoint.methodName}`,
|
|
309
309
|
parameters: []
|
|
310
310
|
};
|
|
311
311
|
|
|
312
|
-
const contentTypeMime =
|
|
313
|
-
const
|
|
314
|
-
const code = Object.keys(
|
|
315
|
-
|
|
312
|
+
const contentTypeMime = endpoint.finalizedResponseHeaders.get('content-type');
|
|
313
|
+
const bodyConfig = this.#getEndpointBody(schema.returnType, contentTypeMime);
|
|
314
|
+
const code = Object.keys(bodyConfig.content).length ? 200 : 201;
|
|
315
|
+
apiConfig.responses![code] = bodyConfig;
|
|
316
316
|
|
|
317
|
-
const methodSchema = SchemaRegistryIndex.
|
|
317
|
+
const methodSchema = SchemaRegistryIndex.get(endpoint.class).getMethod(endpoint.methodName);
|
|
318
318
|
|
|
319
319
|
for (const param of methodSchema.parameters) {
|
|
320
|
-
const result = this.#processEndpointParam(
|
|
320
|
+
const result = this.#processEndpointParam(endpoint, endpoint.parameters[param.index] ?? {}, param);
|
|
321
321
|
if (result) {
|
|
322
322
|
if ('parameters' in result) {
|
|
323
|
-
(
|
|
323
|
+
(apiConfig.parameters ??= []).push(...result.parameters);
|
|
324
324
|
} else {
|
|
325
|
-
|
|
325
|
+
apiConfig.requestBody ??= result.requestBody;
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
const key =
|
|
330
|
+
const key = endpoint.fullPath.replace(/:([A-Za-z0-9_]+)\b/g, (__, param) => `{${param}}`);
|
|
331
331
|
|
|
332
332
|
this.#paths[key] = {
|
|
333
333
|
...(this.#paths[key] ?? {}),
|
|
334
|
-
[HTTP_METHODS[
|
|
334
|
+
[HTTP_METHODS[endpoint.httpMethod].lower]: apiConfig
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
|
|
@@ -356,8 +356,8 @@ export class OpenapiVisitor implements ControllerVisitor<GeneratedSpec> {
|
|
|
356
356
|
paths: Object.fromEntries(
|
|
357
357
|
Object.entries(this.#paths)
|
|
358
358
|
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
359
|
-
.map(([
|
|
360
|
-
Object.entries(
|
|
359
|
+
.map(([key, value]) => [key, Object.fromEntries(
|
|
360
|
+
Object.entries(value)
|
|
361
361
|
.toSorted(([a], [b]) => a.localeCompare(b))
|
|
362
362
|
)])
|
|
363
363
|
),
|
package/src/service.ts
CHANGED
|
@@ -8,7 +8,7 @@ import { SchemaRegistryIndex } from '@travetto/schema';
|
|
|
8
8
|
import { Registry } from '@travetto/registry';
|
|
9
9
|
|
|
10
10
|
import { ApiHostConfig, ApiInfoConfig, ApiSpecConfig } from './config.ts';
|
|
11
|
-
import { OpenapiVisitor } from './
|
|
11
|
+
import { OpenapiVisitor } from './generate.ts';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Open API generation service
|
|
@@ -82,8 +82,8 @@ export class OpenApiService {
|
|
|
82
82
|
stringify(spec);
|
|
83
83
|
|
|
84
84
|
await BinaryUtil.bufferedFileWrite(this.apiSpecConfig.output, output, true);
|
|
85
|
-
} catch (
|
|
86
|
-
console.error('Unable to persist openapi spec',
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error('Unable to persist openapi spec', error);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
}
|
package/support/bin/help.ts
CHANGED
|
@@ -18,8 +18,8 @@ export class OpenApiClientHelp {
|
|
|
18
18
|
.split('DOCUMENTATION')[0]
|
|
19
19
|
.trim()
|
|
20
20
|
.split(/\n/g)
|
|
21
|
-
.filter(
|
|
22
|
-
.map(
|
|
21
|
+
.filter(line => /^\s+-/.test(line) && !/\((beta|experimental)\)/.test(line))
|
|
22
|
+
.map(line => line.replace(/^\s+-\s+/, '').trim());
|
|
23
23
|
|
|
24
24
|
await fs.mkdir(path.dirname(formatCache), { recursive: true });
|
|
25
25
|
await fs.writeFile(formatCache, JSON.stringify([...lines.toSorted(),]));
|
|
@@ -36,7 +36,7 @@ export class OpenApiClientHelp {
|
|
|
36
36
|
'',
|
|
37
37
|
cliTpl`${{ subtitle: 'Available Formats' }}`,
|
|
38
38
|
'----------------------------------',
|
|
39
|
-
...formats.map(
|
|
39
|
+
...formats.map(format => cliTpl`* ${{ input: format }}`)
|
|
40
40
|
);
|
|
41
41
|
}
|
|
42
42
|
return help;
|
|
@@ -16,7 +16,7 @@ export class OpenApiClientCommand implements CliCommandShape {
|
|
|
16
16
|
extendedHelp: boolean = false;
|
|
17
17
|
/** Additional Properties */
|
|
18
18
|
@CliFlag({ short: '-a', full: '--additional-properties' })
|
|
19
|
-
|
|
19
|
+
properties: string[] = [];
|
|
20
20
|
/** Input file */
|
|
21
21
|
input = './openapi.yml';
|
|
22
22
|
/** Output folder */
|
|
@@ -32,7 +32,7 @@ export class OpenApiClientCommand implements CliCommandShape {
|
|
|
32
32
|
this.output = path.resolve(this.output);
|
|
33
33
|
this.input = path.resolve(this.input);
|
|
34
34
|
|
|
35
|
-
const
|
|
35
|
+
const subProcess = cp.spawn('docker', [
|
|
36
36
|
'run',
|
|
37
37
|
'--rm',
|
|
38
38
|
'-i',
|
|
@@ -47,12 +47,12 @@ export class OpenApiClientCommand implements CliCommandShape {
|
|
|
47
47
|
'-g', format,
|
|
48
48
|
'-o', '/workspace',
|
|
49
49
|
'-i', `/input/${path.basename(this.input)}`,
|
|
50
|
-
...(this.
|
|
50
|
+
...(this.properties.length ? ['--additional-properties', this.properties.join(',')] : [])
|
|
51
51
|
], {
|
|
52
52
|
stdio: 'inherit'
|
|
53
53
|
});
|
|
54
54
|
|
|
55
|
-
const result = await ExecUtil.getResult(
|
|
55
|
+
const result = await ExecUtil.getResult(subProcess);
|
|
56
56
|
|
|
57
57
|
if (!result.valid) {
|
|
58
58
|
process.exitCode = 1;
|