- Removed javascript generation

This commit is contained in:
Ferdi Koomen 2019-12-20 00:11:56 +01:00
parent e0b68a3326
commit b110b22921
90 changed files with 1963 additions and 6105 deletions

View File

@ -5,12 +5,12 @@
[![Build Status](https://badgen.net/travis/ferdikoomen/openapi-typescript-codegen/master)](https://travis-ci.org/ferdikoomen/openapi-typescript-codegen)
[![Quality](https://badgen.net/lgtm/grade/javascript/g/ferdikoomen/openapi-typescript-codegen)](https://lgtm.com/projects/g/ferdikoomen/openapi-typescript-codegen)
> NodeJS library that generates Typescript or Javascript clients based on the OpenAPI specification.
> NodeJS library that generates Typescript clients based on the OpenAPI specification.
#### Why?
- Frontend ❤️ OpenAPI, but we do not want to use JAVA codegen in our builds.
- Quick, lightweight, robust and framework agnostic.
- Supports generation of Typescript and Javascript clients.
- Supports generation of Typescript clients.
- Supports generations of fetch and XHR http clients.
- Supports OpenAPI specification v2.0 and v3.0.
- Supports JSON and YAML files for input.
@ -20,7 +20,7 @@
- If you use enums inside your models / definitions then those enums are now
inside a namespace with the same name as your model. This is called declaration
merging. However Babel 7 now support compiling of Typescript and right now they
do not support namespaces.
do not support namespaces.
## Installation
@ -41,7 +41,7 @@ npm install openapi-typescript-codegen --save-dev
}
```
Command line
Command line
```
npm install openapi-typescript-codegen -g
@ -54,10 +54,8 @@ NodeJS API:
```
const OpenAPI = require('openapi-typescript-codegen');
const result = OpenAPI.generate(
OpenAPI.generate(
'./api/openapi.json',
'./dist'
);
console.log(result);
```

View File

@ -10,7 +10,6 @@ program
.version(pkg.version)
.option('--input [value]', 'Path to swagger specification', './spec.json')
.option('--output [value]', 'Output directory', './generated')
.option('--language [value]', 'Language to generate [typescript, javascript]', 'typescript')
.option('--http-client [value]', 'HTTP client to generate [fetch, xhr]', 'fetch')
.parse(process.argv);
@ -20,7 +19,6 @@ if (SwaggerCodegen) {
SwaggerCodegen.generate(
program.input,
program.output,
program.language,
program.httpClient
);
}

View File

@ -43,10 +43,8 @@
"dist/index.d.ts",
"dist/**/*.js",
"dist/**/*.d.ts",
"src/templates/javascript/**/*.hbs",
"src/templates/javascript/**/*.js",
"src/templates/typescript/**/*.hbs",
"src/templates/typescript/**/*.ts"
"src/templates/**/*.hbs",
"src/templates/**/*.ts"
],
"scripts": {
"clean": "rimraf \"./dist\" \"./coverage\" \"./test/result\"",

View File

@ -7,11 +7,6 @@ import { parse as parseV3 } from './openApi/v3';
import { readHandlebarsTemplates } from './utils/readHandlebarsTemplates';
import { writeClient } from './utils/writeClient';
export enum Language {
TYPESCRIPT = 'typescript',
JAVASCRIPT = 'javascript',
}
export enum HttpClient {
FETCH = 'fetch',
XHR = 'xhr',
@ -23,10 +18,9 @@ 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 language: The language that should be generated (Typescript or Javascript).
* @param httpClient: The selected httpClient (fetch or XHR).
*/
export function generate(input: string, output: string, language: Language = Language.TYPESCRIPT, httpClient: HttpClient = HttpClient.FETCH): void {
export function generate(input: string, output: string, httpClient: HttpClient = HttpClient.FETCH): void {
const inputPath = path.resolve(process.cwd(), input);
const outputPath = path.resolve(process.cwd(), output);
@ -35,22 +29,18 @@ export function generate(input: string, output: string, language: Language = Lan
// handlebar templates for the given language
const openApi = getOpenApiSpec(inputPath);
const openApiVersion = getOpenApiVersion(openApi);
const templates = readHandlebarsTemplates(language);
const templates = readHandlebarsTemplates();
switch (language) {
case Language.JAVASCRIPT:
case Language.TYPESCRIPT:
// Generate and write version 2 client
if (openApiVersion === OpenApiVersion.V2) {
const clientV2 = parseV2(openApi);
writeClient(clientV2, language, httpClient, templates, outputPath);
}
switch (openApiVersion) {
case OpenApiVersion.V2:
const clientV2 = parseV2(openApi);
writeClient(clientV2, httpClient, templates, outputPath);
break;
// Generate and write version 3 client
if (openApiVersion === OpenApiVersion.V3) {
const clientV3 = parseV3(openApi);
writeClient(clientV3, language, httpClient, templates, outputPath);
}
case OpenApiVersion.V3:
const clientV3 = parseV3(openApi);
writeClient(clientV3, httpClient, templates, outputPath);
break;
}
} catch (e) {
console.error(e);

View File

@ -0,0 +1,17 @@
/* 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>(model: K) {
if (schemas.hasOwnProperty(model)) {
return schemas[model];
}
return null;
}

11
src/templates/index.ts Normal file
View File

@ -0,0 +1,11 @@
/* 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 * from './models/';
export * from './services/';

View File

@ -1,54 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
import { isSuccess } from "./isSuccess";
export class ApiError extends Error {
constructor(result, message) {
super(message);
this.url = result.url;
this.status = result.status;
this.statusText = result.statusText;
this.body = result.body;
}
}
(function (ApiError) {
let Message;
(function (Message) {
Message.BAD_REQUEST = 'Bad Request';
Message.UNAUTHORIZED = 'Unauthorized';
Message.FORBIDDEN = 'Forbidden';
Message.NOT_FOUND = 'Not Found';
Message.INTERNAL_SERVER_ERROR = 'Internal Server Error';
Message.BAD_GATEWAY = 'Bad Gateway';
Message.SERVICE_UNAVAILABLE = 'Service Unavailable';
Message.GENERIC_ERROR = 'Generic Error';
})(Message = ApiError.Message || (ApiError.Message = {}));
})(ApiError || (ApiError = {}));
/**
* Catch common errors (based on status code).
* @param result
*/
export function catchGenericError(result) {
switch (result.status) {
case 400: throw new ApiError(result, ApiError.Message.BAD_REQUEST);
case 401: throw new ApiError(result, ApiError.Message.UNAUTHORIZED);
case 403: throw new ApiError(result, ApiError.Message.FORBIDDEN);
case 404: throw new ApiError(result, ApiError.Message.NOT_FOUND);
case 500: throw new ApiError(result, ApiError.Message.INTERNAL_SERVER_ERROR);
case 502: throw new ApiError(result, ApiError.Message.BAD_GATEWAY);
case 503: throw new ApiError(result, ApiError.Message.SERVICE_UNAVAILABLE);
}
if (!isSuccess(result.status)) {
throw new ApiError(result, ApiError.Message.GENERIC_ERROR);
}
}

View File

@ -1,13 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
export let OpenAPI;
(function (OpenAPI) {
OpenAPI.BASE = '{{{server}}}';
OpenAPI.VERSION = '{{{version}}}';
OpenAPI.CLIENT = '{{{httpClient}}}';
OpenAPI.TOKEN = '';
})(OpenAPI || (OpenAPI = {}));

View File

@ -1,23 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
/**
* Get FormData from object. This method is needed to upload
* multipart form data to the REST API.
* @param params Key value based object.
*/
export function getFormData(params) {
const formData = new FormData();
for (const key in params) {
if (typeof params[key] !== 'undefined') {
const value = params[key];
if (value !== undefined && value !== null) {
formData.append(key, value);
}
}
}
return formData;
}

View File

@ -1,32 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
/**
* Get query string from query parameters object. This method also
* supports multi-value items by creating a key for each item.
* @param params Key value based object.
*/
export function getQueryString(params) {
const qs = [];
for (const key in params) {
if (typeof params[key] !== 'undefined') {
const value = params[key];
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
value.forEach(value => {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
});
} else {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
}
}
}
if (qs.length > 0) {
return `?${qs.join('&')}`;
}
return '';
}

View File

@ -1,13 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
/**
* Check success response code.
* @param status Status code
*/
export function isSuccess(status) {
return status >= 200 && status < 300;
}

View File

@ -1,80 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
import { getFormData } from './getFormData';
import { getQueryString } from './getQueryString';
import { OpenAPI } from './OpenAPI';
import { requestUsingFetch } from './requestUsingFetch';
import { requestUsingXHR } from './requestUsingXHR';
/**
* Create the request.
* @param options Request method options.
* @returns Result object (see above)
*/
export async function request(options) {
// Create the request URL
let url = `${OpenAPI.BASE}${options.path}`;
// Create request headers
const headers = new Headers({
...options.headers,
Accept: 'application/json',
});
// Create request settings
const request = {
headers,
method: options.method,
credentials: 'same-origin',
};
// If we have a bearer token then we set the authentication header.
if (OpenAPI.TOKEN !== null && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
}
// Add the query parameters (if defined).
if (options.query) {
url += getQueryString(options.query);
}
// Append formData as body
if (options.formData) {
request.body = getFormData(options.formData);
} else if (options.body) {
// If this is blob data, then pass it directly to the body and set content type.
// Otherwise we just convert request data to JSON string (needed for fetch api)
if (options.body instanceof Blob) {
request.body = options.body;
if (options.body.type) {
headers.append('Content-Type', options.body.type);
}
} else {
request.body = JSON.stringify(options.body);
headers.append('Content-Type', 'application/json');
}
}
try {
switch (OpenAPI.CLIENT) {
case 'xhr':
return await requestUsingXHR(url, request);
default:
return await requestUsingFetch(url, request);
}
} catch (error) {
return {
url,
ok: false,
status: 0,
statusText: '',
body: error
};
}
}

View File

@ -1,49 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
/**
* Request content using the new Fetch API. This is the default API that is used and
* is create for all JSON, XML and text objects. However it is limited to UTF-8.
* This is a problem for some of the Docs content, since that requires UTF-16!
* @param url The url to request.
* @param request The request object, containing method, headers, body, etc.
*/
export async function requestUsingFetch(url, request) {
// Fetch response using fetch API.
const response = await fetch(url, request);
// Create result object.
const result = {
url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
body: null,
};
// Try to parse the content for any response status code.
// We check the "Content-Type" header to see if we need to parse the
// content as json or as plain text.
const contentType = response.headers.get('Content-Type');
if (contentType) {
switch (contentType.toLowerCase()) {
case 'application/json':
case 'application/json; charset=utf-8':
result.body = await response.json();
break;
case 'text/plain':
case 'text/xml':
case 'text/xml; charset=utf-8':
case 'text/xml; charset=utf-16':
result.body = await response.text();
break;
}
}
return result;
}

View File

@ -1,72 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
import { isSuccess } from './isSuccess';
/**
* Request content using the new legacy XMLHttpRequest API. This method is useful
* when we want to request UTF-16 content, since it natively supports loading UTF-16.
* We could do the same with the Fetch API, but then we will need to convert the
* content using JavaScript... And that is very very slow.
* @param url The url to request.
* @param request The request object, containing method, headers, body, etc.
*/
export async function requestUsingXHR(url, request) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest();
// Open the request, remember to do this before adding any headers,
// because the request needs to be initialized!
xhr.open(request.method, url, true);
// Add the headers (required when dealing with JSON)
const headers = request.headers as Headers;
headers.forEach((value, key) => {
xhr.setRequestHeader(key, value);
});
// Register the readystate handler, this will fire when the request is done.
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
// Create result object.
const result = {
url,
ok: isSuccess(xhr.status),
status: xhr.status,
statusText: xhr.statusText,
body: null,
};
// Try to parse the content for any response status code.
// We check the "Content-Type" header to see if we need to parse the
// content as json or as plain text.
const contentType = xhr.getResponseHeader('Content-Type');
if (contentType) {
switch (contentType.toLowerCase()) {
case 'application/json':
case 'application/json; charset=utf-8':
result.body = JSON.parse(xhr.responseText);
break;
case 'text/plain':
case 'text/xml':
case 'text/xml; charset=utf-8':
case 'text/xml; charset=utf-16':
result.body = xhr.responseText;
break;
}
}
// Done!
resolve(result);
}
};
// Start the request!
xhr.send(request.body);
});
}

View File

@ -1,40 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
export { ApiError } from './core/ApiError';
export { isSuccess } from './core/isSuccess';
export { OpenAPI } from './core/OpenAPI';
{{#if models}}
{{#each models}}
import { {{{this}}} } from './models/{{{this}}}';
{{/each}}
{{#each models}}
export { {{{this}}} };
{{/each}}
{{/if}}
{{#if services}}
{{#each services}}
export { {{{this}}} } from './services/{{{this}}}';
{{/each}}
{{/if}}
{{#if models}}
const definitions = {
{{#each models}}
'{{{this}}}': {{{this}}}.definition,
{{/each}}
};
export function getDefinition(definition) {
if (definitions.hasOwnProperty(definition)) {
return definitions[definition];
}
return null;
}
{{/if}}

View File

@ -1,13 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
{{#equals export 'interface'}}
{{>exportInterface}}
{{else equals export 'enum'}}
{{>exportEnum}}
{{else}}
{{>exportGeneric}}
{{/equals}}

View File

@ -1,14 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
export let Dictionary;
(function (Dictionary) {
Dictionary.definition = {
type: 'Dictionary'
};
})(Dictionary || (Dictionary = {}));

View File

@ -1,23 +0,0 @@
{
properties: {
{{#if extends}}
{{#each extends}}
...{{{this}}}.definition.properties,
{{/each}}
{{/if}}
{{#if properties}}
{{#each properties}}
{{{name}}}: {{>definition}},
{{/each}}
{{/if}}
},
{{#if isReadOnly~}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,17 +0,0 @@
{{#if description}}
/**
* {{{description}}}
*/
{{/if}}
export let {{{name}}};
(function ({{{name}}}) {
{{#if enum}}
{{#each enum}}
{{{../name}}}.{{{name}}} = {{{value}}};
{{/each}}
{{/if}}
{{{name}}}.definition = {{>definition}};
})({{{name}}} || ({{{name}}} = {}));

View File

@ -1,11 +0,0 @@
{{#if description}}
/**
* {{{description}}}
*/
{{/if}}
export let {{{name}}};
(function ({{{name}}}) {
{{{name}}}.definition = {{>definition}};
})({{{name}}} || ({{{name}}} = {}));

View File

@ -1,26 +0,0 @@
{{#if description}}
/**
* {{{description}}}
*/
{{/if}}
export let {{{name}}};
(function ({{{name}}}) {
{{#if enums}}
{{#each enums}}
{{#if description}}
/**
* {{{description}}}
*/
{{/if}}
{{{../name}}}.{{{name}}} = {
{{#each enum}}
{{{name}}}: {{{value}}},
{{/each}}
};
{{/each}}
{{/if}}
{{{name}}}.definition = {{>definition}};
})({{{name}}} || ({{{name}}} = {}));

View File

@ -1,92 +0,0 @@
'use strict';
/* istanbul ignore file */
/* eslint-disable */
/* prettier-ignore */
import { ApiError, catchGenericError } from '../core/ApiError';
import { request as $request } from '../core/request';
import { OpenAPI } from '../core/OpenAPI';
export class {{{name}}} {
{{#each operations}}
/**
{{#if deprecated}}
* @deprecated
{{/if}}
{{#if summary}}
* {{{summary}}}
{{/if}}
{{#if description}}
* {{{description}}}
{{/if}}
{{#if parameters}}
{{#each parameters}}
* @param {{{name}}} {{{description}}}
{{/each}}
{{/if}}
{{#each results}}
* @result {{{type}}} {{{description}}}
{{/each}}
* @throws ApiError
*/
static async {{{name}}}({{#if parameters}}
{{#each parameters}}
{{{name}}}{{#if default}} = {{{default}}}{{/if}},
{{/each}}
{{/if}}) {
const result = await $request({
method: '{{{method}}}',
path: `{{{path}}}`,
{{#if parametersCookie~}}
cookies: {
{{#each parametersCookie}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersHeader~}}
headers: {
{{#each parametersHeader}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersQuery~}}
query: {
{{#each parametersQuery}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersForm~}}
formData: {
{{#each parametersForm}}
'{{{prop}}}': {{{name}}},
{{/each}}
},
{{/if}}
{{#if parametersBody~}}
body: {{{parametersBody.name}}},
{{/if}}
});
{{#if errors}}
if (!result.ok) {
switch (result.status) {
{{#each errors}}
case {{{code}}}: throw new ApiError(result, `{{{description}}}`);
{{/each}}
}
}
{{/if}}
catchGenericError(result);
return result.body;
}
{{/each}}
}

View File

@ -6,11 +6,3 @@
export type Dictionary<T> = {
[key: string]: T;
}
export namespace Dictionary {
export const definition = {
type: 'Dictionary'
};
}

View File

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

View File

@ -5,7 +5,7 @@
{{#if imports}}
{{#each imports}}
import { {{{this}}} } from '../models/{{{this}}}';
import { {{{this}}} } from './{{{this}}}';
{{/each}}
{{/if}}

View File

@ -8,9 +8,3 @@ export enum {{{name}}} {
{{{name}}} = {{{value}}},
{{/each}}
}
export namespace {{{name}}} {
export const definition = {{>definition}};
}

View File

@ -4,9 +4,3 @@
*/
{{/if}}
export type {{{name}}} = {{>type}};
export namespace {{{name}}} {
export const definition = {{>definition}};
}

View File

@ -13,10 +13,10 @@ export interface {{{name}}}{{>extends}} {
{{>isReadOnly}}{{{name}}}{{>isRequired}}: {{>type parent=../name}};
{{/each}}
}
{{#if enums}}
export namespace {{{name}}} {
{{#if enums}}
{{#each enums}}
{{#if description}}
/**
@ -30,7 +30,6 @@ export namespace {{{name}}} {
}
{{/each}}
{{/if}}
export const definition = {{>definition}};
}
{{/if}}

View File

@ -1,11 +1,11 @@
{{#equals export 'interface'}}
{{>definitionInterface}}
{{>schemaInterface}}
{{else equals export 'enum'}}
{{>definitionEnum}}
{{>schemaEnum}}
{{else equals export 'array'}}
{{>definitionArray}}
{{>schemaArray}}
{{else equals export 'dictionary'}}
{{>definitionDictionary}}
{{>schemaDictionary}}
{{else}}
{{>definitionGeneric}}
{{>schemaGeneric}}
{{/equals}}

View File

@ -2,12 +2,12 @@
properties: {
{{#if extends}}
{{#each extends}}
...{{{this}}}.definition.properties,
...${{{this}}}.properties,
{{/each}}
{{/if}}
{{#if properties}}
{{#each properties}}
{{{name}}}: {{>definition}},
{{{name}}}: {{>schema}},
{{/each}}
{{/if}}
},

View File

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

View File

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

View File

@ -0,0 +1,12 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/* prettier-ignore */
{{#if extends}}
{{#each extends}}
import { ${{{this}}} } from './${{{this}}}';
{{/each}}
{{/if}}
export const ${{{name}}} = {{>schema}};

View File

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

View File

@ -1,42 +0,0 @@
/* 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}}
import { {{{this}}} } from './models/{{{this}}}';
{{/each}}
{{/if}}
{{#if models}}
{{#each models}}
export { {{{this}}} };
{{/each}}
{{/if}}
{{#if services}}
{{#each services}}
export { {{{this}}} } from './services/{{{this}}}';
{{/each}}
{{/if}}
{{#if models}}
const definitions = {
{{#each models}}
'{{{this}}}': {{{this}}}.definition,
{{/each}}
};
export function getDefinition<K extends keyof typeof definitions>(definition: K) {
if (definitions.hasOwnProperty(definition)) {
return definitions[definition];
}
return null;
}
{{/if}}

View File

@ -1,11 +0,0 @@
{{#equals export 'interface'}}
{{>definitionInterface}}
{{else equals export 'enum'}}
{{>definitionEnum}}
{{else equals export 'array'}}
{{>definitionArray}}
{{else equals export 'dictionary'}}
{{>definitionDictionary}}
{{else}}
{{>definitionGeneric}}
{{/equals}}

View File

@ -1,12 +0,0 @@
{
type: 'Array',
{{#if isReadOnly~}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,12 +0,0 @@
{
type: 'Dictionary',
{{#if isReadOnly~}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,12 +0,0 @@
{
type: 'Enum',
{{#if isReadOnly~}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
isNullable: {{{isNullable}}},
{{/if}}
}

View File

@ -1,56 +0,0 @@
{
{{#if type~}}
type: '{{{base}}}',
{{/if}}
{{#if isReadOnly~}}
isReadOnly: {{{isReadOnly}}},
{{/if}}
{{#if isRequired~}}
isRequired: {{{isRequired}}},
{{/if}}
{{#if isNullable~}}
isNullable: {{{isNullable}}},
{{/if}}
{{#if format~}}
format: '{{{format}}}',
{{/if}}
{{#if maximum~}}
maximum: {{{maximum}}},
{{/if}}
{{#if exclusiveMaximum~}}
exclusiveMaximum: {{{exclusiveMaximum}}},
{{/if}}
{{#if minimum~}}
minimum: {{{minimum}}},
{{/if}}
{{#if exclusiveMinimum~}}
exclusiveMinimum: {{{exclusiveMinimum}}},
{{/if}}
{{#if multipleOf~}}
multipleOf: {{{multipleOf}}},
{{/if}}
{{#if maxLength~}}
maxLength: {{{maxLength}}},
{{/if}}
{{#if minLength~}}
minLength: {{{minLength}}},
{{/if}}
{{#if pattern~}}
pattern: '{{{pattern}}}',
{{/if}}
{{#if maxItems~}}
maxItems: {{{maxItems}}},
{{/if}}
{{#if minItems~}}
minItems: {{{minItems}}},
{{/if}}
{{#if uniqueItems~}}
uniqueItems: {{{uniqueItems}}},
{{/if}}
{{#if maxProperties~}}
maxProperties: {{{maxProperties}}},
{{/if}}
{{#if minProperties~}}
minProperties: {{{minProperties}}},
{{/if}}
}

View File

@ -1,9 +0,0 @@
import { Language } from '../index';
import { getFileName } from './getFileName';
describe('getFileName', () => {
it('should convert to correct file name', () => {
expect(getFileName('file', Language.TYPESCRIPT)).toEqual('file.ts');
expect(getFileName('file', Language.JAVASCRIPT)).toEqual('file.js');
});
});

View File

@ -1,16 +0,0 @@
import { Language } from '../index';
/**
* Get the correct file name and extension for a given language.
* @param fileName Any file name.
* @param language Typescript or Javascript.
*/
export function getFileName(fileName: string, language: Language): string {
switch (language) {
case Language.TYPESCRIPT:
return `${fileName}.ts`;
case Language.JAVASCRIPT:
return `${fileName}.js`;
}
return fileName;
}

View File

@ -59,7 +59,7 @@ describe('getModelNames', () => {
properties: [],
});
expect(getModelNames([])).toEqual(['Dictionary']);
expect(getModelNames(models)).toEqual(['Dictionary', 'Doe', 'Jane', 'John']);
expect(getModelNames([])).toEqual([]);
expect(getModelNames(models)).toEqual(['Doe', 'Jane', 'John']);
});
});

View File

@ -3,7 +3,6 @@ import { Model } from '../client/interfaces/Model';
export function getModelNames(models: Model[]): string[] {
return models
.map(model => model.name)
.concat('Dictionary')
.sort((a, b) => {
const nameA = a.toLowerCase();
const nameB = b.toLowerCase();

View File

@ -16,16 +16,18 @@ describe('readHandlebarsTemplates', () => {
fsReadFileSync.mockReturnValue('{{{message}}}');
globSync.mockReturnValue([]);
const template = readHandlebarsTemplates(Language.TYPESCRIPT);
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

@ -1,13 +1,15 @@
import * as Handlebars from 'handlebars';
import * as glob from 'glob';
import * as path from 'path';
import { Language } from '../index';
import { readHandlebarsTemplate } from './readHandlebarsTemplate';
import { registerHandlebarHelpers } from './registerHandlebarHelpers';
export interface Templates {
index: Handlebars.TemplateDelegate;
models: Handlebars.TemplateDelegate;
model: Handlebars.TemplateDelegate;
schemas: Handlebars.TemplateDelegate;
schema: Handlebars.TemplateDelegate;
services: Handlebars.TemplateDelegate;
service: Handlebars.TemplateDelegate;
settings: Handlebars.TemplateDelegate;
}
@ -15,20 +17,22 @@ export interface Templates {
/**
* Read all the Handlebar templates that we need and return on wrapper object
* so we can easily access the templates in out generator / write functions.
* @param language The language we need to generate (Typescript or Javascript).
*/
export function readHandlebarsTemplates(language: Language): Templates {
export function readHandlebarsTemplates(): Templates {
try {
registerHandlebarHelpers();
const templates: Templates = {
index: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/index.hbs`)),
model: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/model.hbs`)),
service: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/service.hbs`)),
settings: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/${language}/core/OpenAPI.hbs`)),
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`)),
settings: readHandlebarsTemplate(path.resolve(__dirname, `../../src/templates/core/OpenAPI.hbs`)),
};
const partials = path.resolve(__dirname, `../../src/templates/${language}/partials`);
const partials = path.resolve(__dirname, `../../src/templates//partials`);
const partialsFiles = glob.sync('*.hbs', { cwd: partials });
partialsFiles.forEach(partial => {
Handlebars.registerPartial(path.basename(partial, '.hbs'), readHandlebarsTemplate(path.resolve(partials, partial)));

View File

@ -3,7 +3,7 @@ import * as glob from 'glob';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';
import { Client } from '../client/interfaces/Client';
import { HttpClient, Language } from '../index';
import { HttpClient } from '../index';
import { Templates } from './readHandlebarsTemplates';
import { writeClient } from './writeClient';
@ -27,15 +27,18 @@ describe('writeClient', () => {
};
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};
globSync.mockReturnValue([]);
writeClient(client, Language.TYPESCRIPT, HttpClient.FETCH, templates, '/');
writeClient(client, HttpClient.FETCH, templates, '/');
expect(rimrafSync).toBeCalled();
expect(mkdirpSync).toBeCalled();

View File

@ -4,24 +4,24 @@ import * as mkdirp from 'mkdirp';
import * as path from 'path';
import * as rimraf from 'rimraf';
import { Client } from '../client/interfaces/Client';
import { HttpClient, Language } from '../index';
import { HttpClient } from '../index';
import { Templates } from './readHandlebarsTemplates';
import { writeClientIndex } from './writeClientIndex';
import { writeClientModels } from './writeClientModels';
import { writeClientSchemas } from './writeClientSchemas';
import { writeClientServices } from './writeClientServices';
import { writeClientSettings } from './writeClientSettings';
/**
* Write our OpenAPI client, using the given templates at the given output path.
* @param client Client object with all the models, services, etc.
* @param language The language that should be generated (Typescript or Javascript).
* @param httpClient The selected httpClient (fetch or XHR).
* @param templates Templates wrapper with all loaded Handlebars templates.
* @param outputPath
*/
export function writeClient(client: Client, language: Language, httpClient: HttpClient, templates: Templates, outputPath: string): void {
export function writeClient(client: Client, httpClient: HttpClient, templates: Templates, outputPath: string): void {
const outputPathCore = path.resolve(outputPath, 'core');
const outputPathModels = path.resolve(outputPath, 'models');
const outputPathSchemas = path.resolve(outputPath, 'schemas');
const outputPathServices = path.resolve(outputPath, 'services');
// Clean output directory
@ -36,14 +36,15 @@ export function writeClient(client: Client, language: Language, httpClient: Http
mkdirp.sync(outputPath);
mkdirp.sync(outputPathCore);
mkdirp.sync(outputPathModels);
mkdirp.sync(outputPathSchemas);
mkdirp.sync(outputPathServices);
} catch (e) {
throw new Error(`Could not create output directories`);
}
// Copy all support files
const supportFiles = path.resolve(__dirname, `../../src/templates/${language}/`);
const supportFilesList = glob.sync('**/*.{ts,js}', { cwd: supportFiles });
const supportFiles = path.resolve(__dirname, `../../src/templates/`);
const supportFilesList = glob.sync('**/*.ts', { cwd: supportFiles });
supportFilesList.forEach(file => {
fs.copyFileSync(
path.resolve(supportFiles, file), // From input path
@ -52,12 +53,8 @@ export function writeClient(client: Client, language: Language, httpClient: Http
});
// Write the client files
try {
writeClientSettings(client, language, httpClient, templates, outputPathCore);
writeClientModels(client.models, language, templates, outputPathModels);
writeClientServices(client.services, language, templates, outputPathServices);
writeClientIndex(client, language, templates, outputPath);
} catch (e) {
throw e;
}
writeClientSettings(client, httpClient, templates, outputPathCore);
writeClientModels(client.models, templates, outputPathModels);
writeClientSchemas(client.models, templates, outputPathSchemas);
writeClientServices(client.services, templates, outputPathServices);
}

View File

@ -1,36 +0,0 @@
import * as fs from 'fs';
import * as glob from 'glob';
import { Client } from '../client/interfaces/Client';
import { Language } from '../index';
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',
service: () => 'dummy',
settings: () => 'dummy',
};
globSync.mockReturnValue([]);
writeClientIndex(client, Language.TYPESCRIPT, templates, '/');
expect(fsWriteFileSync).toBeCalledWith('/index.ts', 'dummy');
});
});

View File

@ -1,34 +0,0 @@
import * as fs from 'fs';
import * as path from 'path';
import { Client } from '../client/interfaces/Client';
import { Language } from '../index';
import { Templates } from './readHandlebarsTemplates';
import { getFileName } from './getFileName';
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 language The output language (Typescript or javascript).
* @param templates The loaded handlebar templates.
* @param outputPath
*/
export function writeClientIndex(client: Client, language: Language, templates: Templates, outputPath: string): void {
const fileName = getFileName('index', language);
try {
fs.writeFileSync(
path.resolve(outputPath, fileName),
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: "${fileName}"`);
}
}

View File

@ -1,5 +1,4 @@
import * as fs from 'fs';
import { Language } from '../index';
import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { writeClientModels } from './writeClientModels';
@ -31,13 +30,16 @@ describe('writeClientModels', () => {
});
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};
writeClientModels(models, Language.TYPESCRIPT, templates, '/');
writeClientModels(models, templates, '/');
expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy');
});

View File

@ -1,28 +1,28 @@
import * as fs from 'fs';
import * as path from 'path';
import { Language } from '../index';
import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { exportModel } from './exportModel';
import { format } from './format';
import { getFileName } from './getFileName';
import { getModelNames } from './getModelNames';
/**
* Generate Models using the Handlebar template and write to disk.
* @param models Array of Models to write.
* @param language The output language (Typescript or javascript).
* @param templates The loaded handlebar templates.
* @param outputPath
*/
export function writeClientModels(models: Model[], language: Language, templates: Templates, outputPath: string): void {
export function writeClientModels(models: Model[], templates: Templates, outputPath: string): void {
models.forEach(model => {
const fileName = getFileName(model.name, language);
try {
const templateData = exportModel(model);
const templateResult = templates.model(templateData);
fs.writeFileSync(path.resolve(outputPath, fileName), format(templateResult));
} catch (e) {
throw new Error(`Could not write model: "${fileName}"`);
}
const file = path.resolve(outputPath, `${model.name}.ts`);
const templateData = exportModel(model);
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

@ -0,0 +1,46 @@
import * as fs from 'fs';
import { Model } from '../client/interfaces/Model';
import { Templates } from './readHandlebarsTemplates';
import { writeClientModels } from './writeClientModels';
jest.mock('fs');
const fsWriteFileSync = fs.writeFileSync as jest.MockedFunction<typeof fs.writeFileSync>;
describe('writeClientModels', () => {
it('should write to filesystem', () => {
const models: Model[] = [];
models.push({
export: 'interface',
name: 'Item',
type: 'Item',
base: 'Item',
template: null,
link: null,
description: null,
isProperty: false,
isReadOnly: false,
isRequired: false,
isNullable: false,
imports: [],
extends: [],
enum: [],
enums: [],
properties: [],
});
const templates: Templates = {
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};
writeClientModels(models, templates, '/');
expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy');
});
});

View File

@ -0,0 +1,28 @@
import * as fs from 'fs';
import * as path from 'path';
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.
* @param models Array of Models to write.
* @param templates The loaded handlebar templates.
* @param outputPath
*/
export function writeClientSchemas(models: Model[], templates: Templates, outputPath: string): void {
models.forEach(model => {
const file = path.resolve(outputPath, `$${model.name}.ts`);
const templateData = exportModel(model);
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

@ -18,13 +18,16 @@ describe('writeClientServices', () => {
});
const templates: Templates = {
index: () => 'dummy',
model: () => 'dummy',
models: () => 'dummy',
schema: () => 'dummy',
schemas: () => 'dummy',
service: () => 'dummy',
services: () => 'dummy',
settings: () => 'dummy',
};
writeClientServices(services, Language.TYPESCRIPT, templates, '/');
writeClientServices(services, templates, '/');
expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy');
});

View File

@ -1,28 +1,28 @@
import * as fs from 'fs';
import * as path from 'path';
import { Language } from '../index';
import { Service } from '../client/interfaces/Service';
import { Templates } from './readHandlebarsTemplates';
import { exportService } from './exportService';
import { format } from './format';
import { getFileName } from './getFileName';
import { getServiceNames } from './getServiceNames';
/**
* Generate Services using the Handlebar template and write to disk.
* @param services Array of Services to write.
* @param language The output language (Typescript or javascript).
* @param templates The loaded handlebar templates.
* @param outputPath
*/
export function writeClientServices(services: Service[], language: Language, templates: Templates, outputPath: string): void {
export function writeClientServices(services: Service[], templates: Templates, outputPath: string): void {
services.forEach(service => {
const fileName = getFileName(service.name, language);
try {
const templateData = exportService(service);
const templateResult = templates.service(templateData);
fs.writeFileSync(path.resolve(outputPath, fileName), format(templateResult));
} catch (e) {
throw new Error(`Could not write service: "${fileName}"`);
}
const file = path.resolve(outputPath, `${service.name}.ts`);
const templateData = exportService(service);
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));
}

View File

@ -1,23 +1,16 @@
import * as fs from 'fs';
import * as path from 'path';
import { Client } from '../client/interfaces/Client';
import { HttpClient, Language } from '../index';
import { HttpClient } from '../index';
import { Templates } from './readHandlebarsTemplates';
import { getFileName } from './getFileName';
export function writeClientSettings(client: Client, language: Language, httpClient: HttpClient, templates: Templates, outputPath: string): void {
const fileName = getFileName('OpenAPI', language);
try {
fs.writeFileSync(
path.resolve(outputPath, fileName),
templates.settings({
language,
httpClient,
server: client.server,
version: client.version,
})
);
} catch (e) {
throw new Error(`Could not write settings: "${fileName}"`);
}
export function writeClientSettings(client: Client, httpClient: HttpClient, templates: Templates, outputPath: string): void {
fs.writeFileSync(
path.resolve(outputPath, 'OpenAPI.ts'),
templates.settings({
httpClient,
server: client.server,
version: client.version,
})
);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +1,16 @@
const OpenAPI = require('../dist');
OpenAPI.generate(
'./test/mock/spec-v2.json',
'./test/result/v2/typescript/',
OpenAPI.Language.TYPESCRIPT,
'./test/mock/v2/spec.json',
'./test/result/v2/',
OpenAPI.HttpClient.FETCH,
);
OpenAPI.generate(
'./test/mock/spec-v2.json',
'./test/result/v2/javascript/',
OpenAPI.Language.JAVASCRIPT,
OpenAPI.HttpClient.XHR,
);
OpenAPI.generate(
'./test/mock/spec-v3.json',
'./test/result/v3/typescript/',
OpenAPI.Language.TYPESCRIPT,
'./test/mock/v3/spec.json',
'./test/result/v3/',
OpenAPI.HttpClient.FETCH,
);
OpenAPI.generate(
'./test/mock/spec-v3.json',
'./test/result/v3/javascript/',
OpenAPI.Language.JAVASCRIPT,
OpenAPI.HttpClient.XHR,
);
OpenAPI.compile('./test/result/v2/typescript/');
OpenAPI.compile('./test/result/v3/typescript/');
OpenAPI.compile('./test/result/v2/');
OpenAPI.compile('./test/result/v3/');

View File

@ -6,79 +6,35 @@ describe('generation', () => {
describe('v2', () => {
describe('typescript', () => {
OpenAPI.generate(
'./test/mock/v2/spec.json',
'./test/result/v2/',
OpenAPI.HttpClient.FETCH,
);
OpenAPI.generate(
'./test/mock/spec-v2.json',
'./test/result/v2/typescript/',
OpenAPI.Language.TYPESCRIPT,
OpenAPI.HttpClient.FETCH,
);
test.each(glob
.sync('./test/result/v2/typescript/**/*.ts')
.map(file => [file])
)('file(%s)', file => {
const content = fs.readFileSync(file, 'utf8').toString();
expect(content).toMatchSnapshot(file);
});
});
describe('javascript', () => {
OpenAPI.generate(
'./test/mock/spec-v2.json',
'./test/result/v2/javascript/',
OpenAPI.Language.JAVASCRIPT,
OpenAPI.HttpClient.XHR,
);
test.each(glob
.sync('./test/result/v2/javascript/**/*.js')
.map(file => [file])
)('file(%s)', file => {
const content = fs.readFileSync(file, 'utf8').toString();
expect(content).toMatchSnapshot(file);
});
test.each(glob
.sync('./test/result/v2/**/*.ts')
.map(file => [file])
)('file(%s)', file => {
const content = fs.readFileSync(file, 'utf8').toString();
expect(content).toMatchSnapshot(file);
});
});
describe('v3', () => {
describe('typescript', () => {
OpenAPI.generate(
'./test/mock/v3/spec.json',
'./test/result/v3/',
OpenAPI.HttpClient.FETCH,
);
OpenAPI.generate(
'./test/mock/spec-v3.json',
'./test/result/v3/typescript/',
OpenAPI.Language.TYPESCRIPT,
OpenAPI.HttpClient.FETCH,
);
test.each(glob
.sync('./test/result/v3/typescript/**/*.ts')
.map(file => [file])
)('file(%s)', file => {
const content = fs.readFileSync(file, 'utf8').toString();
expect(content).toMatchSnapshot(file);
});
});
describe('javascript', () => {
OpenAPI.generate(
'./test/mock/spec-v3.json',
'./test/result/v3/javascript/',
OpenAPI.Language.JAVASCRIPT,
OpenAPI.HttpClient.XHR,
);
test.each(glob
.sync('./test/result/v3/javascript/**/*.js')
.map(file => [file])
)('file(%s)', file => {
const content = fs.readFileSync(file, 'utf8').toString();
expect(content).toMatchSnapshot(file);
});
test.each(glob
.sync('./test/result/v3/**/*.ts')
.map(file => [file])
)('file(%s)', file => {
const content = fs.readFileSync(file, 'utf8').toString();
expect(content).toMatchSnapshot(file);
});
});
});