Swagger TypeScript code generator
Node module to generate TypeScript code for Angular (2 and above) based on Webapi meta data in Swagger v2 format.
Use it in combination with the sibling package angular-swagger-form-field to implement reactive forms.
See angular-swagger-form-field-sample for a sample how to use the generated classes.
Setup
Download the module with npm:
npm install --save-dev zm-swagger-to-ts
Usage in NodeJS
Create a simple .js
file and run it using node path/to/file.js
You can then run this from npm
by adding the node
line from above as a task in your package.json
const { generateTSFiles } = require("zm-swagger-to-ts");
const config = {
file: __dirname + "\\swagger.json"
};
generateTSFiles(
config.file,
{
modelFolder: "./path/to/models",
enumTSFile: "./path/to/models/enums.ts"
}
);
Usage in Gulp
gulp.config
Create a gulp.config
file with the settings you want:
- generateClasses (default: true) - If this flag is set to false, the generator will output only interfaces and enums. It will not output classes and validators.
"use strict";
module.exports = config();
function config() {
var root = "./src/";
var srcAppFolder = root + "app/";
var folders = {
root: root,
srcWebapiFolder: srcAppFolder + "models/webapi/",
srcLanguagesFolder: root + "assets/i18n/",
swaggerFolder: root + "swagger/"
};
var files = {
swaggerJson: "swagger.json"
};
var swagger = {
url: "http://petstore.swagger.io/v2/swagger.json",
swaggerFile: folders.swaggerFolder + files.swaggerJson,
swaggerFolder: folders.swaggerFolder,
swaggerTSGeneratorOptions: {
modelFolder: folders.srcWebapiFolder,
enumTSFile: folders.srcWebapiFolder + "enums.ts",
enumI18NHtmlFile: folders.enumI18NHtmlFolder + "enum-i18n.component.html",
enumLanguageFiles: [
folders.srcLanguagesFolder + "nl.json",
folders.srcLanguagesFolder + "en.json"
],
generateClasses: true,
modelModuleName: "webapi.models",
enumModuleName: "webapi.enums",
enumRef: "./enums",
subTypePropertyName: "typeSelector",
namespacePrefixesToRemove: [],
typeNameSuffixesToRemove: [],
typesToFilter: [
"ModelAndView",
"View"
]
}
};
var config = {
root: root,
files: files,
swagger: swagger
};
return config;
}
gulpfile.js
Create a gulpfile.js
:
"use strict";
var gulp = require("gulp");
var $ = require("gulp-load-plugins")({ lazy: true });
var args = require("yargs").argv;
var swaggerTSGenerator = require("zm-swagger-to-ts");
var request = require("request");
var source = require("vinyl-source-stream");
var config = require("./gulp.config");
gulp.task("default", ["show-help"]);
gulp.task("show-help", $.taskListing);
gulp.task("gen", ["gen-webapi"]);
gulp.task("gen-webapi", ["gen-webapi-download-swagger"], genWebapi);
gulp.task("gen-webapi-download-swagger", genWebapiDownloadSwagger);
function genWebapi(done) {
swaggerTSGenerator.generateTSFiles(
config.swagger.swaggerFile,
config.swagger.swaggerTSGeneratorOptions
);
done();
}
function genWebapiDownloadSwagger(done) {
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
return request
.get({
url: config.swagger.url,
headers: {
"User-Agent": "request",
"content-type": "application/json"
}
})
.pipe(customPlumber("Error gen-webapi-autorest"))
.pipe(source(config.files.swaggerJson))
.pipe($.streamify($.jsbeautifier()))
.pipe(gulp.dest(config.folders.swaggerFolder));
}
function customPlumber(errTitle) {
return $.plumber({
errorHandler: $.notify.onError({
title: errTitle || "Error running Gulp",
message: "Error: <%= error.message %>",
sound: "Glass"
})
});
}
function log(msg) {
$.util.log($.util.colors.yellow(msg));
}
Execute the gulp task(s)
Download the swagger file and generate the code:
gulp gen
Generated files
validators.ts
The generated validators.ts is fixed (its always generated regardless of the Swagger).
It contains some extra validators to implement validation rules for the Swagger which are not part of the standard Angular validators:
maxValueValidator;
minValueValidator;
base-model.ts
The generated base-model.ts is fixed (its always generated regardless of the Swagger).
It contains the base class for all generated models. The next members can be used in your own software:
$formGroup: FormGroup;
addValidatorToControl(controlName: string, validators: ValidatorFn | ValidatorFn[]) {
...
}
}
sub-type-factory.ts
This class is used in the generated models to instantiate subTypes. It uses the swaggerTSGeneratorOptions.subTypePropertyName
from the generator config.
The generated file looks like this:
import { Company } from './company.model';
...
export class SubTypeFactory {
static createSubTypeInstance(value: any, useFormGroupValuesToModel = false): object {
switch (value.typeSelector) {
case 'Company':
return new Company(value, useFormGroupValuesToModel);
...
default:
throw new Error(`${value.typeSelector} not supported here`);
}
}
}
*.model.ts
For each definition in the Swagger an Interface and a Class are generated.
The class contains the $FormGroup
property to be used in the Angular FormBuilder to make a model driven form.
The controls in the FormGroup
contain the validators which implement the validation rules from the Swagger defnition.
Properties of an enum type are generated referencing this type which are generated in the next section.
This is an example of a generated TypeScript file with one model (definition) from the Swagger file:
import {
Validators,
FormControl,
FormGroup,
FormArray,
ValidatorFn
} from "@angular/forms";
import {
minValueValidator,
maxValueValidator,
enumValidator
} from "./validators";
import { BaseModel } from "./base-model";
import { SubTypeFactory } from "./sub-type-factory";
import { type } from "./enums";
import { gender } from "./enums";
import { Address } from "./address.model";
import { Veterinarian } from "./veterinarian.model";
import { Tag } from "./tag.model";
import { NullableOrEmpty } from "./nullable-or-empty.model";
export interface IPet {
name: string;
age?: number;
dob?: Date;
type: type;
gender?: gender;
address?: Address;
vet?: Veterinarian;
tags?: Array<Tag>;
isFavorate?: boolean;
testDate?: NullableOrEmpty<Date>;
primitiveArray?: Array<string>;
}
export class Pet extends BaseModel implements IPet {
name: string;
age: number;
dob: Date;
type: type;
gender: gender;
address: Address;
vet: Veterinarian;
tags: Array<Tag>;
isFavorate: boolean;
testDate: NullableOrEmpty<Date>;
primitiveArray: Array<string>;
constructor(values?: any, useFormGroupValuesToModel = false) {
super();
this.address = new Address();
this.vet = new Veterinarian();
this.tags = new Array<Tag>();
this.testDate = new NullableOrEmpty<Date>();
this.primitiveArray = new Array<string>();
if (values) {
this.setValues(values, useFormGroupValuesToModel);
}
}
setValues(values: any, useFormGroupValuesToModel = false): void {
if (values) {
const rawValues = this.getValuesToUse(values, useFormGroupValuesToModel);
this.name = rawValues.name;
this.age = rawValues.age;
this.dob = rawValues.dob;
this.type = rawValues.type;
this.gender = rawValues.gender;
this.address.setValues(rawValues.address, useFormGroupValuesToModel);
this.vet.setValues(rawValues.vet, useFormGroupValuesToModel);
this.fillModelArray<Tag>(
this,
"tags",
rawValues.tags,
Tag,
useFormGroupValuesToModel,
SubTypeFactory.createSubTypeInstance,
Tag
);
this.isFavorate = rawValues.isFavorate;
this.testDate.setValues(rawValues.testDate, useFormGroupValuesToModel);
this.fillModelArray<string>(
this,
"primitiveArray",
rawValues.primitiveArray,
useFormGroupValuesToModel,
SubTypeFactory.createSubTypeInstance,
string
);
super.setValuesInAddedPropertiesOfAttachedFormControls(
values,
useFormGroupValuesToModel
);
}
}
protected getFormGroup(): FormGroup {
if (!this._formGroup) {
this._formGroup = new FormGroup({
name: new FormControl(this.name, [
Validators.required,
Validators.minLength(4),
Validators.pattern("^[a-zA-Z0-9- ]+$")
]),
age: new FormControl(this.age),
dob: new FormControl(this.dob),
type: new FormControl(this.type, [Validators.required]),
gender: new FormControl(this.gender),
address: this.address.$formGroup,
vet: this.vet.$formGroup,
tags: new FormArray([]),
isFavorate: new FormControl(this.isFavorate),
testDate: this.testDate.$formGroup,
primitiveArray: new FormArray([])
});
this.fillFormArray<Tag>("tags", this.tags, Tag);
this.fillFormArray<string>("primitiveArray", this.primitiveArray);
}
return this._formGroup;
}
setFormGroupValues() {
this.$formGroup.controls["name"].setValue(this.name);
this.$formGroup.controls["age"].setValue(this.age);
this.$formGroup.controls["dob"].setValue(this.dob);
this.$formGroup.controls["type"].setValue(this.type);
this.$formGroup.controls["type"].setValue(this.type);
this.$formGroup.controls["gender"].setValue(this.gender);
this.address.setFormGroupValues();
this.vet.setFormGroupValues();
this.fillFormArray<Tag>("tags", this.tags, Tag);
this.$formGroup.controls["isFavorate"].setValue(this.isFavorate);
this.testDate.setFormGroupValues();
this.fillFormArray<string>("primitiveArray", this.primitiveArray, string);
this.$formGroup.controls["minimumPremiumPercentage"].setValue(
this.minimumPremiumPercentage
);
super.setFormGroupValuesInAddedFormControls();
}
}
Custom models
For custom models you can use the following data in your HBS template
interface TemplateData {
generateClasses: boolean;
hasComplexType: boolean;
validatorFileName: string;
baseModelFileName: string;
moduleName: string;
enumModuleName: string;
enumRef: string;
subTypePropertyName: string;
type: Type;
}
interface Type {
fileName: string;
typeName: string;
namespace: string;
fullNamespace: string;
fullTypeName: string;
isSubType: boolean;
baseType: Type;
baseImportFile: string;
path: string;
pathToRoot: string;
properties: TypeProperty[];
}
interface TypeProperty {
name: string;
typeName: string;
namespace: string;
description: string;
hasValidation: boolean;
isComplexType: boolean;
isImportType: boolean;
isUniqueImportType: boolean;
importType: string;
importFile: string;
isEnum: boolean;
isUniqueImportEnumType: boolean;
importEnumType: string;
isArray: boolean;
isArrayComplexType: boolean;
arrayTypeName: string;
validators: {
validation: {
required: boolean;
minimum: number;
maximum: number;
enum: string;
minLength: number;
maxLength: number;
pattern: string;
};
validatorArray: string[];
};
enum: string[];
}
enums.ts
This is een excerpt from a generated TypeScript file with enums.
export enum type {
cat = <any>"cat",
dog = <any>"dog",
bird = <any>"bird",
whale = <any>"whale"
}
export enum gender {
unknown = <any>"unknown",
male = <any>"male",
female = <any>"female"
}
export enum hairColor {
red = <any>"red",
blond = <any>"blond",
brown = <any>"brown",
black = <any>"black",
white = <any>"white",
gray = <any>"gray"
}
export class AllEnums {
private static _instance: AllEnums = new AllEnums();
constructor() {
if (AllEnums._instance) {
throw new Error(
"Error: Instantiation failed: Use AllEnums.instance instead of new"
);
}
AllEnums._instance = this;
}
static get instance(): AllEnums {
return AllEnums._instance;
}
type = type;
gender = gender;
hairColor = hairColor;
}
Normally enums are numbers based in TypeScript. In out Webapi's whe use stringbased Enums.
The thick with cat = <any>
"cat"
is used to make the TypeScript enums string based.
enum-i18n.component.html
When the I18N features of the angular-cli are used in your application, a view can be gerenarted containing a translatable entry for each enum value. This is triggered by the presence of the enumLanguageFiles
property in the options in the gulp.config
file.
<div class="hidden">
<span i18n="type.cat|ngx-translate">Cat</span>
<span i18n="type.dog|ngx-translate">Dog</span>
<span i18n="type.brid|ngx-translate">Bird</span>
<span i18n="type.whale|ngx-translate">Whale</span>
...
</div>
Using the xliffmerge tool, this file can be used to generate the enum language .json
files for ngx-translate.
Make sure this view is part of lazy loaded Angular component (it does not have to be loaded, it will only be used by the angular-cli i18n extraction tool).
enums language files
As an alternative for the enum-i18n.component.html
file from the section above, enum language files can be generated. Translation must be done by hand. Each new enum value is added to the given enumLanguageFile(s). Enum values already present are left intact.
{
"type": "-------ENUM-TYPE-------",
"cat": "kat",
"dog": "hond",
"bird": "vogel",
"whale": "whale",
"gender": "-------ENUM-TYPE-------",
"unknown": "onbekend",
"male": "man",
"female": "vrouw",
"hairColor": "-------ENUM-TYPE-------",
"red": "rood",
"blond": "blond",
"brown": "bruin",
"black": "zwart",
"white": "wit",
"gray": "grijs"
}
index.ts
This barrel file contains references to all generated files.