- Simplified schema generation

This commit is contained in:
Ferdi Koomen 2019-12-23 15:51:55 +01:00
parent 9a1fa39ada
commit 4aa7f0ef67
28 changed files with 354 additions and 828 deletions

View File

@ -10,7 +10,7 @@ program
.version(pkg.version)
.option('--input [value]', 'Path to swagger specification', './spec.json')
.option('--output [value]', 'Output directory', './generated')
.option('--http-client [value]', 'HTTP client to generate [fetch, xhr]', 'fetch')
.option('--client [value]', 'HTTP client to generate [fetch, xhr]', 'fetch')
.parse(process.argv);
const SwaggerCodegen = require(path.resolve(__dirname, '../dist/index.js'));
@ -19,6 +19,6 @@ if (SwaggerCodegen) {
SwaggerCodegen.generate(
program.input,
program.output,
program.httpClient
program.client
);
}

View File

@ -1,6 +1,6 @@
{
"name": "openapi-typescript-codegen",
"version": "0.1.7",
"version": "0.1.8",
"description": "NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.",
"author": "Ferdi Koomen",
"homepage": "https://github.com/ferdikoomen/openapi-typescript-codegen",

View File

@ -18,7 +18,7 @@ export enum HttpClient {
* service layer, etc.
* @param input The relative location of the OpenAPI spec.
* @param output The relative location of the output directory.
* @param httpClient: The selected httpClient (fetch or XHR).
* @param httpClient The selected httpClient (fetch or XHR).
*/
export function generate(input: string, output: string, httpClient: HttpClient = HttpClient.FETCH): void {
const inputPath = path.resolve(process.cwd(), input);

View File

@ -1,40 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
export type FieldSchema = {
readonly type?: string;
readonly isReadOnly?: boolean;
readonly isRequired?: boolean;
readonly isNullable?: boolean;
readonly format?: string;
readonly maximum?: number;
readonly exclusiveMaximum?: boolean;
readonly minimum?: number;
readonly exclusiveMinimum?: boolean;
readonly multipleOf?: number;
readonly maxLength?: number;
readonly minLength?: number;
readonly pattern?: string;
readonly maxItems?: number;
readonly minItems?: number;
readonly uniqueItems?: boolean;
readonly maxProperties?: number;
readonly minProperties?: number;
}
export type ObjectSchema<T> = FieldSchema & {
properties?: {
readonly [K in keyof T]: Schema<T[K]>;
}
}
export type Schema<T> =
T extends string ? FieldSchema :
T extends number ? FieldSchema :
T extends boolean ? FieldSchema :
T extends File ? FieldSchema :
T extends Blob ? FieldSchema :
T extends Object ? ObjectSchema<T> :
FieldSchema

View File

@ -1,17 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
import * as schemas from '../schemas';
/**
* Get a schema object for a given model name.
* @param model The model name to return the schema from.
*/
export function getSchema<K extends keyof typeof schemas, T>(model: K) {
if (schemas.hasOwnProperty(model)) {
return schemas[model];
}
return null;
}

26
src/templates/index.hbs Normal file
View File

@ -0,0 +1,26 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
export { ApiError } from './core/ApiError';
export { isSuccess } from './core/isSuccess';
export { OpenAPI } from './core/OpenAPI';
{{#if models}}
{{#each models}}
export { {{{this}}} } from './models/{{{this}}}';
{{/each}}
{{/if}}
{{#if models}}
{{#each models}}
export { ${{{this}}} } from './schemas/${{{this}}}';
{{/each}}
{{/if}}
{{#if services}}
{{#each services}}
export { {{{this}}} } from './services/{{{this}}}';
{{/each}}
{{/if}}

View File

@ -1,12 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
export { ApiError } from './core/ApiError';
export { getSchema } from './core/getSchema';
export { isSuccess } from './core/isSuccess';
export { OpenAPI } from './core/OpenAPI';
export { FieldSchema, Schema, ObjectSchema } from './core/Schema';
export * from './models/';
export * from './services/';

View File

@ -14,5 +14,5 @@ import { {{{this}}} } from './{{{this}}}';
{{else equals export 'enum'}}
{{>exportEnum}}
{{else}}
{{>exportGeneric}}
{{>exportType}}
{{/equals}}

View File

@ -1,8 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
{{#each models}}
export { {{{this}}} } from './{{{this}}}';
{{/each}}

View File

@ -2,13 +2,11 @@
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
{{#if extends}}
{{#each extends}}
import { ${{{this}}} } from './${{{this}}}';
{{/each}}
{{/if}}
import { Schema as __Schema } from '../core/Schema';
import { {{{name}}} } from '../models/{{{name}}}';
export const ${{{name}}}: __Schema<{{{name}}}> = {{>schema}};
export const ${{{name}}} = {{>schema}};

View File

@ -1,8 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
export const $Dictionary = {
type: 'Dictionary'
};

View File

@ -1,8 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
{{#each schemas}}
export { ${{{this}}} as {{{this}}} } from './${{{this}}}';
{{/each}}

View File

@ -1,8 +0,0 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
{{#each services}}
export { {{{this}}} } from './{{{this}}}';
{{/each}}

View File

@ -1,6 +1,5 @@
import * as fs from 'fs';
import * as glob from 'glob';
import { Language } from '../index';
import { readHandlebarsTemplates } from './readHandlebarsTemplates';
jest.mock('fs');
@ -19,15 +18,13 @@ describe('readHandlebarsTemplates', () => {
const template = readHandlebarsTemplates();
expect(template).toBeDefined();
expect(template.index).toBeDefined();
expect(template.model).toBeDefined();
expect(template.models).toBeDefined();
expect(template.service).toBeDefined();
expect(template.services).toBeDefined();
expect(template.settings).toBeDefined();
expect(template.index({ message: 'Hello World!' })).toEqual('Hello World!');
expect(template.model({ message: 'Hello World!' })).toEqual('Hello World!');
expect(template.models({ message: 'Hello World!' })).toEqual('Hello World!');
expect(template.service({ message: 'Hello World!' })).toEqual('Hello World!');
expect(template.services({ message: 'Hello World!' })).toEqual('Hello World!');
expect(template.settings({ message: 'Hello World!' })).toEqual('Hello World!');
});
});

View File

@ -5,11 +5,9 @@ import { readHandlebarsTemplate } from './readHandlebarsTemplate';
import { registerHandlebarHelpers } from './registerHandlebarHelpers';
export interface Templates {
models: Handlebars.TemplateDelegate;
index: Handlebars.TemplateDelegate;
model: Handlebars.TemplateDelegate;
schemas: Handlebars.TemplateDelegate;
schema: Handlebars.TemplateDelegate;
services: Handlebars.TemplateDelegate;
service: Handlebars.TemplateDelegate;
settings: Handlebars.TemplateDelegate;
}
@ -23,12 +21,10 @@ export function readHandlebarsTemplates(): Templates {
registerHandlebarHelpers();
const templates: Templates = {
models: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/models/index.hbs`)),
model: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/models/model.hbs`)),
schemas: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/schemas/index.hbs`)),
schema: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/schemas/schema.hbs`)),
services: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/services/index.hbs`)),
service: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/services/service.hbs`)),
index: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/index.hbs`)),
model: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/model.hbs`)),
schema: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/schema.hbs`)),
service: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/service.hbs`)),
settings: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/core/OpenAPI.hbs`)),
};

View File

@ -27,12 +27,10 @@ describe('writeClient', () => {
};
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};

View File

@ -6,6 +6,7 @@ import * as rimraf from 'rimraf';
import { Client } from '../client/interfaces/Client';
import { HttpClient } from '../index';
import { Templates } from './readHandlebarsTemplates';
import { writeClientIndex } from './writeClientIndex';
import { writeClientModels } from './writeClientModels';
import { writeClientSchemas } from './writeClientSchemas';
import { writeClientServices } from './writeClientServices';
@ -53,8 +54,9 @@ export function writeClient(client: Client, httpClient: HttpClient, templates: T
});
// Write the client files
writeClientSettings(client, httpClient, templates, outputPathCore);
writeClientModels(client.models, templates, outputPathModels);
writeClientSchemas(client.models, templates, outputPathSchemas);
writeClientServices(client.services, templates, outputPathServices);
writeClientSettings(client, httpClient, templates, outputPathCore);
writeClientIndex(client, templates, outputPath);
}

View File

@ -0,0 +1,36 @@
import * as fs from 'fs';
import * as glob from 'glob';
import { Client } from '../client/interfaces/Client';
import { Templates } from './readHandlebarsTemplates';
import { writeClientIndex } from './writeClientIndex';
jest.mock('fs');
jest.mock('glob');
const fsWriteFileSync = fs.writeFileSync as jest.MockedFunction<typeof fs.writeFileSync>;
const globSync = glob.sync as jest.MockedFunction<typeof glob.sync>;
describe('writeClientIndex', () => {
it('should write to filesystem', () => {
const client: Client = {
server: 'http://localhost:8080',
version: '1.0',
models: [],
services: [],
};
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
schema: () => 'dummy',
service: () => 'dummy',
settings: () => 'dummy',
};
globSync.mockReturnValue([]);
writeClientIndex(client, templates, '/');
expect(fsWriteFileSync).toBeCalledWith('/index.ts', 'dummy');
});
});

View File

@ -0,0 +1,30 @@
import * as fs from 'fs';
import * as path from 'path';
import { Client } from '../client/interfaces/Client';
import { Templates } from './readHandlebarsTemplates';
import { getModelNames } from './getModelNames';
import { getServiceNames } from './getServiceNames';
/**
* Generate the OpenAPI client index file using the Handlebar template and write it to disk.
* The index file just contains all the exports you need to use the client as a standalone
* library. But yuo can also import individual models and services directly.
* @param client Client object, containing, models, schemas and services.
* @param templates The loaded handlebar templates.
* @param outputPath
*/
export function writeClientIndex(client: Client, templates: Templates, outputPath: string): void {
try {
fs.writeFileSync(
path.resolve(outputPath, 'index.ts'),
templates.index({
server: client.server,
version: client.version,
models: getModelNames(client.models),
services: getServiceNames(client.services),
})
);
} catch (e) {
throw new Error(`Could not write index`);
}
}

View File

@ -30,12 +30,10 @@ describe('writeClientModels', () => {
});
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};

View File

@ -4,7 +4,6 @@ import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { exportModel } from './exportModel';
import { format } from './format';
import { getModelNames } from './getModelNames';
/**
* Generate Models using the Handlebar template and write to disk.
@ -19,10 +18,4 @@ export function writeClientModels(models: Model[], templates: Templates, outputP
const templateResult = templates.model(templateData);
fs.writeFileSync(file, format(templateResult));
});
const file = path.resolve(outputPath, 'index.ts');
const templateResult = templates.models({
models: getModelNames(models),
});
fs.writeFileSync(file, format(templateResult));
}

View File

@ -30,12 +30,10 @@ describe('writeClientModels', () => {
});
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};

View File

@ -4,7 +4,6 @@ import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { exportModel } from './exportModel';
import { format } from './format';
import { getModelNames } from './getModelNames';
/**
* Generate Schemas using the Handlebar template and write to disk.
@ -19,10 +18,4 @@ export function writeClientSchemas(models: Model[], templates: Templates, output
const templateResult = templates.schema(templateData);
fs.writeFileSync(file, format(templateResult));
});
const file = path.resolve(outputPath, 'index.ts');
const templateResult = templates.schemas({
schemas: getModelNames(models),
});
fs.writeFileSync(file, format(templateResult));
}

View File

@ -1,5 +1,4 @@
import * as fs from 'fs';
import { Language } from '../index';
import { Service } from '../client/interfaces/Service';
import { Templates } from './readHandlebarsTemplates';
import { writeClientServices } from './writeClientServices';
@ -18,12 +17,10 @@ describe('writeClientServices', () => {
});
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};

View File

@ -4,7 +4,6 @@ import { Service } from '../client/interfaces/Service';
import { Templates } from './readHandlebarsTemplates';
import { exportService } from './exportService';
import { format } from './format';
import { getServiceNames } from './getServiceNames';
/**
* Generate Services using the Handlebar template and write to disk.
@ -19,10 +18,4 @@ export function writeClientServices(services: Service[], templates: Templates, o
const templateResult = templates.service(templateData);
fs.writeFileSync(file, format(templateResult));
});
const file = path.resolve(outputPath, 'index.ts');
const templateResult = templates.services({
services: getServiceNames(services),
});
fs.writeFileSync(file, format(templateResult));
}

File diff suppressed because it is too large Load Diff