mirror of
https://github.com/ferdikoomen/openapi-typescript-codegen.git
synced 2025-12-08 20:16:21 +00:00
- Working on support for formData in v3
This commit is contained in:
parent
fa2bab05c7
commit
433b4f1b21
@ -3,6 +3,6 @@
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"arrowParens": "avoid",
|
||||
"printWidth": 200,
|
||||
"printWidth": 120,
|
||||
"tabWidth": 4
|
||||
}
|
||||
|
||||
13
src/client/interfaces/Schema.d.ts
vendored
13
src/client/interfaces/Schema.d.ts
vendored
@ -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;
|
||||
|
||||
28
src/index.ts
28
src/index.ts
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
13
src/openApi/v2/interfaces/OpenApiHeader.d.ts
vendored
13
src/openApi/v2/interfaces/OpenApiHeader.d.ts
vendored
@ -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;
|
||||
|
||||
13
src/openApi/v2/interfaces/OpenApiItems.d.ts
vendored
13
src/openApi/v2/interfaces/OpenApiItems.d.ts
vendored
@ -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;
|
||||
|
||||
13
src/openApi/v2/interfaces/OpenApiParameter.d.ts
vendored
13
src/openApi/v2/interfaces/OpenApiParameter.d.ts
vendored
@ -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';
|
||||
|
||||
13
src/openApi/v2/interfaces/OpenApiSchema.d.ts
vendored
13
src/openApi/v2/interfaces/OpenApiSchema.d.ts
vendored
@ -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>;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}`;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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}');
|
||||
|
||||
@ -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: '',
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
13
src/openApi/v3/interfaces/OpenApiSchema.d.ts
vendored
13
src/openApi/v3/interfaces/OpenApiSchema.d.ts
vendored
@ -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;
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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}`;
|
||||
|
||||
@ -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}');
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -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(' | ')
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user