@penkov/swagger-code-gen 1.15.0 → 1.16.1

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,8 @@ 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)
15
+ .option('--includeSchemasByMask <masks...>', 'Space-separated list of schema name masks to force-include with dependencies (supports * and ? wildcards)')
14
16
  .option('--enableScats', 'Generate scats', false)
15
17
  .option('--targetNode', 'Add imports for node-fetch into generated code', false)
16
18
  .option('--user <username>', 'If swagger requires authorisation')
@@ -28,12 +30,16 @@ const targetNode = program.opts().targetNode;
28
30
  const outputFile = program.args[0];
29
31
  const includeTags = HashSet.from(program.opts().includeTags || []);
30
32
  const excludeTags = HashSet.from(program.opts().excludeTags || []);
33
+ const onlyUsedSchemas = program.opts().onlyUsedSchemas;
34
+ const includeSchemasByMask = HashSet.from(program.opts().includeSchemasByMask || []);
31
35
  main(url, enableScats, targetNode, outputFile, ignoreSSLErrors, option(user).flatMap(u => option(password).map(p => ({
32
36
  user: u,
33
37
  password: p
34
38
  }))), {
35
39
  referencedObjectsNullableByDefault: referencedObjectsNullableByDefault,
36
40
  includeTags: includeTags,
37
- excludeTags: excludeTags
41
+ excludeTags: excludeTags,
42
+ onlyUsedSchemas: onlyUsedSchemas,
43
+ includeSchemasByMask: includeSchemasByMask
38
44
  }).then(() => {
39
45
  });
@@ -1,6 +1,6 @@
1
- import { Collection, mutable, Nil, option } from 'scats';
1
+ import { Collection, HashSet, 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,59 @@ 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
+ function wildcardMaskToRegExp(mask) {
99
+ const escaped = mask.replace(/[.+^${}()|[\]\\]/g, '\\$&');
100
+ const regex = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
101
+ return new RegExp(`^${regex}$`);
102
+ }
103
+ export function filterUsedSchemas(paths, schemas, includeSchemasByMask = HashSet.empty) {
104
+ const used = new Set();
105
+ const pending = [];
106
+ const maskRegexes = includeSchemasByMask.map(mask => wildcardMaskToRegExp(mask));
107
+ const add = (name) => {
108
+ if (!used.has(name) && schemas.containsKey(name)) {
109
+ used.add(name);
110
+ pending.push(name);
111
+ }
112
+ };
113
+ const collectFromProperty = (p) => {
114
+ collectSchemaRefsFromType(p.type, schemas, add);
115
+ collectSchemaRefsFromType(p.items, schemas, add);
116
+ };
117
+ paths.foreach(m => {
118
+ m.parameters.foreach(p => p.referencedSchemas.foreach(t => add(t)));
119
+ collectFromProperty(m.response.asProperty);
120
+ m.body.foreach(b => {
121
+ if (b.body instanceof Property) {
122
+ collectFromProperty(b.body);
123
+ }
124
+ });
125
+ });
126
+ schemas.keySet
127
+ .filter(name => maskRegexes.exists(regex => regex.test(name)))
128
+ .foreach(name => add(name));
129
+ while (pending.length > 0) {
130
+ const schemaName = pending.shift();
131
+ const schema = schemas.get(schemaName).getOrElseThrow(() => new Error(`No schema for ${schemaName}`));
132
+ if (schema instanceof SchemaObject) {
133
+ schema.parents.keySet.foreach(parentName => add(parentName));
134
+ schema.properties.foreach(p => collectFromProperty(p));
135
+ }
136
+ else if (schema instanceof Property) {
137
+ collectFromProperty(schema);
138
+ }
139
+ }
140
+ return schemas.keySet
141
+ .filter(name => used.has(name))
142
+ .toMap(name => [name, schemas.get(name).getOrElseThrow(() => new Error(`No schema for ${name}`))]);
143
+ }
91
144
  export function generateInPlace(paths, schemasTypes, options, pool) {
92
145
  const collectInplaceFromProperty = (p) => {
93
146
  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,11 @@ 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
32
+ ? filterUsedSchemas(paths, allSchemas, options.includeSchemasByMask)
33
+ : allSchemas;
31
34
  const inplace = generateInPlace(paths, schemasTypes, options, schemas);
32
35
  logger.debug(`Downloaded swagger: ${schemas.size} schemas, ${paths.size} paths`);
33
36
  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.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "generate-client": "dist/cli.mjs"