- Cleanup and latest dependencies

This commit is contained in:
Ferdi Koomen 2020-11-18 23:16:57 +01:00
parent bd495d39c9
commit 2d8aa162cd
45 changed files with 204 additions and 270 deletions

View File

@ -24,7 +24,8 @@
"@typescript-eslint/explicit-module-boundary-types": 0,
"sort-imports": "off",
"import/order": "off",
"simple-import-sort/sort": "error",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"prettier/prettier": ["error"]
}
}

View File

@ -15,6 +15,4 @@ export interface Model extends Schema {
enum: Enum[];
enums: Model[];
properties: Model[];
extendedFrom?: string[];
extendedBy?: string[];
}

View File

@ -1,5 +1,5 @@
import type { WithEnumExtension } from './Extensions/WithEnumExtension';
import { WithNullableExtension } from './Extensions/WithNullableExtension';
import type { WithNullableExtension } from './Extensions/WithNullableExtension';
import type { OpenApiItems } from './OpenApiItems';
import type { OpenApiReference } from './OpenApiReference';
import type { OpenApiSchema } from './OpenApiSchema';

View File

@ -1,44 +0,0 @@
export enum PrimaryType {
FILE = 'File',
OBJECT = 'any',
ARRAY = 'any[]',
BOOLEAN = 'boolean',
NUMBER = 'number',
STRING = 'string',
VOID = 'void',
NULL = 'null',
}
export const TYPE_MAPPINGS = new Map<string, PrimaryType>([
['File', PrimaryType.FILE],
['file', PrimaryType.FILE],
['any', PrimaryType.OBJECT],
['object', PrimaryType.OBJECT],
['array', PrimaryType.ARRAY],
['boolean', PrimaryType.BOOLEAN],
['byte', PrimaryType.NUMBER],
['int', PrimaryType.NUMBER],
['integer', PrimaryType.NUMBER],
['float', PrimaryType.NUMBER],
['double', PrimaryType.NUMBER],
['short', PrimaryType.NUMBER],
['long', PrimaryType.NUMBER],
['number', PrimaryType.NUMBER],
['char', PrimaryType.STRING],
['date', PrimaryType.STRING],
['date-time', PrimaryType.STRING],
['password', PrimaryType.STRING],
['string', PrimaryType.STRING],
['void', PrimaryType.VOID],
['null', PrimaryType.NULL],
]);
export enum Method {
GET = 'get',
PUT = 'put',
POST = 'post',
DELETE = 'delete',
OPTIONS = 'options',
HEAD = 'head',
PATCH = 'patch',
}

View File

@ -12,8 +12,8 @@ export function extendEnum(enumerators: Enum[], definition: WithEnumExtension):
const descriptions = definition['x-enum-descriptions'];
return enumerators.map((enumerator, index) => ({
name: (names && names[index]) || enumerator.name,
description: (descriptions && descriptions[index]) || enumerator.description,
name: names?.[index] || enumerator.name,
description: descriptions?.[index] || enumerator.description,
value: enumerator.value,
type: enumerator.type,
}));

View File

@ -1,5 +1,4 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { PrimaryType } from './constants';
import { isDefined } from './isDefined';
export function getEnum(values?: (string | number)[]): Enum[] {
@ -14,7 +13,7 @@ export function getEnum(values?: (string | number)[]): Enum[] {
return {
name: `_${value}`,
value: String(value),
type: PrimaryType.NUMBER,
type: 'number',
description: null,
};
}
@ -25,7 +24,7 @@ export function getEnum(values?: (string | number)[]): Enum[] {
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: `'${value}'`,
type: PrimaryType.STRING,
type: 'string',
description: null,
};
});

View File

@ -1,5 +1,4 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { PrimaryType } from './constants';
export function getEnumFromDescription(description: string): Enum[] {
// Check if we can find this special format string:
@ -20,7 +19,7 @@ export function getEnumFromDescription(description: string): Enum[] {
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: String(value),
type: PrimaryType.NUMBER,
type: 'number',
description: null,
});
}

View File

