Skip to content

Commit 9ce1657

Browse files
Merge pull request #14 from ProtocolNebula/feature/3-custom-templates
Feature/3 custom templates Feature/6 custom types mapping Feature/7 multi language support natively (through templates)
2 parents acbfca2 + 16ed714 commit 9ce1657

File tree

18 files changed

+334
-86
lines changed

18 files changed

+334
-86
lines changed

Readme.md

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Is recommended to add a script to your `package.json` or package manager that yo
3333
> Change `src/generatedApi` by your desired folder to generate the code.
3434
3535
```bash
36-
transform-swagger -f URI/TO/SWAGGER/JSON/OR/YAML -o src/generatedApi/
36+
transform-swagger -f URI/TO/SWAGGER/JSON/OR/YAML -o src/generatedApi/ -t angular2
3737
```
3838

3939
### Main CLI commands
@@ -42,17 +42,19 @@ transform-swagger -f URI/TO/SWAGGER/JSON/OR/YAML -o src/generatedApi/
4242
Usage: transform-swagger [options]
4343

4444
Options:
45-
--version Show version number [boolean]
45+
--version, -v Show version number [boolean]
4646
--clean No clean the output-folder, so old files will remain
47-
[boolean] [default: true]
48-
-f, --file Path OR URL to the swagger document to parse [required]
47+
[boolean] [default: true]
48+
-f, --file Path OR URL to the swagger document to parse
49+
[required]
4950
-o, --output-folder Specify the output folder (generated folders will be
50-
replaced) [default: "output"]
51-
-h, --help Show help [boolean]
51+
replaced) [default: "output"]
52+
-t, --template Template (preset) name or path to a template
53+
-h, --help Show help [boolean]
5254

