@oslo-flanders/swagger-generator 1.1.0 → 1.2.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
@@ -3,12 +3,15 @@
3
3
  > Given an OSLO-compliant RDF file, this tool generates a Swagger API from it.
4
4
 
5
5
  ## Install
6
+
6
7
  ```bash
7
8
  npm install @oslo-flanders/swagger-generator
8
9
  ```
9
10
 
10
11
  ## Global install
12
+
11
13
  To use the service from the command line anywhere, you can install it globally.
14
+
12
15
  ```bash
13
16
  npm install -g @oslo-flanders/swagger-generator
14
17
  ```
@@ -16,25 +19,31 @@ npm install -g @oslo-flanders/swagger-generator
16
19
  ## API
17
20
 
18
21
  The service is executed from the CLI and expects the following parameters:
19
- | Parameter | Description | Required | Possible values |
22
+ | Parameter | Description | Required | Possible values |
20
23
  | ------------------ | ------------------------------------------------------------ | ------------------ | --------------------------- |
21
- | `--input` | The URL or local file path of an OSLO-compliant RDF file | :heavy_check_mark: | |
22
- | `--output` | The name of the output file | :heavy_check_mark: | |
23
- | `--language` | The language in which the Swagger must be generated (labels) | No | |
24
- | `--versionSwagger` | Swagger OpenAPI specification version | :heavy_check_mark: | |
25
- | `--versionAPI` | API version | :heavy_check_mark: | |
26
- | `--title` | Title of the API document | :heavy_check_mark: | |
27
- | `--description` | Description of the API document | :heavy_check_mark: | |
28
- | `--contextURL` | JSON-LD context URL of the datastandard used in the API | :heavy_check_mark: | |
29
- | `--baseURL` | API base URL for endpoints and PURIs | :heavy_check_mark: | |
30
- | `--contactName` | Name of the person or organisation to contact about the API | No | |
31
- | `--contactEmail` | E-mail to contact for questions about the API | No | |
32
- | `--contactURL` | Link to follow as contact about the API | No | |
33
- | `--licenseName` | Name of the license of the API | No | |
34
- | `--licenseURL` | URL of the license of the API | No | |
35
- | `--silent` | Suppress log messages | No | `true` or `false` (default) |
24
+ | `--input` | The URL or local file path of an OSLO-compliant RDF file | :heavy_check_mark: | |
25
+ | `--output` | The name of the output file | :heavy_check_mark: | |
26
+ | `--language` | The language in which the Swagger must be generated (labels) | No | |
27
+ | `--versionSwagger` | Swagger OpenAPI specification version | :heavy_check_mark: | |
28
+ | `--versionAPI` | API version | :heavy_check_mark: | |
29
+ | `--title` | Title of the API document | :heavy_check_mark: | |
30
+ | `--description` | Description of the API document | :heavy_check_mark: | |
31
+ | `--contextURL` | JSON-LD context URL of the datastandard used in the API | :heavy_check_mark: | |
32
+ | `--baseURL` | API base URL for endpoints and PURIs | :heavy_check_mark: | |
33
+ | `--contactName` | Name of the person or organisation to contact about the API | No | |
34
+ | `--contactEmail` | E-mail to contact for questions about the API | No | |
35
+ | `--contactURL` | Link to follow as contact about the API | No | |
36
+ | `--licenseName` | Name of the license of the API | No | |
37
+ | `--licenseURL` | URL of the license of the API | No | |
38
+ | `--excludeClasses` | Classes to exclude from the generated Swagger | No | Persoon Organisatie Test |
39
+ | `--excludeProperties` | Properties to exclude from the generated Swagger | No | voornaam achternaam |
40
+ | `--silent` | Suppress log messages | No | `true` or `false` (default) |
36
41
 
37
42
  ## Usage