@ -1,9 +1,31 @@
import { PrimaryType, TYPE_MAPPINGS } from './constants';
export const TYPE_MAPPINGS = new Map<string, string>([
['File', 'File'],
['file', 'File'],
['any', 'any'],
['object', 'any'],
['array', 'any[]'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['integer', 'number'],
['float', 'number'],
['double', 'number'],
['short', 'number'],
['long', 'number'],
['number', 'number'],
['char', 'string'],
['date', 'string'],
['date-time', 'string'],
['password', 'string'],
['string', 'string'],
['void', 'void'],
['null', 'null'],
]);
/**
* Get mapped type for given type to any basic Typescript/Javascript type.
*/
export function getMappedType(type: string): PrimaryType | undefined {
export function getMappedType(type: string): string | undefined {
return TYPE_MAPPINGS.get(type);
}

View File

@ -1,7 +1,6 @@
import type { Model } from '../../../client/interfaces/Model';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { PrimaryType } from './constants';
import { extendEnum } from './extendEnum';
import { getComment } from './getComment';
import { getEnum } from './getEnum';
@ -14,8 +13,8 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
const model: Model = {
name: name,
export: 'interface',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
description: getComment(definition.description),
@ -59,8 +58,8 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
const extendedEnumerators = extendEnum(enumerators, definition);
if (extendedEnumerators.length) {
model.export = 'enum';
model.type = PrimaryType.STRING;
model.base = PrimaryType.STRING;
model.type = 'string';
model.base = 'string';
model.enum.push(...extendedEnumerators);
return model;
}
@ -70,8 +69,8 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
const enumerators = getEnumFromDescription(definition.description);
if (enumerators.length) {
model.export = 'enum';
model.type = PrimaryType.NUMBER;
model.base = PrimaryType.NUMBER;
model.type = 'number';
model.base = 'number';
model.enum.push(...enumerators);
return model;
}
@ -98,7 +97,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
}
}
if (definition.type === 'object' && definition.additionalProperties && typeof definition.additionalProperties === 'object') {
if (definition.type === 'object' && typeof definition.additionalProperties === 'object') {
if (definition.additionalProperties.$ref) {
const additionalProperties = getType(definition.additionalProperties.$ref);
model.export = 'dictionary';
@ -121,10 +120,10 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
if (definition.type === 'object' || definition.allOf) {
model.export = 'interface';
model.type = PrimaryType.OBJECT;
model.base = PrimaryType.OBJECT;
model.type = 'any';
model.base = 'any';
if (definition.allOf && definition.allOf.length) {
if (definition.allOf?.length) {
definition.allOf.forEach(parent => {
if (parent.$ref) {
const parentRef = getType(parent.$ref);

View File

@ -3,18 +3,19 @@ import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { escapeName } from './escapeName';
import { getComment } from './getComment';
import type { getModel } from './getModel';
import { getPattern } from './getPattern';
import { getType } from './getType';
// Fix for circular dependency between getModel and getModelProperties
export type GetModel = (openApi: OpenApi, definition: OpenApiSchema, isDefinition?: boolean, name?: string) => Model;
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema, getModel: GetModel): Model[] {
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema, getModel: GetModelFn): Model[] {
const models: Model[] = [];
for (const propertyName in definition.properties) {
if (definition.properties.hasOwnProperty(propertyName)) {
const property = definition.properties[propertyName];
const propertyRequired = definition.required && definition.required.includes(propertyName);
const propertyRequired = definition.required?.includes(propertyName);
if (property.$ref) {
const model = getType(property.$ref);
models.push({

View File

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

View File

@ -1,7 +1,6 @@
import type { OperationParameter } from '../../../client/interfaces/OperationParameter';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
import { PrimaryType } from './constants';
import { extendEnum } from './extendEnum';
import { getComment } from './getComment';
import { getEnum } from './getEnum';
@ -18,8 +17,8 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
prop: parameter.name,
export: 'interface',
name: getOperationParameterName(parameter.name),
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
description: getComment(parameter.description),
@ -62,8 +61,8 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
const extendedEnumerators = extendEnum(enumerators, parameter);
if (extendedEnumerators.length) {
operationParameter.export = 'enum';
operationParameter.type = PrimaryType.STRING;
operationParameter.base = PrimaryType.STRING;
operationParameter.type = 'string';
operationParameter.base = 'string';
operationParameter.enum.push(...extendedEnumerators);
operationParameter.default = getOperationParameterDefault(parameter, operationParameter);
return operationParameter;
@ -74,8 +73,8 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
const enumerators = getEnumFromDescription(parameter.description);
if (enumerators.length) {
operationParameter.export = 'enum';
operationParameter.type = PrimaryType.NUMBER;
operationParameter.base = PrimaryType.NUMBER;
operationParameter.type = 'number';
operationParameter.base = 'number';
operationParameter.enum.push(...enumerators);
operationParameter.default = getOperationParameterDefault(parameter, operationParameter);
return operationParameter;

View File

@ -16,7 +16,7 @@ export function getOperationParameterDefault(parameter: OpenApiParameter, operat
case 'int':
case 'integer':
case 'number':
if (operationParameter.export == 'enum' && operationParameter.enum.length && operationParameter.enum[parameter.default]) {
if (operationParameter.export == 'enum' && operationParameter.enum?.[parameter.default]) {
return operationParameter.enum[parameter.default].value;
}
return parameter.default;

View File

@ -1,7 +1,6 @@
import type { OperationResponse } from '../../../client/interfaces/OperationResponse';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiResponse } from '../interfaces/OpenApiResponse';
import { PrimaryType } from './constants';
import { getComment } from './getComment';
import { getModel } from './getModel';
import { getPattern } from './getPattern';
@ -14,8 +13,8 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
code: responseCode,
description: getComment(response.description)!,
export: 'generic',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
isDefinition: false,
@ -36,8 +35,8 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
if (response.headers.hasOwnProperty(name)) {
operationResponse.in = 'header';
operationResponse.name = name;
operationResponse.type = PrimaryType.STRING;
operationResponse.base = PrimaryType.STRING;
operationResponse.type = 'string';
operationResponse.base = 'string';
return operationResponse;
}
}

View File

@ -1,6 +1,5 @@
import type { Model } from '../../../client/interfaces/Model';
import type { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { PrimaryType } from './constants';
function areEqual(a: Model, b: Model): boolean {
const equal = a.type === b.type && a.base === b.base && a.template === b.template;
@ -26,8 +25,8 @@ export function getOperationResults(operationResponses: OperationResponse[]): Op
code: 200,
description: '',
export: 'interface',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
isDefinition: false,

View File

@ -5,7 +5,7 @@ import type { OpenApi } from '../interfaces/OpenApi';
* @param openApi
*/
export function getServer(openApi: OpenApi): string {
const scheme = (openApi.schemes && openApi.schemes[0]) || 'http';
const scheme = openApi.schemes?.[0] || 'http';
const host = openApi.host;
const basePath = openApi.basePath || '';
return host ? `${scheme}://${host}${basePath}` : basePath;

View File

@ -1,6 +1,5 @@
import type { Service } from '../../../client/interfaces/Service';
import type { OpenApi } from '../interfaces/OpenApi';
import { Method } from './constants';
import { getOperation } from './getOperation';
import { getOperationParameters } from './getOperationParameters';
@ -19,13 +18,13 @@ export function getServices(openApi: OpenApi): Service[] {
for (const method in path) {
if (path.hasOwnProperty(method)) {
switch (method) {
case Method.GET:
case Method.PUT:
case Method.POST:
case Method.DELETE:
case Method.OPTIONS:
case Method.HEAD:
case Method.PATCH:
case 'get':
case 'put':
case 'post':
case 'delete':
case 'options':
case 'head':
case 'patch':
// Each method contains an OpenAPI operation, we parse the operation
const op = path[method]!;
const operation = getOperation(openApi, url, method, op, pathParams);

View File

@ -1,5 +1,4 @@
import type { Type } from '../../../client/interfaces/Type';
import { PrimaryType } from './constants';
import { getMappedType, hasMappedType } from './getMappedType';
import { stripNamespace } from './stripNamespace';
@ -14,8 +13,8 @@ function encode(value: string): string {
*/
export function getType(value?: string, template?: string): Type {
const result: Type = {
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
imports: [],
};
@ -24,11 +23,11 @@ export function getType(value?: string, template?: string): Type {
if (/\[.*\]$/g.test(valueClean)) {
const matches = valueClean.match(/(.*?)\[(.*)\]$/);
if (matches && matches.length) {
if (matches?.length) {
const match1 = getType(encode(matches[1]));
const match2 = getType(encode(matches[2]));
if (match1.type === PrimaryType.ARRAY) {
if (match1.type === 'any[]') {
result.type = `${match2.type}[]`;
result.base = match2.type;
match1.imports = [];

View File

@ -1,54 +0,0 @@
export enum PrimaryType {
FILE = 'File',
OBJECT = 'any',
ARRAY = 'any[]',
BOOLEAN = 'boolean',
NUMBER = 'number',
STRING = 'string',
VOID = 'void',
NULL = 'null',
}
export const TYPE_MAPPINGS = new Map<string, PrimaryType>([
['File', PrimaryType.FILE],
['file', PrimaryType.FILE],
['any', PrimaryType.OBJECT],
['object', PrimaryType.OBJECT],
['array', PrimaryType.ARRAY],
['boolean', PrimaryType.BOOLEAN],
['byte', PrimaryType.NUMBER],
['int', PrimaryType.NUMBER],
['integer', PrimaryType.NUMBER],
['float', PrimaryType.NUMBER],
['double', PrimaryType.NUMBER],
['short', PrimaryType.NUMBER],
['long', PrimaryType.NUMBER],
['number', PrimaryType.NUMBER],
['char', PrimaryType.STRING],
['date', PrimaryType.STRING],
['date-time', PrimaryType.STRING],
['password', PrimaryType.STRING],
['string', PrimaryType.STRING],
['void', PrimaryType.VOID],
['null', PrimaryType.NULL],
]);
export enum Method {
GET = 'get',
PUT = 'put',
POST = 'post',
DELETE = 'delete',
OPTIONS = 'options',
HEAD = 'head',
PATCH = 'patch',
}
export enum ContentType {
APPLICATION_JSON_PATCH = 'application/json-patch+json',
APPLICATION_JSON = 'application/json',
TEXT_JSON = 'text/json',
TEXT_PAIN = 'text/plain',
MULTIPART_MIXED = 'multipart/mixed',
MULTIPART_RELATED = 'multipart/related',
MULTIPART_BATCH = 'multipart/batch',
}

View File

@ -12,8 +12,8 @@ export function extendEnum(enumerators: Enum[], definition: WithEnumExtension):
const descriptions = definition['x-enum-descriptions'];
return enumerators.map((enumerator, index) => ({
name: (names && names[index]) || enumerator.name,
description: (descriptions && descriptions[index]) || enumerator.description,
name: names?.[index] || enumerator.name,
description: descriptions?.[index] || enumerator.description,
value: enumerator.value,
type: enumerator.type,
}));

View File

@ -2,30 +2,29 @@ import type { Dictionary } from '../../../utils/types';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiMediaType } from '../interfaces/OpenApiMediaType';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { ContentType } from './constants';
export function getContent(openApi: OpenApi, content: Dictionary<OpenApiMediaType>): OpenApiSchema | null {
/* prettier-ignore */
return (
content[ContentType.APPLICATION_JSON_PATCH] &&
content[ContentType.APPLICATION_JSON_PATCH].schema
content['application/json-patch+json'] &&
content['application/json-patch+json'].schema
) || (
content[ContentType.APPLICATION_JSON] &&
content[ContentType.APPLICATION_JSON].schema
content['application/json'] &&
content['application/json'].schema
) || (
content[ContentType.TEXT_JSON] &&
content[ContentType.TEXT_JSON].schema
content['text/json'] &&
content['text/json'].schema
) || (
content[ContentType.TEXT_PAIN] &&
content[ContentType.TEXT_PAIN].schema
content['text/plain'] &&
content['text/plain'].schema
) || (
content[ContentType.MULTIPART_MIXED] &&
content[ContentType.MULTIPART_MIXED].schema
content['multipart/mixed'] &&
content['multipart/mixed'].schema
) || (
content[ContentType.MULTIPART_RELATED] &&
content[ContentType.MULTIPART_RELATED].schema
content['multipart/related'] &&
content['multipart/related'].schema
) || (
content[ContentType.MULTIPART_BATCH] &&
content[ContentType.MULTIPART_BATCH].schema
content['multipart/batch'] &&
content['multipart/batch'].schema
) || null;
}

View File

@ -1,5 +1,4 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { PrimaryType } from './constants';
import { isDefined } from './isDefined';
export function getEnum(values?: (string | number)[]): Enum[] {
@ -14,7 +13,7 @@ export function getEnum(values?: (string | number)[]): Enum[] {
return {
name: `_${value}`,
value: String(value),
type: PrimaryType.NUMBER,
type: 'number',
description: null,
};
}
@ -25,7 +24,7 @@ export function getEnum(values?: (string | number)[]): Enum[] {
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: `'${value}'`,
type: PrimaryType.STRING,
type: 'string',
description: null,
};
});

View File

@ -1,5 +1,4 @@
import type { Enum } from '../../../client/interfaces/Enum';
import { PrimaryType } from './constants';
export function getEnumFromDescription(description: string): Enum[] {
// Check if we can find this special format string:
@ -20,7 +19,7 @@ export function getEnumFromDescription(description: string): Enum[] {
.replace(/([a-z])([A-Z]+)/g, '$1_$2')
.toUpperCase(),
value: String(value),
type: PrimaryType.NUMBER,
type: 'number',
description: null,
});
}

View File

@ -1,9 +1,31 @@
import { PrimaryType, TYPE_MAPPINGS } from './constants';
export const TYPE_MAPPINGS = new Map<string, string>([
['File', 'File'],
['file', 'File'],
['any', 'any'],
['object', 'any'],
['array', 'any[]'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['integer', 'number'],
['float', 'number'],
['double', 'number'],
['short', 'number'],
['long', 'number'],
['number', 'number'],
['char', 'string'],
['date', 'string'],
['date-time', 'string'],
['password', 'string'],
['string', 'string'],
['void', 'void'],
['null', 'null'],
]);
/**
* Get mapped type for given type to any basic Typescript/Javascript type.
*/
export function getMappedType(type: string): PrimaryType | undefined {
export function getMappedType(type: string): string | undefined {
return TYPE_MAPPINGS.get(type);
}

View File

@ -1,7 +1,6 @@
import type { Model } from '../../../client/interfaces/Model';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { PrimaryType } from './constants';
import { extendEnum } from './extendEnum';
import { getComment } from './getComment';
import { getEnum } from './getEnum';
@ -15,8 +14,8 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
const model: Model = {
name: name,
export: 'interface',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
description: getComment(definition.description),
@ -61,8 +60,8 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
const extendedEnumerators = extendEnum(enumerators, definition);
if (extendedEnumerators.length) {
model.export = 'enum';
model.type = PrimaryType.STRING;
model.base = PrimaryType.STRING;
model.type = 'string';
model.base = 'string';
model.enum.push(...extendedEnumerators);
model.default = getModelDefault(definition, model);
return model;
@ -73,8 +72,8 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
const enumerators = getEnumFromDescription(definition.description);
if (enumerators.length) {
model.export = 'enum';
model.type = PrimaryType.NUMBER;
model.base = PrimaryType.NUMBER;
model.type = 'number';
model.base = 'number';
model.enum.push(...enumerators);
model.default = getModelDefault(definition, model);
return model;
@ -104,7 +103,7 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
}
}
if (definition.type === 'object' && definition.additionalProperties && typeof definition.additionalProperties === 'object') {
if (definition.type === 'object' && typeof definition.additionalProperties === 'object') {
if (definition.additionalProperties.$ref) {
const additionalProperties = getType(definition.additionalProperties.$ref);
model.export = 'dictionary';
@ -127,30 +126,26 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
}
}
// TODO:
// Add correct support for oneOf, anyOf, allOf
// https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/
if (definition.anyOf && definition.anyOf.length && !definition.properties) {
model.export = 'generic';
if (definition.anyOf?.length && !definition.properties) {
const compositionTypes = definition.anyOf.filter(type => type.$ref).map(type => getType(type.$ref));
const composition = compositionTypes
.map(type => type.type)
.sort()
.join(' | ');
model.export = 'generic';
model.imports.push(...compositionTypes.map(type => type.base));
model.type = composition;
model.base = composition;
return model;
}
if (definition.oneOf && definition.oneOf.length && !definition.properties) {
model.export = 'generic';
if (definition.oneOf?.length && !definition.properties) {
const compositionTypes = definition.oneOf.filter(type => type.$ref).map(type => getType(type.$ref));
const composition = compositionTypes
.map(type => type.type)
.sort()
.join(' | ');
model.export = 'generic';
model.imports.push(...compositionTypes.map(type => type.base));
model.type = composition;
model.base = composition;
@ -159,11 +154,11 @@ export function getModel(openApi: OpenApi, definition: OpenApiSchema, isDefiniti
if (definition.type === 'object' || definition.allOf) {
model.export = 'interface';
model.type = PrimaryType.OBJECT;
model.base = PrimaryType.OBJECT;
model.type = 'any';
model.base = 'any';
model.default = getModelDefault(definition, model);
if (definition.allOf && definition.allOf.length) {
if (definition.allOf?.length) {
definition.allOf.forEach(parent => {
if (parent.$ref) {
const parentRef = getType(parent.$ref);

View File

@ -16,7 +16,7 @@ export function getModelDefault(definition: OpenApiSchema, model?: Model): strin
case 'int':
case 'integer':
case 'number':
if (model && model.export == 'enum' && model.enum.length && model.enum[definition.default]) {
if (model?.export == 'enum' && model.enum?.[definition.default]) {
return model.enum[definition.default].value;
}
return definition.default;

View File

@ -3,18 +3,19 @@ import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { escapeName } from './escapeName';
import { getComment } from './getComment';
import type { getModel } from './getModel';
import { getPattern } from './getPattern';
import { getType } from './getType';
// Fix for circular dependency between getModel and getModelProperties
export type GetModel = (openApi: OpenApi, definition: OpenApiSchema, isDefinition?: boolean, name?: string) => Model;
// Fix for circular dependency
export type GetModelFn = typeof getModel;
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema, getModel: GetModel): Model[] {
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema, getModel: GetModelFn): Model[] {
const models: Model[] = [];
for (const propertyName in definition.properties) {
if (definition.properties.hasOwnProperty(propertyName)) {
const property = definition.properties[propertyName];
const propertyRequired = definition.required && definition.required.includes(propertyName);
const propertyRequired = definition.required?.includes(propertyName);
if (property.$ref) {
const model = getType(property.$ref);
models.push({

View File

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

View File

@ -1,7 +1,6 @@
import type { OperationParameter } from '../../../client/interfaces/OperationParameter';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
import { PrimaryType } from './constants';
import { getComment } from './getComment';
import { getModel } from './getModel';
import { getModelDefault } from './getModelDefault';
@ -15,8 +14,8 @@ export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParame
prop: parameter.name,
export: 'interface',
name: getOperationParameterName(parameter.name),
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
description: getComment(parameter.description),

View File

@ -1,7 +1,6 @@
import type { OperationParameter } from '../../../client/interfaces/OperationParameter';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiRequestBody } from '../interfaces/OpenApiRequestBody';
import { PrimaryType } from './constants';
import { getComment } from './getComment';
import { getContent } from './getContent';
import { getModel } from './getModel';
@ -14,8 +13,8 @@ export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequ
prop: 'body',
export: 'interface',
name: 'requestBody',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
description: getComment(parameter.description),
@ -34,7 +33,7 @@ export function getOperationRequestBody(openApi: OpenApi, parameter: OpenApiRequ
if (parameter.content) {
const schema = getContent(openApi, parameter.content);
if (schema) {
if (schema && schema.$ref) {
if (schema?.$ref) {
const model = getType(schema.$ref);
requestBody.export = 'reference';
requestBody.type = model.type;

View File

@ -1,7 +1,6 @@
import type { OperationResponse } from '../../../client/interfaces/OperationResponse';
import type { OpenApi } from '../interfaces/OpenApi';
import type { OpenApiResponse } from '../interfaces/OpenApiResponse';
import { PrimaryType } from './constants';
import { getComment } from './getComment';
import { getContent } from './getContent';
import { getModel } from './getModel';
@ -15,8 +14,8 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
code: responseCode,
description: getComment(response.description)!,
export: 'generic',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
isDefinition: false,
@ -37,8 +36,8 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
if (response.headers.hasOwnProperty(name)) {
operationResponse.in = 'header';
operationResponse.name = name;
operationResponse.type = PrimaryType.STRING;
operationResponse.base = PrimaryType.STRING;
operationResponse.type = 'string';
operationResponse.base = 'string';
return operationResponse;
}
}
@ -47,7 +46,7 @@ export function getOperationResponse(openApi: OpenApi, response: OpenApiResponse
if (response.content) {
const schema = getContent(openApi, response.content);
if (schema) {
if (schema && schema.$ref) {
if (schema?.$ref) {
const model = getType(schema.$ref);
operationResponse.export = 'reference';
operationResponse.type = model.type;

View File

@ -1,6 +1,5 @@
import type { Model } from '../../../client/interfaces/Model';
import type { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { PrimaryType } from './constants';
function areEqual(a: Model, b: Model): boolean {
const equal = a.type === b.type && a.base === b.base && a.template === b.template;
@ -26,8 +25,8 @@ export function getOperationResults(operationResponses: OperationResponse[]): Op
code: 200,
description: '',
export: 'interface',
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
link: null,
isDefinition: false,

View File

@ -1,9 +1,9 @@
import type { OpenApi } from '../interfaces/OpenApi';
export function getServer(openApi: OpenApi): string {
const server = openApi.servers && openApi.servers[0];
const variables = (server && server.variables) || {};
let url = (server && server.url) || '';
const server = openApi.servers?.[0];
const variables = server?.variables || {};
let url = server?.url || '';
for (const variable in variables) {
if (variables.hasOwnProperty(variable)) {
url = url.replace(`{${variable}}`, variables[variable].default);

View File

@ -1,6 +1,5 @@
import type { Service } from '../../../client/interfaces/Service';
import type { OpenApi } from '../interfaces/OpenApi';
import { Method } from './constants';
import { getOperation } from './getOperation';
import { getOperationParameters } from './getOperationParameters';
@ -19,13 +18,13 @@ export function getServices(openApi: OpenApi): Service[] {
for (const method in path) {
if (path.hasOwnProperty(method)) {
switch (method) {
case Method.GET:
case Method.PUT:
case Method.POST:
case Method.DELETE:
case Method.OPTIONS:
case Method.HEAD:
case Method.PATCH:
case 'get':
case 'put':
case 'post':
case 'delete':
case 'options':
case 'head':
case 'patch':
// Each method contains an OpenAPI operation, we parse the operation
const op = path[method]!;
const operation = getOperation(openApi, url, method, op, pathParams);

View File

@ -1,5 +1,4 @@
import type { Type } from '../../../client/interfaces/Type';
import { PrimaryType } from './constants';
import { getMappedType, hasMappedType } from './getMappedType';
import { stripNamespace } from './stripNamespace';
@ -14,8 +13,8 @@ function encode(value: string): string {
*/
export function getType(value?: string, template?: string): Type {
const result: Type = {
type: PrimaryType.OBJECT,
base: PrimaryType.OBJECT,
type: 'any',
base: 'any',
template: null,
imports: [],
};
@ -24,11 +23,11 @@ export function getType(value?: string, template?: string): Type {
if (/\[.*\]$/g.test(valueClean)) {
const matches = valueClean.match(/(.*?)\[(.*)\]$/);
if (matches && matches.length) {
if (matches?.length) {
const match1 = getType(encode(matches[1]));
const match2 = getType(encode(matches[2]));
if (match1.type === PrimaryType.ARRAY) {
if (match1.type === 'any[]') {
result.type = `${match2.type}[]`;
result.base = `${match2.type}`;
match1.imports = [];

11
src/typings/hbs.d.ts vendored
View File

@ -7,11 +7,10 @@
* @see: build.js for more information
*/
declare module '*.hbs' {
export default {
compiler: [8, '>= 4.3.0'],
useData: true,
main: function () {
return '';
},
const template: {
compiler: [number, string];
useData: true;
main: () => void;
};
export default template;
}

View File

@ -11,7 +11,7 @@ export enum OpenApiVersion {
*/
export function getOpenApiVersion(openApi: any): OpenApiVersion {
const info: any = openApi.swagger || openApi.openapi;
if (info && typeof info === 'string') {
if (typeof info === 'string') {
const c = info.charAt(0);
const v = Number.parseInt(c);
if (v === OpenApiVersion.V2 || v === OpenApiVersion.V3) {

View File

@ -19,7 +19,7 @@ describe('getServiceNames', () => {
imports: [],
};
const services: Service[] = [john, jane, doe];
const services = [john, jane, doe];
expect(getServiceNames([])).toEqual([]);
expect(getServiceNames(services)).toEqual(['Doe', 'Jane', 'John']);

View File

@ -30,7 +30,7 @@ describe('writeClientIndex', () => {
},
};
await writeClientIndex(client, templates, '/', true, true, true, true);
await writeClientIndex(client, templates, '/', true, true, true, true, true);
expect(writeFile).toBeCalledWith('/index.ts', 'index');
});

View File

@ -1,4 +1,5 @@
import type { Model } from '../client/interfaces/Model';
import { HttpClient } from '../index';
import { writeFile } from './fileSystem';
import { Templates } from './registerHandlebarTemplates';
import { writeClientModels } from './writeClientModels';
@ -44,7 +45,7 @@ describe('writeClientModels', () => {
},
};
await writeClientModels(models, templates, '/');
await writeClientModels(models, templates, '/', HttpClient.FETCH, false);
expect(writeFile).toBeCalledWith('/MyModel.ts', 'model');
});

View File

@ -1,4 +1,5 @@
import type { Model } from '../client/interfaces/Model';
import { HttpClient } from '../index';
import { writeFile } from './fileSystem';
import { Templates } from './registerHandlebarTemplates';
import { writeClientSchemas } from './writeClientSchemas';
@ -44,7 +45,7 @@ describe('writeClientSchemas', () => {
},
};
await writeClientSchemas(models, templates, '/');
await writeClientSchemas(models, templates, '/', HttpClient.FETCH, false);
expect(writeFile).toBeCalledWith('/$MyModel.ts', 'schema');
});

View File

@ -1,4 +1,5 @@
import type { Service } from '../client/interfaces/Service';
import { HttpClient } from '../index';
import { writeFile } from './fileSystem';
import { Templates } from './registerHandlebarTemplates';
import { writeClientServices } from './writeClientServices';
@ -31,7 +32,7 @@ describe('writeClientServices', () => {
},
};
await writeClientServices(services, templates, '/', false);
await writeClientServices(services, templates, '/', HttpClient.FETCH, false, false);
expect(writeFile).toBeCalledWith('/MyService.ts', 'service');
});

View File

@ -22,6 +22,7 @@ function compileWithTypescript(dir) {
strictNullChecks: true,
strictFunctionTypes: true,
allowSyntheticDefaultImports: true,
skipLibCheck: true
},
include: ['./index.ts'],
};

View File

@ -15,7 +15,8 @@
"strict": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"files": [

View File

@ -1310,7 +1310,12 @@
"@types/node" "*"
form-data "^3.0.0"
"@types/node@*", "@types/node@14.14.7":
"@types/node@*":
version "14.14.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.8.tgz#2127bd81949a95c8b7d3240f3254352d72563aec"
integrity sha512-z/5Yd59dCKI5kbxauAJgw6dLPzW+TNOItNE00PkpzNwUIEwdj/Lsqwq94H5DdYBX7C13aRA0CY32BK76+neEUA==
"@types/node@14.14.7":
version "14.14.7"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.7.tgz#8ea1e8f8eae2430cf440564b98c6dfce1ec5945d"
integrity sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg==
@ -1343,9 +1348,9 @@
"@types/node" "*"
"@types/serve-static@*":
version "1.13.7"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.7.tgz#e51b51a0becda910f9fd04c718044da69d6c492e"
integrity sha512-3diZWucbR+xTmbDlU+FRRxBf+31OhFew7cJXML/zh9NmvSPTNoFecAwHB66BUqFgENJtqMiyl7JAwUE/siqdLw==
version "1.13.8"
resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.8.tgz#851129d434433c7082148574ffec263d58309c46"
integrity sha512-MoJhSQreaVoL+/hurAZzIm8wafFR6ajiTM1m4A0kv6AGeVBl4r4pOV8bGFrjjq1sGxDTnCoF8i22o0/aE5XCyA==
dependencies:
"@types/mime" "*"
"@types/node" "*"
@ -1361,9 +1366,9 @@
integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
"@types/yargs@^15.0.0":
version "15.0.9"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.9.tgz#524cd7998fe810cdb02f26101b699cccd156ff19"
integrity sha512-HmU8SeIRhZCWcnRskCs36Q1Q00KBV6Cqh/ora8WN1+22dY07AZdn6Gel8QZ3t26XYPImtcL8WV/eqjhVmMEw4g==
version "15.0.10"
resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.10.tgz#0fe3c8173a0d5c3e780b389050140c3f5ea6ea74"
integrity sha512-z8PNtlhrj7eJNLmrAivM7rjBESG6JwC5xP3RVk12i/8HVP7Xnx/sEmERnRImyEuUaJfO942X0qMOYsoupaJbZQ==
dependencies:
"@types/yargs-parser" "*"
@ -1904,9 +1909,9 @@ camelcase@^5.0.0, camelcase@^5.3.1:
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001157:
version "1.0.30001157"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001157.tgz#2d11aaeb239b340bc1aa730eca18a37fdb07a9ab"
integrity sha512-gOerH9Wz2IRZ2ZPdMfBvyOi3cjaz4O4dgNwPGzx8EhqAs4+2IL/O+fJsbt+znSigujoZG8bVcIAUM/I/E5K3MA==
version "1.0.30001159"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001159.tgz#bebde28f893fa9594dadcaa7d6b8e2aa0299df20"
integrity sha512-w9Ph56jOsS8RL20K9cLND3u/+5WASWdhC/PPrf+V3/HsM3uHOavWOR1Xzakbv4Puo/srmPHudkmCRWM7Aq+/UA==
capture-exit@^2.0.0:
version "2.0.0"
@ -2338,9 +2343,9 @@ ee-first@1.1.1:
integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
electron-to-chromium@^1.3.591:
version "1.3.596"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.596.tgz#c7ed98512c7ff36ddcbfed9e54e6355335c35257"
integrity sha512-nLO2Wd2yU42eSoNJVQKNf89CcEGqeFZd++QsnN2XIgje1s/19AgctfjLIbPORlvcCO8sYjLwX4iUgDdusOY8Sg==
version "1.3.599"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.599.tgz#3fbb004733f3c0dcf59934c8644ddf800b94443a"
integrity sha512-u6VGpFsIzSCNrWJb1I72SUypz3EGoBaiEgygoMkd0IOcGR3WF3je5VTx9OIRI9Qd8UOMHinLImyJFkYHTq6nsg==
emittery@^0.7.1:
version "0.7.2"
@ -4336,9 +4341,9 @@ node-notifier@^8.0.0:
which "^2.0.2"
node-releases@^1.1.66:
version "1.1.66"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.66.tgz#609bd0dc069381015cd982300bae51ab4f1b1814"
integrity sha512-JHEQ1iWPGK+38VLB2H9ef2otU4l8s3yAMt9Xf934r6+ojCYDMHPMqvCc9TnzfeFSP1QEOeU6YZEd3+De0LTCgg==
version "1.1.67"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.67.tgz#28ebfcccd0baa6aad8e8d4d8fe4cbc49ae239c12"
integrity sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==
normalize-package-data@^2.5.0:
version "2.5.0"
@ -5536,9 +5541,9 @@ terminal-link@^2.0.0:
supports-hyperlinks "^2.0.0"
terser@^5.0.0:
version "5.3.8"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.8.tgz#991ae8ba21a3d990579b54aa9af11586197a75dd"
integrity sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==
version "5.4.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.4.0.tgz#9815c0839072d5c894e22c6fc508fbe9f5e7d7e8"
integrity sha512-3dZunFLbCJis9TAF2VnX+VrQLctRUmt1p3W2kCsJuZE4ZgWqh//+1MZ62EanewrqKoUf4zIaDGZAvml4UDc0OQ==
dependencies:
commander "^2.20.0"
source-map "~0.7.2"