5355
Examples:
54-
transform-swagger -f swagger.js Convert a Swagger JSON file to
55-
compatible-angular API
56+
transform-swagger -f swagger.js -o Convert a Swagger JSON file to
57+
api/ -t angular2 compatible-angular API
5658
```
5759

5860
## Development
@@ -71,6 +73,6 @@ npm run start:dev -- -f URL_TO_SWAGGER.JSON/YAML
7173
> You can re-use the downloaded file or specify another folder
7274
7375
```
74-
npm run start:dev -- -f output/temp.json
76+
npm run start:dev -- -f output/temp.json -o src/generatedApi/ -t angular2
7577
```
7678

changelog.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## [Unreleased]
88

9+
### Added
10+
- Issue #3 - Custom templates: Now you can create a custom template and use it without modify the source code.
11+
- Issue #6 - Custom enums mapping: As part of a template, enums are configurable.
12+
- Issue #7 - Default templates support. Now current template is named `angular2`, so you must to add `-t angular2` to you `cli script`
13+
```bash
14+
transform-swagger -f ...json -o src/generatedApi/ -t angular2
15+
```
16+
- `ConfigModel` class
17+
- `TemplateConfigModel` class
18+
19+
### Changed
20+
- Output folders now are retrieved from ConfigModel.
21+
- Now `template` is required as parameter.
22+
- Default type added.
23+
- `Yargs` (arguments) are not read directly, are parsed to `ConfigModel`.
924

1025
## [2.1.3] - 2021-01-05
1126

src/main.ts

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as fs from 'fs-extra';
2-
import * as path from 'path';
2+
import { config } from './models/config.model';
33
import { argumentsInstance } from './services/arguments.service';
44
import { FileReaderService } from './services/parsers/file-reader.service';
55
import { APIParserService } from './services/parsers/open-api-v3/api-parser.service';
@@ -8,36 +8,34 @@ import { ApiWritterService } from './services/writters/api-writter.service';
88
import { ModelWritterService } from './services/writters/model-writter.service';
99
import { Store } from './stores/entities.store';
1010

11-
// FOLDERS INFORMATION
12-
const OUTPUT_PATH = argumentsInstance.outputFolder + path.sep;
11+
config.parseYargs(argumentsInstance);
1312

14-
const GENERATED_FOLDER = OUTPUT_PATH + 'generated' + path.sep;
15-
const OUTPUT_FOLDERS = {
16-
OUTPUT_PATH: OUTPUT_PATH,
17-
BASE_FOLDER: GENERATED_FOLDER,
18-
MODELS: GENERATED_FOLDER + 'models',
19-
APIS: GENERATED_FOLDER + 'api',
20-
};
13+
// FOLDERS INFORMATION
2114
console.info('Output folders:');
22-
console.table(OUTPUT_FOLDERS);
23-
24-
console.log('');
25-
if (argumentsInstance.clean) {
26-
console.log('Removing previously generated data...');
27-
fs.removeSync(GENERATED_FOLDER);
28-
} else {
29-
console.log('no-clean flag recevived, clean folder skipped');
30-
}
31-
console.log('');
15+
console.table({
16+
OUTPUT_PATH: config.outputPath,
17+
BASE_FOLDER: config.exportPath,
18+
MODELS: config.exportPath,
19+
APIS: config.exportPath,
20+
});
3221

3322
// Read the file
3423
async function run() {
3524
try {
25+
// Check if the template does not exist
26+
config.templatePath;
27+
28+
console.log('');
29+
if (argumentsInstance.clean) {
30+
console.log('Removing previously generated data...');
31+
fs.removeSync(config.exportPath);
32+
} else {
33+
console.log('no-clean flag recevived, clean folder skipped');
34+
}
35+
console.log('');
36+
3637
console.log('Opening file:', argumentsInstance.file);
37-
const fileParser = new FileReaderService(
38-
argumentsInstance.file,
39-
OUTPUT_FOLDERS,
40-
);
38+
const fileParser = new FileReaderService(argumentsInstance.file, config);
4139
console.log('Parsing file...');
4240
console.log('');
4341
const documentParsed = await fileParser.readFile();
@@ -51,9 +49,9 @@ async function run() {
5149

5250
console.info('');
5351
console.info('Generating files');
54-
const modelWritter = new ModelWritterService(OUTPUT_FOLDERS, Store);
52+
const modelWritter = new ModelWritterService(Store, config);
5553
modelWritter.write();
56-
const apiWritter = new ApiWritterService(OUTPUT_FOLDERS, Store);
54+
const apiWritter = new ApiWritterService(Store, config);
5755
apiWritter.write();
5856
console.info('Files generation finished');
5957

src/models/api.model.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { camel, capital } from 'case';
2+
import { getFixedTypeName } from '../utils/models.util';
23
import { ModelStore } from '../stores/model.store';
34
import { ApiURLModel } from './api-url.model';
45
import { PhysycalFile } from './entities';
@@ -58,7 +59,7 @@ export class ApiModel implements PhysycalFile {
5859
return this.requestBody?.type;
5960
}
6061
get responseType(): string {
61-
return this.response?.type || 'void';
62+
return this.response?.type || getFixedTypeName('empty');
6263
}
6364

6465
get verb(): string {

src/models/config.model.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { existsSync } from 'fs';
2+
import { resolve as pathResolve } from 'path';
3+
import { TemplateConfigModel } from './template-config.model';
4+
5+
export interface ConfigI {
6+
outputPath: string;
7+
exportPath: string;
8+
outputModelsPath: string;
9+
outputApisPath: string;
10+
templateConfig: TemplateConfigModel;
11+
}
12+
13+
class ConfigModel implements ConfigI {
14+
private _outputPath: string;
15+
private _outputBaseFolder: string = 'generated';
16+
private _outputModelsFolder: string = 'models';
17+
private _outputApisFolder: string = 'apis';
18+
19+
private _template: string;
20+
private _templatePath: string;
21+
private _templateConfig: TemplateConfigModel;
22+
23+
get outputPath(): string {
24+
return this._outputPath;
25+
}
26+
27+
get exportPath(): string {
28+
return pathResolve(this.outputPath, this._outputBaseFolder);
29+
}
30+
31+
get outputModelsPath(): string {
32+
return pathResolve(this.exportPath, this._outputModelsFolder);
33+
}
34+
35+
get outputApisPath(): string {
36+
return pathResolve(this.exportPath, this._outputApisFolder);
37+
}
38+
39+
get template(): string {
40+
if (!this._template) {
41+
throw 'No template defined';
42+
}
43+
return this._template;
44+
}
45+
46+
set template(template: string) {
47+
this._template = template;
48+
this._templatePath = null;
49+
this._templateConfig = null;
50+
}
51+
52+
get templatePath(): string {
53+
if (!this._templatePath) {
54+
// Generate the template path
55+
const template = this.template;
56+
if (existsSync(template)) {
57+
this._templatePath = template;
58+
} else {
59+
this._templatePath = pathResolve(
60+
__dirname,
61+
'..',
62+
'..',
63+
'templates',
64+
template,
65+
);
66+
}
67+
}
68+
69+
if (!existsSync(this._templatePath)) {
70+
throw `Template ${this.template} not exist`;
71+
}
72+
return this._templatePath;
73+
}
74+
75+
get templateConfig(): TemplateConfigModel {
76+
if (!this._templateConfig) {
77+
this._templateConfig = new TemplateConfigModel(pathResolve(this.templatePath, 'config'));
78+
}
79+
return this._templateConfig;
80+
}
81+
82+
constructor() {}
83+
84+
parseYargs(yargs): void {
85+
this._outputPath = yargs.outputFolder;
86+
this.template = yargs.template;
87+
}
88+
}
89+
90+
export const config = new ConfigModel();
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { fileExtension } from "../utils/files.util";
2+
3+
export interface TypesMapped {
4+
/**
5+
* Type if NO TYPE (literally nothing)
6+
*/
7+
empty: string;
8+
9+
[type: string]: string;
10+
default: string;
11+
boolean: string;
12+
file: string;
13+
integer: string;
14+
number: string;
15+
object: string;
16+
string: string;
17+
}
18+
19+
export interface TemplateConfigI {
20+
// Template files
21+
apiFile: string;
22+
modelFile: string;
23+
enumModelFile: string;
24+
25+
/**
26+
* Types mapping from 'OpenAPI' to the language template
27+
* `default` will be used as default type (if not defined/not in the list)
28+
*/
29+
typesMapped: TypesMapped;
30+
}
31+
32+
export class TemplateConfigModel implements TemplateConfigI {
33+
private configMapped: TemplateConfigI;
34+
35+
get apiFile(): string {
36+
return this.configMapped.apiFile;
37+
}
38+
get apiExtension(): string {
39+
return fileExtension(this.configMapped.apiFile);
40+
}
41+
get modelFile(): string {
42+
return this.configMapped.modelFile;
43+
}
44+
get modelExtension(): string {
45+
return fileExtension(this.configMapped.modelFile);
46+
}
47+
get enumModelFile(): string {
48+
return this.configMapped.enumModelFile;
49+
}
50+
get enumModelExtension(): string {
51+
return fileExtension(this.configMapped.enumModelFile);
52+
}
53+
54+
get typesMapped(): TypesMapped {
55+
return this.configMapped.typesMapped;
56+
}
57+
58+
constructor(templatePath: string) {
59+
this.configMapped = require(templatePath);
60+
}
61+
}

src/services/arguments.service.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ export const argumentsInstance = yargs
55
.usage('Usage: $0 [options]')
66
// .command('count', 'Count the lines in a file')
77
.example(
8-
'$0 -f swagger.js',
8+
'$0 -f swagger.js -o api/ -t angular2',
99
'Convert a Swagger JSON file to compatible-angular API',
1010
)
11-
.alias('f', 'file')
1211
.nargs('file', 1)
12+
.alias('f', 'file')
1313
.describe('file', 'Path OR URL to the swagger document to parse')
1414
.demandOption(['file'])
1515
.alias('o', 'output-folder')
1616
.nargs('output-folder', 1)
17+
.nargs('template', 1)
18+
.alias('t', 'template')
19+
.describe('template', 'Template (preset) name or path to a template')
20+
.implies('file', 'output-folder')
21+
.implies('file', 'template')
1722
.describe(
1823
'output-folder',
1924
'Specify the output folder (generated folders will be replaced)',
@@ -24,4 +29,5 @@ export const argumentsInstance = yargs
2429
.default('clean', true)
2530
.help('help')
2631
.alias('h', 'help')
27-
.epilog('copyright 2020').argv;
32+
.epilog('For more info visit: https://github.com/ProtocolNebula/ts-openapi-generator/')
33+
.argv;

src/services/parsers/file-reader.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { OpenAPIV3 } from 'openapi-types';
2+
import { config, ConfigI } from '../../models/config.model';
23
import {
34
downloadFile,
45
fileExtension,
@@ -25,7 +26,7 @@ export class FileReaderService {
2526
return this._document;
2627
}
2728

28-
constructor(private path, private outputFolders: any) {}
29+
constructor(private path, private configuration: ConfigI = config) {}
2930

3031
async readFile(): Promise<OpenAPIV3.Document> {
3132
await this.prepareFile();
@@ -60,7 +61,7 @@ export class FileReaderService {
6061
}
6162

6263
if (isURL(this.path)) {
63-
this.localFilePath = `${this.outputFolders.OUTPUT_PATH}temp.${extension}`;
64+
this.localFilePath = `${this.configuration.outputPath}temp.${extension}`;
6465
console.log(`${this.path} will be saved as ${this.localFilePath}...`);
6566
await downloadFile(this.path, this.localFilePath);
6667
console.log(`${this.localFilePath} saved!`);

src/services/parsers/open-api-v3/parser-base.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export abstract class ParserBaseService {
125125
console.warn('WARNING: No schema defined! Any will be use instead');
126126
console.warn('TIP: Don\'t fill "content" for responses if void');
127127
const instance = new ModelAttributessModel(null);
128-
instance.typeURI = 'any';
128+
instance.typeURI = 'default';
129129
return instance;
130130
}
131131
if (this.isRefObject(schema)) {
@@ -227,9 +227,9 @@ export abstract class ParserBaseService {
227227
console.warn(
228228
`WARNING: ${attrName} not recognized OpenAPIV3 Schema type ${JSON.stringify(
229229
rawAttribute,
230-
)}. Any will be used instead.`,
230+
)}. DEFAULT will be used instead.`,
231231
);
232-
attribute.typeURI = 'any';
232+
attribute.typeURI = 'default';
233233
}
234234
}
235235
}

0 commit comments

Comments
 (0)