@penkov/swagger-code-gen 1.15.0 → 1.16.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 CHANGED
@@ -11,6 +11,7 @@ Usage:
11
11
  ```shell
12
12
  generate-client --url <URI> output_filename.ts
13
13
  generate-client --url <URI> --includeTags tag1 tag2 -- output_filename.ts
14
+ generate-client --url <URI> --includeTags tag1 tag2 --onlyUsedSchemas -- output_filename.ts
14
15
  ```
15
16
 
16
17
  Cli parameters:
@@ -21,6 +22,8 @@ Cli parameters:
21
22
  * `--excludeTags <tags...>` - Space-separated list of tags of paths to be excluded.
22
23
  Path is excluded if exclusion list is non-empty and path contains any of the tags from exclusion list.
23
24
  If the tag is both in inclusion and exclusion lists, it gets excluded.
25
+ * `--onlyUsedSchemas` - generate only schemas that are reachable from filtered methods
26
+ (useful with `--includeTags`/`--excludeTags`). By default all schemas are generated.
24
27
  * `--referencedObjectsNullableByDefault` - then specified, the generated code will assume that
25
28
  any object's field, that references another object, can be null, unless it is explicitly specified to be not nullable
26
29
  (which is default in .net world: asp generates wrong spec)
@@ -28,6 +31,18 @@ Cli parameters:
28
31
  style for all objects and create a service with methods for each endpoint.
29
32
  * `--targetNode` - adds imports for `node-fetch` package in generated code.
30
33
 
34
+ ## Tag filtering and schema pruning
35
+
36
+ By default, tag filters affect only methods and all schemas are still generated:
37
+ ```shell
38
+ generate-client --url <URI> --includeTags public -- output_filename.ts
39
+ ```
40
+
41
+ To generate only schemas that are reachable from filtered methods, add `--onlyUsedSchemas`:
42
+ ```shell
43
+ generate-client --url <URI> --includeTags public --onlyUsedSchemas -- output_filename.ts
44
+ ```
45
+
31
46
 
32
47
 
33
48
  # Example of generated code:
package/dist/cli.mjs CHANGED
@@ -11,6 +11,7 @@ program
11
11
  .option('--referencedObjectsNullableByDefault', 'Assume that referenced objects can be null (say hello to .net assholes)', false)
12
12
  .option('--includeTags <tags...>', 'Space-separated list of tags of paths to be included. Path is included if it contains any of specified tag')
13
13
  .option('--excludeTags <tags...>', 'Space-separated list of tags of paths to be excluded. Path is excluded if it contains any of specified tag')
14
+ .option('--onlyUsedSchemas', 'Generate only schemas reachable from filtered methods', false)
14
15
  .option('--enableScats', 'Generate scats', false)
15
16
  .option('--targetNode', 'Add imports for node-fetch into generated code', false)
16
17
  .option('--user <username>', 'If swagger requires authorisation')
@@ -28,12 +29,14 @@ const targetNode = program.opts().targetNode;
28
29
  const outputFile = program.args[0];
29
30
  const includeTags = HashSet.from(program.opts().includeTags || []);
30
31
  const excludeTags = HashSet.from(program.opts().excludeTags || []);
32
+ const onlyUsedSchemas = program.opts().onlyUsedSchemas;
31
33
  main(url, enableScats, targetNode, outputFile, ignoreSSLErrors, option(user).flatMap(u => option(password).map(p => ({
32
34
  user: u,
33
35
  password: p
34
36
  }))), {
35
37
  referencedObjectsNullableByDefault: referencedObjectsNullableByDefault,
36
38
  includeTags: includeTags,
37
- excludeTags: excludeTags
39
+ excludeTags: excludeTags,
40
+ onlyUsedSchemas: onlyUsedSchemas
38
41
  }).then(() => {
39
42
  });
@@ -1,6 +1,6 @@
1
1
  import { Collection, mutable, Nil, option } from 'scats';
2
2
  import { SchemaFactory, SchemaObject } from './schemas.js';
3
- import { SCHEMA_PREFIX } from './property.js';
3
+ import { Property, SCHEMA_PREFIX } from './property.js';
4
4
  import { Method, supportedBodyMimeTypes } from './method.js';
