- Working on support for formData in v3

This commit is contained in:
Ferdi Koomen 2021-10-28 15:51:39 +02:00
parent fa2bab05c7
commit 433b4f1b21
37 changed files with 408 additions and 113 deletions

View File

@ -3,6 +3,6 @@
"singleQuote": true,
"trailingComma": "es5",
"arrowParens": "avoid",
"printWidth": 200,
"printWidth": 120,
"tabWidth": 4
}

View File

@ -3,7 +3,18 @@ export interface Schema {
isReadOnly: boolean;
isRequired: boolean;
isNullable: boolean;
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
format?:
| 'int32'
| 'int64'
| 'float'
| 'double'
| 'string'
| 'boolean'
| 'byte'
| 'binary'
| 'date'
| 'date-time'
| 'password';
maximum?: number;
exclusiveMaximum?: boolean;
minimum?: number;

View File

@ -66,7 +66,19 @@ export async function generate({
const client = parseV2(openApi);
const clientFinal = postProcessClient(client);
if (!write) break;
await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, request);
await writeClient(
clientFinal,
templates,
output,
httpClient,
useOptions,
useUnionTypes,
exportCore,
exportServices,
exportModels,
exportSchemas,
request
);
break;
}
@ -74,7 +86,19 @@ export async function generate({
const client = parseV3(openApi);
const clientFinal = postProcessClient(client);
if (!write) break;
await writeClient(clientFinal, templates, output, httpClient, useOptions, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas, request);
await writeClient(
clientFinal,
templates,
output,
httpClient,
useOptions,
useUnionTypes,
exportCore,
exportServices,
exportModels,
exportSchemas,
request
);
break;
}
}

View File

@ -7,7 +7,18 @@ import type { OpenApiItems } from './OpenApiItems';
export interface OpenApiHeader {
description?: string;
type: 'string' | 'number' | 'integer' | 'boolean' | 'array';
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
format?:
| 'int32'
| 'int64'
| 'float'
| 'double'
| 'string'
| 'boolean'
| 'byte'
| 'binary'
| 'date'
| 'date-time'
| 'password';
items?: Dictionary<OpenApiItems>;
collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes';
default?: any;

View File

@ -5,7 +5,18 @@ import type { WithEnumExtension } from './Extensions/WithEnumExtension';
*/
export interface OpenApiItems extends WithEnumExtension {
type?: string;
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
format?:
| 'int32'
| 'int64'
| 'float'
| 'double'
| 'string'
| 'boolean'
| 'byte'
| 'binary'
| 'date'
| 'date-time'
| 'password';
items?: OpenApiItems;
collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes';
default?: any;

View File

@ -14,7 +14,18 @@ export interface OpenApiParameter extends OpenApiReference, WithEnumExtension, W
required?: boolean;
schema?: OpenApiSchema;
type?: string;
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
format?:
| 'int32'
| 'int64'
| 'float'
| 'double'
| 'string'
| 'boolean'
| 'byte'
| 'binary'
| 'date'
| 'date-time'
| 'password';
allowEmptyValue?: boolean;
items?: OpenApiItems;
collectionFormat?: 'csv' | 'ssv' | 'tsv' | 'pipes' | 'multi';

View File

@ -28,7 +28,18 @@ export interface OpenApiSchema extends OpenApiReference, WithEnumExtension, With
required?: string[];
enum?: (string | number)[];
type?: string;
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
format?:
| 'int32'
| 'int64'
| 'float'
| 'double'
| 'string'
| 'boolean'
| 'byte'
| 'binary'
| 'date'
| 'date-time'
| 'password';
items?: OpenApiSchema;
allOf?: OpenApiSchema[];
properties?: Dictionary<OpenApiSchema>;

View File

@ -4,12 +4,25 @@ import { getComment } from './getComment';
describe('getComment', () => {
it('should parse comments', () => {
const multiline = 'Testing multiline comments.' + EOL + ' * This must go to the next line.' + EOL + ' * ' + EOL + ' * This will contain a break.';
const multiline =
'Testing multiline comments.' +
EOL +
' * This must go to the next line.' +
EOL +
' * ' +
EOL +
' * This will contain a break.';
expect(getComment('')).toEqual(null);
expect(getComment('Hello')).toEqual('Hello');
expect(getComment('Hello World!')).toEqual('Hello World!');
expect(getComment('Testing */escape/*')).toEqual('Testing *_/escape/*');
expect(getComment('Testing multiline comments.\nThis must go to the next line.\n\nThis will contain a break.')).toEqual(multiline);
expect(getComment('Testing multiline comments.\r\nThis must go to the next line.\r\n\r\nThis will contain a break.')).toEqual(multiline);
expect(
getComment('Testing multiline comments.\nThis must go to the next line.\n\nThis will contain a break.')
).toEqual(multiline);
expect(
getComment(
'Testing multiline comments.\r\nThis must go to the next line.\r\n\r\nThis will contain a break.'
)
).toEqual(multiline);
});
});

View File

@ -10,7 +10,12 @@ import { getModelComposition } from './getModelComposition';
import { getModelProperties } from './getModelProperties';
import { getType } from './getType';
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefinition: boolean = false, name: string = ''): Model {
export function getModel(
openApi: OpenApi,
definition: OpenApiSchema,
isDefinition: boolean = false,
name: string = ''
): Model {
const model: Model = {
name,
export: 'interface',

View File

@ -9,7 +9,13 @@ import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFro
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, definitions: OpenApiSchema[], type: 'one-of' | 'any-of' | 'all-of', getModel: GetModelFn): ModelComposition {
export function getModelComposition(
openApi: OpenApi,
definition: OpenApiSchema,
definitions: OpenApiSchema[],
type: 'one-of' | 'any-of' | 'all-of',
getModel: GetModelFn
): ModelComposition {
const composition: ModelComposition = {
type,
imports: [],
@ -35,7 +41,12 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema,
});
if (definition.required) {
const requiredProperties = getRequiredPropertiesFromComposition(openApi, definition.required, definitions, getModel);
const requiredProperties = getRequiredPropertiesFromComposition(
openApi,
definition.required,
definitions,
getModel
);
requiredProperties.forEach(requiredProperty => {
composition.imports.push(...requiredProperty.imports);
composition.enums.push(...requiredProperty.enums);

View File

@ -12,7 +12,13 @@ import { getOperationResponses } from './getOperationResponses';
import { getOperationResults } from './getOperationResults';
import { getServiceClassName } from './getServiceClassName';
export function getOperation(openApi: OpenApi, url: string, method: string, op: OpenApiOperation, pathParams: OperationParameters): Operation {
export function getOperation(
openApi: OpenApi,
url: string,
method: string,
op: OpenApiOperation,
pathParams: OperationParameters
): Operation {
const serviceName = op.tags?.[0] || 'Service';
const serviceClassName = getServiceClassName(serviceName);
const operationNameFallback = `${method}${serviceClassName}`;

View File

@ -1,7 +1,10 @@
import type { OperationParameter } from '../../../client/interfaces/OperationParameter';
import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
export function getOperationParameterDefault(parameter: OpenApiParameter, operationParameter: OperationParameter): string | undefined {
export function getOperationParameterDefault(
parameter: OpenApiParameter,
operationParameter: OperationParameter
): string | undefined {
if (parameter.default === undefined) {
return;
}

View File

@ -2,7 +2,9 @@ import { getOperationPath } from './getOperationPath';
describe('getOperationPath', () => {
it('should produce correct result', () => {
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}');
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual(
'/api/v${OpenAPI.VERSION}/list/${id}/${type}'
);
expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getOperationPath('/api/{foobar}')).toEqual('/api/${foobar}');

View File

@ -6,7 +6,11 @@ import { getComment } from './getComment';
import { getModel } from './getModel';
import { getType } from './getType';
export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse, responseCode: number): OperationResponse {
export function getOperationResponse(
openApi: OpenApi,
response: OpenApiResponse,
responseCode: number
): OperationResponse {
const operationResponse: OperationResponse = {
in: 'response',
name: '',

View File

@ -17,7 +17,9 @@ export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
// if we cannot find it, then we throw an error.
let result: any = openApi;
paths.forEach(path => {
const decodedPath = decodeURIComponent(path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~'));
const decodedPath = decodeURIComponent(
path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~')
);
if (result.hasOwnProperty(decodedPath)) {
result = result[decodedPath];
} else {

View File

@ -7,7 +7,12 @@ import { getRef } from './getRef';
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getRequiredPropertiesFromComposition(openApi: OpenApi, required: string[], definitions: OpenApiSchema[], getModel: GetModelFn): Model[] {
export function getRequiredPropertiesFromComposition(
openApi: OpenApi,
required: string[],
definitions: OpenApiSchema[],
getModel: GetModelFn
): Model[] {
return definitions
.reduce((properties, definition) => {
if (definition.$ref) {

View File

@ -34,7 +34,18 @@ export interface OpenApiSchema extends OpenApiReference, WithEnumExtension {
properties?: Dictionary<OpenApiSchema>;
additionalProperties?: boolean | OpenApiSchema;
description?: string;
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
format?:
| 'int32'
| 'int64'
| 'float'
| 'double'
| 'string'
| 'boolean'
| 'byte'
| 'binary'
| 'date'
| 'date-time'
| 'password';
default?: any;
nullable?: boolean;
discriminator?: OpenApiDiscriminator;

View File

@ -4,12 +4,25 @@ import { getComment } from './getComment';
describe('getComment', () => {
it('should parse comments', () => {
const multiline = 'Testing multiline comments.' + EOL + ' * This must go to the next line.' + EOL + ' * ' + EOL + ' * This will contain a break.';
const multiline =
'Testing multiline comments.' +
EOL +
' * This must go to the next line.' +
EOL +
' * ' +
EOL +
' * This will contain a break.';
expect(getComment('')).toEqual(null);
expect(getComment('Hello')).toEqual('Hello');
expect(getComment('Hello World!')).toEqual('Hello World!');
expect(getComment('Testing */escape/*')).toEqual('Testing *_/escape/*');
expect(getComment('Testing multiline comments.\nThis must go to the next line.\n\nThis will contain a break.')).toEqual(multiline);
expect(getComment('Testing multiline comments.\r\nThis must go to the next line.\r\n\r\nThis will contain a break.')).toEqual(multiline);
expect(
getComment('Testing multiline comments.\nThis must go to the next line.\n\nThis will contain a break.')
).toEqual(multiline);
expect(
getComment(
'Testing multiline comments.\r\nThis must go to the next line.\r\n\r\nThis will contain a break.'
)
).toEqual(multiline);
});
});

View File

@ -4,21 +4,38 @@ import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiMediaType } from '../interfaces/OpenApiMediaType';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
export function getContent(openApi: OpenApi, content: Dictionary<OpenApiMediaType>): OpenApiSchema | null {
const basicMediaTypeSchema =
content['application/json-patch+json']?.schema ||
content['application/json']?.schema ||
content['text/json']?.schema ||
content['text/plain']?.schema ||
content['multipart/mixed']?.schema ||
content['multipart/related']?.schema ||
content['multipart/batch']?.schema;
export interface Content {
mediaType: string;
schema: OpenApiSchema;
}
if (basicMediaTypeSchema) {
return basicMediaTypeSchema;
const BASIC_MEDIA_TYPES = [
'application/json-patch+json',
'application/json',
'text/json',
'text/plain',
'multipart/form-data',
'multipart/mixed',
'multipart/related',
'multipart/batch',
];
export function getContent(openApi: OpenApi, content: Dictionary<OpenApiMediaType>): Content | null {
const basicMedia = BASIC_MEDIA_TYPES.find(mediaType => isDefined(content[mediaType]?.schema));
if (basicMedia) {
return {
mediaType: basicMedia,
schema: content[basicMedia],
};
}
const mediaTypes = Object.values(content);
const mediaType = mediaTypes.find(mediaType => isDefined(mediaType.schema));
return mediaType?.schema || null;
const otherMediaTypes = Object.keys(content);
const otherMediaType = otherMediaTypes.find(mediaType => isDefined(content[mediaType]?.schema));
if (otherMediaType) {
return {
mediaType: otherMediaType,
schema: content[otherMediaType],
};
}
return null;
}

View File

@ -2,9 +2,17 @@ import type { Dictionary } from '../../../utils/types';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiMediaType } from '../interfaces/OpenApiMediaType';
const supportedMediaTypes = [
'application/json-patch+json',
'application/json',
'text/json',
'text/plain',
'multipart/form-data',
'multipart/mixed',
'multipart/related',
'multipart/batch',
];
export function getMediaType(openApi: OpenApi, content: Dictionary<OpenApiMediaType>): string | null {
return (
Object.keys(content).find(key => ['application/json-patch+json', 'application/json', 'text/json', 'text/plain', 'multipart/mixed', 'multipart/related', 'multipart/batch'].includes(key)) ||
null
);
return Object.keys(content).find(key => supportedMediaTypes.includes(key)) || null;
}

View File

@ -11,7 +11,12 @@ import { getModelDefault } from './getModelDefault';
import { getModelProperties } from './getModelProperties';
import { getType } from './getType';
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefinition: boolean = false, name: string = ''): Model {
export function getModel(
openApi: OpenApi,
definition: OpenApiSchema,
isDefinition: boolean = false,
name: string = ''
): Model {
const model: Model = {
name,
export: 'interface',

View File

@ -9,7 +9,13 @@ import { getRequiredPropertiesFromComposition } from './getRequiredPropertiesFro
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema, definitions: OpenApiSchema[], type: 'one-of' | 'any-of' | 'all-of', getModel: GetModelFn): ModelComposition {
export function getModelComposition(
openApi: OpenApi,
definition: OpenApiSchema,
definitions: OpenApiSchema[],
type: 'one-of' | 'any-of' | 'all-of',
getModel: GetModelFn
): ModelComposition {
const composition: ModelComposition = {
type,
imports: [],
@ -35,7 +41,12 @@ export function getModelComposition(openApi: OpenApi, definition: OpenApiSchema,
});
if (definition.required) {
const requiredProperties = getRequiredPropertiesFromComposition(openApi, definition.required, definitions, getModel);
const requiredProperties = getRequiredPropertiesFromComposition(
openApi,
definition.required,
definitions,
getModel
);
requiredProperties.forEach(requiredProperty => {
composition.imports.push(...requiredProperty.imports);
composition.enums.push(...requiredProperty.enums);

View File

@ -16,7 +16,13 @@ import { getRef } from './getRef';
import { getServiceClassName } from './getServiceClassName';
import { sortByRequired } from './sortByRequired';
export function getOperation(openApi: OpenApi, url: string, method: string, op: OpenApiOperation, pathParams: OperationParameters): Operation {
export function getOperation(
openApi: OpenApi,
url: string,
method: string,
op: OpenApiOperation,
pathParams: OperationParameters
): Operation {
const serviceName = op.tags?.[0] || 'Service';
const serviceClassName = getServiceClassName(serviceName);
const operationNameFallback = `${method}${serviceClassName}`;

View File

@ -2,7 +2,9 @@ import { getOperationPath } from './getOperationPath';
describe('getOperationPath', () => {
it('should produce correct result', () => {
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}');
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual(
'/api/v${OpenAPI.VERSION}/list/${id}/${type}'
);
expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getOperationPath('/api/{foobar}')).toEqual('/api/${foobar}');

View File

@ -4,15 +4,14 @@ import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiRequestBody } from '../interfaces/OpenApiRequestBody';
import { getComment } from './getComment';
import { getContent } from './getContent';
import { getMediaType } from './getMediaType';
import { getModel } from './getModel';
import { getType } from './getType';
export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequestBody): OperationParameter {
const requestBody: OperationParameter = {
in: 'body',
prop: 'body',
export: 'interface',
prop: 'requestBody',
name: 'requestBody',
type: 'any',
base: 'any',
@ -32,11 +31,15 @@ export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequ
};
if (parameter.content) {
const schema = getContent(openApi, parameter.content);
if (schema) {
requestBody.mediaType = getMediaType(openApi, parameter.content);
if (schema?.$ref) {
const model = getType(schema.$ref);
const content = getContent(openApi, parameter.content);
if (content) {
if (content.mediaType === 'multipart/form-data') {
requestBody.in = 'formData';
requestBody.name = 'formData';
requestBody.prop = 'formData';
}
if (content.schema.$ref) {
const model = getType(content.schema.$ref);
requestBody.export = 'reference';
requestBody.type = model.type;
requestBody.base = model.base;
@ -44,7 +47,7 @@ export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequ
requestBody.imports.push(...model.imports);
return requestBody;
} else {
const model = getModel(openApi, schema);
const model = getModel(openApi, content.schema);
requestBody.export = model.export;
requestBody.type = model.type;
requestBody.base = model.base;

View File

@ -7,7 +7,11 @@ import { getContent } from './getContent';
import { getModel } from './getModel';
import { getType } from './getType';
export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse, responseCode: number): OperationResponse {
export function getOperationResponse(
openApi: OpenApi,
response: OpenApiResponse,
responseCode: number
): OperationResponse {
const operationResponse: OperationResponse = {
in: 'response',
name: '',
@ -29,10 +33,10 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
};
if (response.content) {
const schema = getContent(openApi, response.content);
if (schema) {
if (schema?.$ref) {
const model = getType(schema.$ref);
const content = getContent(openApi, response.content);
if (content) {
if (content.schema.$ref) {
const model = getType(content.schema.$ref);
operationResponse.export = 'reference';
operationResponse.type = model.type;
operationResponse.base = model.base;
@ -40,7 +44,7 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
operationResponse.imports.push(...model.imports);
return operationResponse;
} else {
const model = getModel(openApi, schema);
const model = getModel(openApi, content.schema);
operationResponse.export = model.export;
operationResponse.type = model.type;
operationResponse.base = model.base;

View File

@ -17,7 +17,9 @@ export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
// if we cannot find it, then we throw an error.
let result: any = openApi;
paths.forEach(path => {
const decodedPath = decodeURIComponent(path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~'));
const decodedPath = decodeURIComponent(
path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~')
);
if (result.hasOwnProperty(decodedPath)) {
result = result[decodedPath];
} else {

View File

@ -7,7 +7,12 @@ import { getRef } from './getRef';
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getRequiredPropertiesFromComposition(openApi: OpenApi, required: string[], definitions: OpenApiSchema[], getModel: GetModelFn): Model[] {
export function getRequiredPropertiesFromComposition(
openApi: OpenApi,
required: string[],
definitions: OpenApiSchema[],
getModel: GetModelFn
): Model[] {
return definitions
.reduce((properties, definition) => {
if (definition.$ref) {

View File

@ -69,7 +69,12 @@ export class {{{name}}} {
},
{{/if}}
{{#if parametersBody}}
{{#equals parametersBody.in 'formData'}}
formData: {{{parametersBody.name}}},
{{/equals}}
{{#equals parametersBody.in 'body'}}
body: {{{parametersBody.name}}},
{{/equals}}
{{#if parametersBody.mediaType}}
mediaType: '{{{parametersBody.mediaType}}}',
{{/if}}

View File

@ -5,50 +5,78 @@ import { Model } from '../client/interfaces/Model';
import { HttpClient } from '../HttpClient';
import { unique } from './unique';
export function registerHandlebarHelpers(root: { httpClient: HttpClient; useOptions: boolean; useUnionTypes: boolean }): void {
Handlebars.registerHelper('equals', function (this: any, a: string, b: string, options: Handlebars.HelperOptions): string {
return a === b ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper('notEquals', function (this: any, a: string, b: string, options: Handlebars.HelperOptions): string {
return a !== b ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper('containsSpaces', function (this: any, value: string, options: Handlebars.HelperOptions): string {
return /\s+/.test(value) ? options.fn(this) : options.inverse(this);
});
Handlebars.registerHelper('union', function (this: any, properties: Model[], parent: string | undefined, options: Handlebars.HelperOptions) {
const type = Handlebars.partials['type'];
const types = properties.map(property => type({ ...root, ...property, parent }));
const uniqueTypes = types.filter(unique);
let uniqueTypesString = uniqueTypes.join(' | ');
if (uniqueTypes.length > 1) {
uniqueTypesString = `(${uniqueTypesString})`;
export function registerHandlebarHelpers(root: {
httpClient: HttpClient;
useOptions: boolean;
useUnionTypes: boolean;
}): void {
Handlebars.registerHelper(
'equals',
function (this: any, a: string, b: string, options: Handlebars.HelperOptions): string {
return a === b ? options.fn(this) : options.inverse(this);
}
return options.fn(uniqueTypesString);
});
);
Handlebars.registerHelper('intersection', function (this: any, properties: Model[], parent: string | undefined, options: Handlebars.HelperOptions) {
const type = Handlebars.partials['type'];
const types = properties.map(property => type({ ...root, ...property, parent }));
const uniqueTypes = types.filter(unique);
let uniqueTypesString = uniqueTypes.join(' & ');
if (uniqueTypes.length > 1) {
uniqueTypesString = `(${uniqueTypesString})`;
Handlebars.registerHelper(
'notEquals',
function (this: any, a: string, b: string, options: Handlebars.HelperOptions): string {
return a !== b ? options.fn(this) : options.inverse(this);
}
return options.fn(uniqueTypesString);
});
);
Handlebars.registerHelper('enumerator', function (this: any, enumerators: Enum[], parent: string | undefined, name: string | undefined, options: Handlebars.HelperOptions) {
if (!root.useUnionTypes && parent && name) {
return `${parent}.${name}`;
Handlebars.registerHelper(
'containsSpaces',
function (this: any, value: string, options: Handlebars.HelperOptions): string {
return /\s+/.test(value) ? options.fn(this) : options.inverse(this);
}
return options.fn(
enumerators
.map(enumerator => enumerator.value)
.filter(unique)
.join(' | ')
);
});
);
Handlebars.registerHelper(
'union',
function (this: any, properties: Model[], parent: string | undefined, options: Handlebars.HelperOptions) {
const type = Handlebars.partials['type'];
const types = properties.map(property => type({ ...root, ...property, parent }));
const uniqueTypes = types.filter(unique);
let uniqueTypesString = uniqueTypes.join(' | ');
if (uniqueTypes.length > 1) {
uniqueTypesString = `(${uniqueTypesString})`;
}
return options.fn(uniqueTypesString);
}
);
Handlebars.registerHelper(
'intersection',
function (this: any, properties: Model[], parent: string | undefined, options: Handlebars.HelperOptions) {
const type = Handlebars.partials['type'];
const types = properties.map(property => type({ ...root, ...property, parent }));
const uniqueTypes = types.filter(unique);
let uniqueTypesString = uniqueTypes.join(' & ');
if (uniqueTypes.length > 1) {
uniqueTypesString = `(${uniqueTypesString})`;
}
return options.fn(uniqueTypesString);
}
);
Handlebars.registerHelper(
'enumerator',
function (
this: any,
enumerators: Enum[],
parent: string | undefined,
name: string | undefined,
options: Handlebars.HelperOptions
) {
if (!root.useUnionTypes && parent && name) {
return `${parent}.${name}`;
}
return options.fn(
enumerators
.map(enumerator => enumerator.value)
.filter(unique)
.join(' | ')
);
}
);
}

View File

@ -97,7 +97,11 @@ 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.
*/
export function registerHandlebarTemplates(root: { httpClient: HttpClient; useOptions: boolean; useUnionTypes: boolean }): Templates {
export function registerHandlebarTemplates(root: {
httpClient: HttpClient;
useOptions: boolean;
useUnionTypes: boolean;
}): Templates {
registerHandlebarHelpers(root);
// Main templates (entry points for the files we write to disk)

View File

@ -57,7 +57,14 @@ export async function writeClient(
if (exportServices) {
await rmdir(outputPathServices);
await mkdir(outputPathServices);
await writeClientServices(client.services, templates, outputPathServices, httpClient, useUnionTypes, useOptions);
await writeClientServices(
client.services,
templates,
outputPathServices,
httpClient,
useUnionTypes,
useOptions
);
}
if (exportSchemas) {
@ -74,6 +81,15 @@ export async function writeClient(
if (exportCore || exportServices || exportSchemas || exportModels) {
await mkdir(outputPath);
await writeClientIndex(client, templates, outputPath, useUnionTypes, exportCore, exportServices, exportModels, exportSchemas);
await writeClientIndex(
client,
templates,
outputPath,
useUnionTypes,
exportCore,
exportServices,
exportModels,
exportSchemas
);
}
}

View File

@ -13,7 +13,13 @@ import { Templates } from './registerHandlebarTemplates';
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param request: Path to custom request file
*/
export async function writeClientCore(client: Client, templates: Templates, outputPath: string, httpClient: HttpClient, request?: string): Promise<void> {
export async function writeClientCore(
client: Client,
templates: Templates,
outputPath: string,
httpClient: HttpClient,
request?: string
): Promise<void> {
const context = {
httpClient,
server: client.server,

View File

@ -14,7 +14,13 @@ import { Templates } from './registerHandlebarTemplates';
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useUnionTypes Use union types instead of enums
*/
export async function writeClientModels(models: Model[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean): Promise<void> {
export async function writeClientModels(
models: Model[],
templates: Templates,
outputPath: string,
httpClient: HttpClient,
useUnionTypes: boolean
): Promise<void> {
for (const model of models) {
const file = resolve(outputPath, `${model.name}.ts`);
const templateResult = templates.exports.model({

View File

@ -14,7 +14,13 @@ import { Templates } from './registerHandlebarTemplates';
* @param httpClient The selected httpClient (fetch, xhr, node or axios)
* @param useUnionTypes Use union types instead of enums
*/
export async function writeClientSchemas(models: Model[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean): Promise<void> {
export async function writeClientSchemas(
models: Model[],
templates: Templates,
outputPath: string,
httpClient: HttpClient,
useUnionTypes: boolean
): Promise<void> {
for (const model of models) {
const file = resolve(outputPath, `$${model.name}.ts`);
const templateResult = templates.exports.schema({

View File

@ -17,7 +17,14 @@ const VERSION_TEMPLATE_STRING = 'OpenAPI.VERSION';
* @param useUnionTypes Use union types instead of enums
* @param useOptions Use options or arguments functions
*/
export async function writeClientServices(services: Service[], templates: Templates, outputPath: string, httpClient: HttpClient, useUnionTypes: boolean, useOptions: boolean): Promise<void> {
export async function writeClientServices(
services: Service[],
templates: Templates,
outputPath: string,
httpClient: HttpClient,
useUnionTypes: boolean,
useOptions: boolean
): Promise<void> {
for (const service of services) {
const file = resolve(outputPath, `${service.name}.ts`);
const useVersion = service.operations.some(operation => operation.path.includes(VERSION_TEMPLATE_STRING));

View File

@ -19,7 +19,7 @@ async function generateV2() {
async function generateV3() {
await OpenAPI.generate({
input: './test/spec/v3.json',
input: './test/spec/spec.json',
output: './test/generated/v3/',
httpClient: OpenAPI.HttpClient.FETCH,
useOptions: false,
@ -33,7 +33,7 @@ async function generateV3() {
}
async function generate() {
await generateV2();
// await generateV2();
await generateV3();
}