43
+
38
44
  ```bash
39
45
  oslo-generator-swagger --input report.jsonld --output swagger.json --language nl --versionSwagger 3.0.4 --versionAPI 1.0.0 --title "Mijn API" --description "Mijn API beschrijving." --contextURL http://example.com/context.jsonld --baseURL http://example.com
46
+ oslo-generator-swagger --input report.jsonld --output swagger.json --language nl --versionSwagger 3.0.4 --versionAPI 1.0.0 --title "Mijn API" --description "Mijn API beschrijving." --contextURL http://example.com/context.jsonld --baseURL http://example.com --excludeClasses Persoon Organisatie
47
+ oslo-generator-swagger --input report.jsonld --output swagger.json --language nl --versionSwagger 3.0.4 --versionAPI 1.0.0 --title "Mijn API" --description "Mijn API beschrijving." --contextURL http://example.com/context.jsonld --baseURL http://example.com --excludeClasses Persoon --excludeProperties Persoon.voornaam
48
+ oslo-generator-swagger --input report.jsonld --output swagger.json --language nl --versionSwagger 3.0.4 --versionAPI 1.0.0 --title "Mijn API" --description "Mijn API beschrijving." --contextURL http://example.com/context.jsonld --baseURL http://example.com --excludeClasses GeregistreerdPersoon --excludeProperties Persoon.voornaam Persoon.achternaam
40
49
  ```
@@ -10,6 +10,7 @@ export declare class SwaggerGenerationService implements IService {
10
10
  private getProbleemdetailsLink;
11
11
  private getContact;
12
12
  private getLicense;
13
+ private schemaExists;
13
14
  init(): Promise<void>;
14
15
  run(): Promise<void>;
15
16
  createSwagger(schemas: any, links: any): Object;
@@ -89,6 +89,15 @@ let SwaggerGenerationService = class SwaggerGenerationService {
89
89
  url: this.configuration.licenseURL,
90
90
  };
91
91
  }
92
+ /* The schema already exists in the model. This would mean that there are identical labels, which isn't ideal
93
+ https://vlaamseoverheid.atlassian.net/browse/DATAST-2249?atlOrigin=eyJpIjoiMWYyZjVmOTFhMWExNDc4MmIwMGQxNjhkMjJmN2NhZGUiLCJwIjoiaiJ9
94
+ */
95
+ schemaExists(schema, label) {
96
+ if (schema[label]) {
97
+ this.logger.warn(`[SwaggerGenerationService]: Schema already exists for the label (${label}) and will be overwritten.`);
98
+ }
99
+ return !!schema[label];
100
+ }
92
101
  async init() {
93
102
  return this.store.addQuadsFromFile(this.configuration.input);
94
103
  }
@@ -152,6 +161,10 @@ let SwaggerGenerationService = class SwaggerGenerationService {
152
161
  }
153
162
  /* Class labels should be always pascal cased */
154
163
  label = (0, core_1.toPascalCase)(label);
164
+ /* If a class is excluded via configuration, stop here */
165
+ if (this.configuration.excludeClasses.includes(label)) {
166
+ continue;
167
+ }
155
168
  /* Only keep links which are related to the class */