5
5
  export function resolveSchemasTypes(json) {
6
6
  const jsonSchemas = json.components.schemas;
@@ -88,6 +88,50 @@ export function resolvePaths(json, schemasTypes, options, pool) {
88
88
  return included && !excluded;
89
89
  });
90
90
  }
91
+ function collectSchemaRefsFromType(type, schemas, add) {
92
+ Collection.from(type.split(/[|&]/))
93
+ .map(t => t.trim())
94
+ .filter(t => t.length > 0)
95
+ .filter(t => schemas.containsKey(t))
96
+ .foreach(t => add(t));
97
+ }
98
+ export function filterUsedSchemas(paths, schemas) {
99
+ const used = new Set();
100
+ const pending = [];
101
+ const add = (name) => {
102
+ if (!used.has(name) && schemas.containsKey(name)) {
103
+ used.add(name);
104
+ pending.push(name);
105
+ }
106
+ };
107
+ const collectFromProperty = (p) => {
108
+ collectSchemaRefsFromType(p.type, schemas, add);
109
+ collectSchemaRefsFromType(p.items, schemas, add);
110
+ };
111
+ paths.foreach(m => {
112
+ m.parameters.foreach(p => p.referencedSchemas.foreach(t => add(t)));
113
+ collectFromProperty(m.response.asProperty);
114
+ m.body.foreach(b => {
115
+ if (b.body instanceof Property) {
116
+ collectFromProperty(b.body);
117
+ }
118
+ });
119
+ });
120
+ while (pending.length > 0) {
121
+ const schemaName = pending.shift();
122
+ const schema = schemas.get(schemaName).getOrElseThrow(() => new Error(`No schema for ${schemaName}`));
123
+ if (schema instanceof SchemaObject) {
124
+ schema.parents.keySet.foreach(parentName => add(parentName));
125
+ schema.properties.foreach(p => collectFromProperty(p));
126
+ }
127
+ else if (schema instanceof Property) {
128
+ collectFromProperty(schema);
129
+ }
130
+ }
131
+ return schemas.keySet
132
+ .filter(name => used.has(name))
133
+ .toMap(name => [name, schemas.get(name).getOrElseThrow(() => new Error(`No schema for ${name}`))]);
134
+ }
91
135
  export function generateInPlace(paths, schemasTypes, options, pool) {
92
136
  const collectInplaceFromProperty = (p) => {
93
137
  if (p.inPlace.isDefined) {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import log4js from 'log4js';
2
2
  import fetch from 'node-fetch';
3
3
  import { Renderer } from './renderer.js';
4
- import { generateInPlace, resolvePaths, resolveSchemas, resolveSchemasTypes } from './components-parse.js';
4
+ import { filterUsedSchemas, generateInPlace, resolvePaths, resolveSchemas, resolveSchemasTypes } from './components-parse.js';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname } from 'path';
7
7
  import https from 'https';
@@ -26,8 +26,9 @@ export async function main(url, enableScats, targetNode, outputFile, ignoreSSLEr
26
26
  .then(res => res.json())
27
27
  .then(async (json) => {
28
28
  const schemasTypes = resolveSchemasTypes(json);
29
- const schemas = resolveSchemas(json, schemasTypes, options);
30
- const paths = resolvePaths(json, schemasTypes, options, schemas);
29
+ const allSchemas = resolveSchemas(json, schemasTypes, options);
30
+ const paths = resolvePaths(json, schemasTypes, options, allSchemas);
31
+ const schemas = options.onlyUsedSchemas ? filterUsedSchemas(paths, allSchemas) : allSchemas;
31
32
  const inplace = generateInPlace(paths, schemasTypes, options, schemas);
32
33
  logger.debug(`Downloaded swagger: ${schemas.size} schemas, ${paths.size} paths`);
33
34
  await renderer.renderToFile(schemas.appendedAll(inplace.toMap(s => [s.name, s])).values, paths, enableScats, targetNode, outputFile);
package/dist/parameter.js CHANGED
@@ -3,12 +3,13 @@ import { Collection, identity, none, option } from 'scats';
3
3
  import { Method } from './method.js';
4
4
  import { Property } from './property.js';
5
5
  export class Parameter {
6
- constructor(name, rawName, uniqueName, inValue, jsType, required, isArray, defaultValue, description) {
6
+ constructor(name, rawName, uniqueName, inValue, jsType, referencedSchemas, required, isArray, defaultValue, description) {
7
7
  this.name = name;
8
8
  this.rawName = rawName;
9
9
  this.uniqueName = uniqueName;
10
10
  this.inValue = inValue;
11
11
  this.jsType = jsType;
12
+ this.referencedSchemas = referencedSchemas;
12
13
  this.required = required;
13
14
  this.isArray = isArray;
14
15
  this.defaultValue = defaultValue;
@@ -21,6 +22,7 @@ export class Parameter {
21
22
  const inValue = def.in;
22
23
  const desc = option(def.description);
23
24
  let defaultValue = none;
25
+ const references = new Set();
24
26
  const schema = def.schema ?
25
27
  SchemaFactory.build(def.name, def.schema, schemas, options) :
26
28
  Property.fromDefinition('', name, {
@@ -35,10 +37,14 @@ export class Parameter {
35
37
  if (schema.type === 'integer') {
36
38
  jsType = 'number';
37
39
  }
40
+ if (schemas.containsKey(schema.name)) {
41
+ references.add(schema.name);
42
+ }
38
43
  }
39
44
  else if (schema instanceof SchemaEnum) {
40
45
  if (schemas.containsKey(schema.name)) {
41
46
  jsType = schema.name;
47
+ references.add(schema.name);
42
48
  }
43
49
  else if (schema.type === 'string') {
44
50
  jsType = `${schema.values.map(x => `'${x}'`).mkString(' | ')}`;
@@ -60,13 +66,19 @@ export class Parameter {
60
66
  }
61
67
  else if (schema instanceof Property) {
62
68
  jsType = schema.jsType;
69
+ if (schemas.containsKey(schema.type)) {
70
+ references.add(schema.type);
71
+ }
72
+ if (schema.isArray && schemas.containsKey(schema.items)) {
73
+ references.add(schema.items);
74
+ }
63
75
  }
64
76
  else {
65
77
  throw new Error('Unknown schema type');
66
78
  }
67
79
  const required = option(def.required).exists(identity) || defaultValue.nonEmpty;
68
80
  const isArray = def?.schema?.type === 'array';
69
- return new Parameter(name, rawName, name, inValue, jsType, required, isArray, defaultValue, desc);
81
+ return new Parameter(name, rawName, name, inValue, jsType, Collection.from(Array.from(references)), required, isArray, defaultValue, desc);
70
82
  }
71
83
  static toJSName(path) {
72
84
  const tokens = Collection.from(path.split(/\W/)).filter(t => t.length > 0);
@@ -75,6 +87,6 @@ export class Parameter {
75
87
  }).mkString();
76
88
  }
77
89
  copy(p) {
78
- return new Parameter(option(p.name).getOrElseValue(this.name), option(p.rawName).getOrElseValue(this.rawName), option(p.uniqueName).getOrElseValue(this.uniqueName), option(p.in).getOrElseValue(this.in), option(p.jsType).getOrElseValue(this.jsType), option(p.required).getOrElseValue(this.required), option(p.isArray).getOrElseValue(this.isArray), option(p.defaultValue).getOrElseValue(this.defaultValue), option(p.description).getOrElseValue(this.description));
90
+ return new Parameter(option(p.name).getOrElseValue(this.name), option(p.rawName).getOrElseValue(this.rawName), option(p.uniqueName).getOrElseValue(this.uniqueName), option(p.in).getOrElseValue(this.in), option(p.jsType).getOrElseValue(this.jsType), option(p.referencedSchemas).getOrElseValue(this.referencedSchemas), option(p.required).getOrElseValue(this.required), option(p.isArray).getOrElseValue(this.isArray), option(p.defaultValue).getOrElseValue(this.defaultValue), option(p.description).getOrElseValue(this.description));
79
91
  }
80
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@penkov/swagger-code-gen",
3
- "version": "1.15.0",
3
+ "version": "1.16.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "generate-client": "dist/cli.mjs"