156
169
  for (const key of Object.keys(links)) {
157
170
  if (key.startsWith(`${label}.`))
@@ -248,7 +261,7 @@ let SwaggerGenerationService = class SwaggerGenerationService {
248
261
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
249
262
  const schemas = {};
250
263
  /* Create schema for each enumeration */
251
- for (const enumId of this.store.findSubjects(core_1.ns.oslo('assignedURI'), core_1.ns.skos('Concept'))) {
264
+ for (const enumId of this.store.getEnumerations()) {
252
265
  let label = (_a = (0, core_1.getApplicationProfileLabel)(enumId, this.store, this.configuration.language)) === null || _a === void 0 ? void 0 : _a.value;
253
266
  if (!label) {
254
267
  this.logger.error(`Unknown enum label for ${enumId.value}`);
@@ -256,6 +269,7 @@ let SwaggerGenerationService = class SwaggerGenerationService {
256
269
  }
257
270
  /* Class labels should be always pascal cased */
258
271
  label = (0, core_1.toPascalCase)(label);
272
+ this.schemaExists(schemas, label);
259
273
  schemas[label] = {
260
274
  title: label,
261
275
  type: 'object',
@@ -273,7 +287,9 @@ let SwaggerGenerationService = class SwaggerGenerationService {
273
287
  for (const classId of [
274
288
  ...this.store.findSubjects(core_1.ns.rdf('type'), core_1.ns.owl('Class')),
275
289
  ...this.store.findSubjects(core_1.ns.rdf('type'), core_1.ns.rdfs('Datatype')),
276
- ]) {
290
+ ]
291
+ // Extra filter to exclude all the enumerations since these are mentioned twice in the intermedairy format under enums and classes
292
+ .filter((val) => !(0, core_1.isEnumeration)(this.store, val))) {
277
293
  const assignedUri = this.store.getAssignedUri(classId);
278
294
  /* Primitive datatypes may not be generated */
279
295
  if (assignedUri && [...core_1.DataTypes.values()].includes(assignedUri.value))
@@ -294,6 +310,10 @@ let SwaggerGenerationService = class SwaggerGenerationService {
294
310
  }
295
311
  /* Class labels should be always pascal cased */
296
312
  label = (0, core_1.toPascalCase)(label);
313
+ /* If a class is excluded via configuration, stop here */
314
+ if (this.configuration.excludeClasses.includes(label)) {
315
+ continue;
316
+ }
297
317
  attributes[label] = [];
298
318
  requiredAttributes[label] = [];
299
319
  /* Find all attributes for object */
@@ -311,6 +331,11 @@ let SwaggerGenerationService = class SwaggerGenerationService {
311
331
  }
312
332
  /* Attribute labels should be always camel cased */
313
333
  attributeLabel = (0, core_1.toCamelCase)(attributeLabel);
334
+ // Addition for the `excludeProperties` flag to remove any reference to an excluded property
335
+ const classNameWithPropertyName = `${label}.${attributeLabel}`;
336
+ if (this.configuration.excludeProperties.includes(classNameWithPropertyName)) {
337
+ continue;
338
+ }
314
339
  if (!attributeRangeId) {
315
340
  this.logger.error(`Unknown range for attribute ${attributeId.value}`);
316
341
  continue;
@@ -344,7 +369,11 @@ let SwaggerGenerationService = class SwaggerGenerationService {
344
369
  }
345
370
  /* Arrays must be introduced into the schema if the max cardinality is 2 or more */
346
371
  const description = `${attributeDefinition}${attributeUsageNote ? ' ' + attributeUsageNote : ''}`;
347
- const properties = (0, Properties_1.mapProperties)(attributeDatatypeId, attributeDatatypeLabel, subclasses, attributeDatatypeAbstract);
372
+ const properties = (0, Properties_1.mapProperties)(attributeDatatypeId, attributeDatatypeLabel, subclasses, attributeDatatypeAbstract, this.configuration.excludeClasses);
373
+ // If mapProperties returns nothing, the property pointed to an excluded class (this.configuration.excludeClasses)
374
+ if (!properties) {
375
+ continue;
376
+ }
348
377
  const requiredProperties = properties
349
378
  ? Object.keys(properties)
350
379
  : undefined;
@@ -395,6 +424,7 @@ let SwaggerGenerationService = class SwaggerGenerationService {
395
424
  },
396
425
  };
397
426
  }
427
+ this.schemaExists(schemas, label);
398
428
  /* Create components for each schema */
399
429
  schemas[label] = {
400
430
  title: label,
@@ -437,6 +467,10 @@ let SwaggerGenerationService = class SwaggerGenerationService {
437
467
  }
438
468
  /* Class labels should be always pascal cased */
439
469
  label = (0, core_1.toPascalCase)(label);
470
+ /* If a class is excluded via configuration, stop here */
471
+ if (this.configuration.excludeClasses.includes(label)) {
472
+ continue;
473
+ }
440
474
  /* Find all attributes for object */
441
475
  for (const attributeId of attributeIds) {
442
476
  let attributeLabel = (_b = (0, core_1.getApplicationProfileLabel)(attributeId, this.store, this.configuration.language)) === null || _b === void 0 ? void 0 : _b.value;
@@ -447,6 +481,11 @@ let SwaggerGenerationService = class SwaggerGenerationService {
447
481
  }
448
482
  /* Attribute labels should be always camel cased */
449
483
  attributeLabel = (0, core_1.toCamelCase)(attributeLabel);
484
+ // Addition for the `excludeProperties` flag to remove any reference to an excluded property
485
+ const classNameWithPropertyName = `${label}.${attributeLabel}`;
486
+ if (this.configuration.excludeProperties.includes(classNameWithPropertyName)) {
487
+ continue;
488
+ }
450
489
  if (!attributeRangeId) {
451
490
  this.logger.error(`Unknown range for attribute ${attributeId.value}`);
452
491
  continue;
@@ -458,6 +497,10 @@ let SwaggerGenerationService = class SwaggerGenerationService {
458
497
  continue;
459
498
  }
460
499
  attributeDatatypeLabel = (0, core_1.toPascalCase)(attributeDatatypeLabel);
500
+ // Addition for the `excludeClasses` flag to remove any reference to an excluded class
501
+ if (this.configuration.excludeClasses.includes(attributeDatatypeLabel)) {
502
+ continue;
503
+ }
461
504
  /* Create all possible links for endpoint */
462
505
  if (!(0, core_1.isStandardDatatype)(attributeDatatypeId)) {
463
506
  links[`${label}.${attributeLabel}`] = {
@@ -32,6 +32,16 @@ class SwaggerGenerationServiceRunner extends core_1.AppRunner {
32
32
  .option('contactURL', { describe: 'API contact URL.' })
33
33
  .option('licenseName', { describe: 'API license name.' })
34
34
  .option('licenseURL', { describe: 'API license URL.' })
35
+ .option('excludeClasses', {
36
+ describe: 'A list of class names to exclude from the output.',
37
+ type: 'string',
38
+ array: true,
39
+ })
40
+ .option('excludeProperties', {
41
+ describe: 'A list of property names to exclude from the output.',
42
+ type: 'string',
43
+ array: true,
44
+ })
35
45
  .option('silent', {
36
46
  describe: 'All logs are suppressed',
37
47
  default: false,
@@ -56,6 +56,14 @@ export declare class SwaggerGenerationServiceConfiguration implements IConfigura
56
56
  * API metadata: license URL
57
57
  */
58
58
  private _licenseURL;
59
+ /**
60
+ * An array of class names to be excluded from the generated output
61
+ */
62
+ private _excludeClasses;
63
+ /**
64
+ * An array of property names to be excluded from the generated output
65
+ */
66
+ private _excludeProperties;
59
67
  createFromCli(params: YargsParams): Promise<void>;
60
68
  get input(): string;
61
69
  get output(): string;
@@ -71,4 +79,6 @@ export declare class SwaggerGenerationServiceConfiguration implements IConfigura
71
79
  get contactEmail(): string | undefined;
72
80
  get licenseName(): string | undefined;
73
81
  get licenseURL(): string | undefined;
82
+ get excludeClasses(): string[];
83
+ get excludeProperties(): string[];
74
84
  }
@@ -24,6 +24,8 @@ let SwaggerGenerationServiceConfiguration = class SwaggerGenerationServiceConfig
24
24
  this._contactEmail = params.contactEmail;
25
25
  this._licenseName = params.licenseName;
26
26
  this._licenseURL = params.licenseURL;
27
+ this._excludeClasses = params.excludeClasses;
28
+ this._excludeProperties = params.excludeProperties;
27
29
  }
28
30
  get input() {
29
31
  if (!this._input) {
@@ -91,6 +93,12 @@ let SwaggerGenerationServiceConfiguration = class SwaggerGenerationServiceConfig
91
93
  get licenseURL() {
92
94
  return this._licenseURL;
93
95
  }
96
+ get excludeClasses() {
97
+ return this._excludeClasses || [];
98
+ }
99
+ get excludeProperties() {
100
+ return this._excludeProperties || [];
101
+ }
94
102
  };
95
103
  SwaggerGenerationServiceConfiguration = __decorate([
96
104
  (0, inversify_1.injectable)()
@@ -1 +1 @@
1
- export declare const mapProperties: (datatype: string, label: string, subclasses: string[], abstract: boolean) => object;
1
+ export declare const mapProperties: (datatype: string, label: string, subclasses: string[], abstract: boolean, excludeClasses: string[]) => object | undefined;
@@ -30,39 +30,47 @@ const Properties = new Map([
30
30
  [core_1.ns.xsd('unsignedShort').value, { '@value': { type: 'number', format: 'int32', minimum: 0, maximum: 65535 }, '@type': { type: 'string', pattern: '^UnsignedShort$' } }],
31
31
  [core_1.ns.xsd('unsignedByte').value, { '@value': { type: 'number', format: 'int32', minimum: 0, maximum: 255 }, '@type': { type: 'string', pattern: '^UnsignedByte$' } }],
32
32
  [core_1.ns.xsd('decimal').value, { '@value': { type: 'string', pattern: '(+|-)?([0-9]+(.[0-9]*)?|.[0-9]+)' }, '@type': { type: 'string', pattern: '^Decimal$' } }],
33
+ [core_1.ns.xsd('language').value, { '@value': { type: 'string', pattern: '^[a-z]{2,3}(-[A-Z]{2})?' }, '@type': { type: 'string', pattern: '^Language$' } }],
33
34
  ]);
34
35
  /* eslint-enable max-len*/
35
- const mapProperties = (datatype, label, subclasses, abstract) => {
36
+ const mapProperties = (datatype, label, subclasses, abstract, excludeClasses) => {
36
37
  /* Primitive data type conversion from Linked Data to Swagger */
37
38
  if (Properties.has(datatype)) {
38
39
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
39
40
  return Properties.get(datatype);
40
41
  }
41
- /* Multiple subclasses requires a discriminator to link both the superclass and subclasses as schemas */
42
- if (subclasses.length > 0) {
43
- const mapping = {};
44
- const oneOf = [];
45
- /* Only allow super class if it is not abstract */
46
- if (!abstract) {
47
- mapping[label] = `#/components/schemas/${label}`;
48
- oneOf.push({ $ref: `#/components/schemas/${label}` });
49
- }
50
- for (const subclass of subclasses) {
51
- const ref = `#/components/schemas/${subclass}`;
52
- oneOf.push({ $ref: ref });
53
- mapping[subclass] = ref;
42
+ const oneOf = [];
43
+ // Add the superclass to the list if it's not abstract and not excluded
44
+ if (!abstract && !excludeClasses.includes(label)) {
45
+ oneOf.push({ $ref: `#/components/schemas/${label}` });
46
+ }
47
+ // Add all subclasses that are not excluded
48
+ for (const subclass of subclasses) {
49
+ if (!excludeClasses.includes(subclass)) {
50
+ oneOf.push({ $ref: `#/components/schemas/${subclass}` });
54
51
  }
55
- return {
56
- oneOf,
57
- discriminator: {
58
- propertyName: '@type',
59
- mapping,
60
- },
61
- };
62
52
  }
63
- /* Regular schemas without any subclassing */
53
+ // If no valid classes are left for this property, it should be omitted
54
+ if (oneOf.length === 0) {
55
+ return undefined;
56
+ }
57
+ // If only one class remains, we don't need a discriminator
58
+ if (oneOf.length === 1) {
59
+ return oneOf[0];
60
+ }
61
+ // If multiple classes remain, build the discriminator object
62
+ const mapping = {};
63
+ for (const item of oneOf) {
64
+ const refParts = item.$ref.split('/');
65
+ const className = refParts[refParts.length - 1];
66
+ mapping[className] = item.$ref;
67
+ }
64
68
  return {
65
- $ref: `#/components/schemas/${label}`,
69
+ oneOf,
70
+ discriminator: {
71
+ propertyName: '@type',
72
+ mapping,
73
+ },
66
74
  };
67
75
  };
68
76
  exports.mapProperties = mapProperties;
@@ -91,3 +91,6 @@ export interface SwaggerLink {
91
91
  parameters: Record<string, string>;
92
92
  description: string;
93
93
  }
94
+ export interface Schema {
95
+ [key: string]: any;
96
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oslo-flanders/swagger-generator",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Generate OpenAPI Swagger YAML documentation.",
5
5
  "author": "Digitaal Vlaanderen <https://data.vlaanderen.be/id/organisatie/OVO002949>",
6
6
  "homepage": "https://github.com/informatievlaanderen/OSLO-UML-Transformer/tree/main/packages/oslo-generator-swagger#readme",
@@ -40,7 +40,7 @@
40
40
  "@types/streamify-array": "^1.0.0"
41
41
  },
42
42
  "dependencies": {
43
- "@oslo-flanders/core": "^1.1.0",
43
+ "@oslo-flanders/core": "^1.2.0",
44
44
  "inversify": "^6.0.1",
45
45
  "n3": "^2.0.0",
46
46
  "rdf-data-factory": "^2.0.0",