mirror of
https://github.com/ferdikoomen/openapi-typescript-codegen.git
synced 2025-12-08 20:16:21 +00:00
- v3 generator
This commit is contained in:
parent
cd8b72b080
commit
b92a02a907
@ -1,7 +1,7 @@
|
||||
import { Model } from './Model';
|
||||
|
||||
export interface OperationParameter extends Model {
|
||||
in: 'path' | 'query' | 'header' | 'formData' | 'body';
|
||||
in: 'path' | 'query' | 'header' | 'formData' | 'body' | 'cookie';
|
||||
prop: string;
|
||||
default?: any;
|
||||
}
|
||||
|
||||
44
src/openApi/v3/parser/constants.ts
Normal file
44
src/openApi/v3/parser/constants.ts
Normal file
@ -0,0 +1,44 @@
|
||||
export enum PrimaryType {
|
||||
FILE = 'File',
|
||||
OBJECT = 'any',
|
||||
BOOLEAN = 'boolean',
|
||||
NUMBER = 'number',
|
||||
STRING = 'string',
|
||||
VOID = 'void',
|
||||
NULL = 'null',
|
||||
}
|
||||
|
||||
export const TYPE_MAPPINGS = new Map<string, PrimaryType>([
|
||||
['file', PrimaryType.FILE],
|
||||
['binary', PrimaryType.FILE],
|
||||
['any', PrimaryType.OBJECT],
|
||||
['object', PrimaryType.OBJECT],
|
||||
['boolean', PrimaryType.BOOLEAN],
|
||||
['byte', PrimaryType.NUMBER],
|
||||
['int', PrimaryType.NUMBER],
|
||||
['int32', PrimaryType.NUMBER],
|
||||
['int64', 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',
|
||||
}
|
||||
13
src/openApi/v3/parser/getComment.ts
Normal file
13
src/openApi/v3/parser/getComment.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { EOL } from 'os';
|
||||
|
||||
export function getComment(comment?: string): string | null {
|
||||
if (comment) {
|
||||
return comment
|
||||
.split(/(\r\n|\n|\r)+/g)
|
||||
.filter(line => line)
|
||||
.map(line => line.trim())
|
||||
.join(EOL)
|
||||
.replace(/(\r\n|\n|\r)+/g, '$1 * ');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
26
src/openApi/v3/parser/getEnum.ts
Normal file
26
src/openApi/v3/parser/getEnum.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { Enum } from '../../../client/interfaces/Enum';
|
||||
import { PrimaryType } from './constants';
|
||||
|
||||
export function getEnum(values?: (string | number)[]): Enum[] {
|
||||
if (Array.isArray(values)) {
|
||||
return values
|
||||
.filter((value, index, arr) => {
|
||||
return arr.indexOf(value) === index;
|
||||
})
|
||||
.map(value => {
|
||||
if (typeof value === 'number') {
|
||||
return {
|
||||
name: `NUM_${value}`,
|
||||
value: String(value),
|
||||
type: PrimaryType.NUMBER,
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: value.replace(/([a-z])([A-Z]+)/g, '$1_$2').toUpperCase(),
|
||||
value: `'${value}'`,
|
||||
type: PrimaryType.STRING,
|
||||
};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}
|
||||
32
src/openApi/v3/parser/getEnumFromDescription.ts
Normal file
32
src/openApi/v3/parser/getEnumFromDescription.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Enum } from '../../../client/interfaces/Enum';
|
||||
import { PrimaryType } from './constants';
|
||||
|
||||
export function getEnumFromDescription(description: string): Enum[] {
|
||||
// Check if we can find this special format string:
|
||||
// None=0,Something=1,AnotherThing=2
|
||||
if (/^(\w+=[0-9]+,?)+$/g.test(description)) {
|
||||
const matches = description.match(/(\w+=[0-9]+,?)/g);
|
||||
if (matches) {
|
||||
// Grab the values from the description
|
||||
const symbols: Enum[] = [];
|
||||
matches.forEach(match => {
|
||||
const name = match.split('=')[0];
|
||||
const value = parseInt(match.split('=')[1].replace(/[^0-9]/g, ''));
|
||||
if (name && Number.isInteger(value)) {
|
||||
symbols.push({
|
||||
name: name.replace(/([a-z])([A-Z]+)/g, '$1_$2').toUpperCase(),
|
||||
value: String(value),
|
||||
type: PrimaryType.NUMBER,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Filter out any duplicate names
|
||||
return symbols.filter((symbol, index, arr) => {
|
||||
return arr.map(item => item.name).indexOf(symbol.name) === index;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
18
src/openApi/v3/parser/getEnumType.ts
Normal file
18
src/openApi/v3/parser/getEnumType.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Enum } from '../../../client/interfaces/Enum';
|
||||
import { getEnumValues } from './getEnumValues';
|
||||
|
||||
export function getEnumType(enumerators: Enum[], addParentheses: boolean = false): string {
|
||||
// Fetch values from the symbols, just to be sure we filter out
|
||||
// any double values and finally we sort them to make them easier
|
||||
// to read when we use them in our generated code.
|
||||
const values = getEnumValues(enumerators);
|
||||
|
||||
// Add grouping parentheses if needed. This can be handy if enum values
|
||||
// are used in Arrays, so that you will get the following definition:
|
||||
// const myArray: ('EnumValue1' | 'EnumValue2' | 'EnumValue3')[];
|
||||
if (values.length > 1 && addParentheses) {
|
||||
return `(${values.join(' | ')})`;
|
||||
}
|
||||
|
||||
return values.join(' | ');
|
||||
}
|
||||
13
src/openApi/v3/parser/getEnumValues.ts
Normal file
13
src/openApi/v3/parser/getEnumValues.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Enum } from '../../../client/interfaces/Enum';
|
||||
|
||||
export function getEnumValues(enumerators: Enum[]): string[] {
|
||||
// Fetch values from the symbols, just to be sure we filter out
|
||||
// any double values and finally we sort them to make them easier
|
||||
// to read when we use them in our generated code.
|
||||
return enumerators
|
||||
.map(enumerator => enumerator.value)
|
||||
.filter((enumerator, index, arr) => {
|
||||
return arr.indexOf(enumerator) === index;
|
||||
})
|
||||
.sort();
|
||||
}
|
||||
21
src/openApi/v3/parser/getMappedType.spec.ts
Normal file
21
src/openApi/v3/parser/getMappedType.spec.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { getMappedType } from './getMappedType';
|
||||
|
||||
describe('getMappedType', () => {
|
||||
it('should map types to the basics', () => {
|
||||
expect(getMappedType('File')).toEqual('File');
|
||||
expect(getMappedType('String')).toEqual('string');
|
||||
expect(getMappedType('date')).toEqual('string');
|
||||
expect(getMappedType('date-time')).toEqual('string');
|
||||
expect(getMappedType('float')).toEqual('number');
|
||||
expect(getMappedType('double')).toEqual('number');
|
||||
expect(getMappedType('short')).toEqual('number');
|
||||
expect(getMappedType('int')).toEqual('number');
|
||||
expect(getMappedType('boolean')).toEqual('boolean');
|
||||
expect(getMappedType('any')).toEqual('any');
|
||||
expect(getMappedType('object')).toEqual('any');
|
||||
expect(getMappedType('void')).toEqual('void');
|
||||
expect(getMappedType('null')).toEqual('null');
|
||||
expect(getMappedType('unknown')).toEqual('unknown');
|
||||
expect(getMappedType('')).toEqual('');
|
||||
});
|
||||
});
|
||||
16
src/openApi/v3/parser/getMappedType.ts
Normal file
16
src/openApi/v3/parser/getMappedType.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { PrimaryType, TYPE_MAPPINGS } from './constants';
|
||||
|
||||
/**
|
||||
* Get mapped type for given type to any basic Typescript/Javascript type.
|
||||
*/
|
||||
export function getMappedType(type: string): PrimaryType | string {
|
||||
const mapped = TYPE_MAPPINGS.get(type.toLowerCase());
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
export function hasMappedType(type: string): boolean {
|
||||
return TYPE_MAPPINGS.has(type.toLowerCase());
|
||||
}
|
||||
159
src/openApi/v3/parser/getModel.ts
Normal file
159
src/openApi/v3/parser/getModel.ts
Normal file
@ -0,0 +1,159 @@
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
|
||||
import { getComment } from './getComment';
|
||||
import { getType } from './getType';
|
||||
import { Model } from '../../../client/interfaces/Model';
|
||||
import { PrimaryType } from './constants';
|
||||
import { getEnumType } from './getEnumType';
|
||||
import { getEnum } from './getEnum';
|
||||
import { getEnumFromDescription } from './getEnumFromDescription';
|
||||
import { getModelProperties } from './getModelProperties';
|
||||
|
||||
export function getModel(openApi: OpenApi, definition: OpenApiSchema, isProperty: boolean = false, name: string = ''): Model {
|
||||
const model: Model = {
|
||||
name: name,
|
||||
export: 'interface',
|
||||
type: PrimaryType.OBJECT,
|
||||
base: PrimaryType.OBJECT,
|
||||
template: null,
|
||||
link: null,
|
||||
description: getComment(definition.description),
|
||||
isProperty: isProperty,
|
||||
isReadOnly: definition.readOnly || false,
|
||||
isRequired: false,
|
||||
isNullable: false,
|
||||
imports: [],
|
||||
extends: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
};
|
||||
|
||||
if (definition.$ref) {
|
||||
const definitionRef = getType(definition.$ref);
|
||||
model.export = 'reference';
|
||||
model.type = definitionRef.type;
|
||||
model.base = definitionRef.base;
|
||||
model.template = definitionRef.template;
|
||||
model.imports.push(...definitionRef.imports);
|
||||
return model;
|
||||
}
|
||||
|
||||
if (definition.enum) {
|
||||
const enumerators = getEnum(definition.enum);
|
||||
if (enumerators.length) {
|
||||
model.export = 'enum';
|
||||
model.type = getEnumType(enumerators);
|
||||
model.base = PrimaryType.STRING;
|
||||
model.enum.push(...enumerators);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
if ((definition.type === 'int' || definition.type === 'integer') && definition.description) {
|
||||
const enumerators = getEnumFromDescription(definition.description);
|
||||
if (enumerators.length) {
|
||||
model.export = 'enum';
|
||||
model.type = getEnumType(enumerators);
|
||||
model.base = PrimaryType.NUMBER;
|
||||
model.enum.push(...enumerators);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type === 'array' && definition.items) {
|
||||
if (definition.items.$ref) {
|
||||
const arrayItems = getType(definition.items.$ref);
|
||||
model.export = 'array';
|
||||
model.type = arrayItems.type;
|
||||
model.base = arrayItems.base;
|
||||
model.template = arrayItems.template;
|
||||
model.imports.push(...arrayItems.imports);
|
||||
return model;
|
||||
} else {
|
||||
const arrayItems = getModel(openApi, definition.items, true);
|
||||
model.export = 'array';
|
||||
model.type = arrayItems.type;
|
||||
model.base = arrayItems.base;
|
||||
model.template = arrayItems.template;
|
||||
model.link = arrayItems;
|
||||
model.imports.push(...arrayItems.imports);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type === 'object' && definition.additionalProperties && typeof definition.additionalProperties === 'object') {
|
||||
if (definition.additionalProperties.$ref) {
|
||||
const additionalProperties = getType(definition.additionalProperties.$ref);
|
||||
model.export = 'dictionary';
|
||||
model.type = additionalProperties.type;
|
||||
model.base = additionalProperties.base;
|
||||
model.template = additionalProperties.template;
|
||||
model.imports.push(...additionalProperties.imports);
|
||||
model.imports.push('Dictionary');
|
||||
return model;
|
||||
} else {
|
||||
const additionalProperties = getModel(openApi, definition.additionalProperties);
|
||||
model.export = 'dictionary';
|
||||
model.type = additionalProperties.type;
|
||||
model.base = additionalProperties.base;
|
||||
model.template = additionalProperties.template;
|
||||
model.link = additionalProperties;
|
||||
model.imports.push(...additionalProperties.imports);
|
||||
model.imports.push('Dictionary');
|
||||
return model;
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type === 'object') {
|
||||
model.export = 'interface';
|
||||
model.type = PrimaryType.OBJECT;
|
||||
model.base = PrimaryType.OBJECT;
|
||||
|
||||
if (definition.allOf) {
|
||||
definition.allOf.forEach(parent => {
|
||||
if (parent.$ref) {
|
||||
const parentRef = getType(parent.$ref);
|
||||
model.extends.push(parentRef.type);
|
||||
model.imports.push(parentRef.base);
|
||||
}
|
||||
if (parent.type === 'object' && parent.properties) {
|
||||
const properties = getModelProperties(openApi, parent);
|
||||
properties.forEach(property => {
|
||||
model.properties.push(property);
|
||||
model.imports.push(...property.imports);
|
||||
if (property.export === 'enum') {
|
||||
model.enums.push(property);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (definition.properties) {
|
||||
const properties = getModelProperties(openApi, definition);
|
||||
properties.forEach(property => {
|
||||
model.properties.push(property);
|
||||
model.imports.push(...property.imports);
|
||||
if (property.export === 'enum') {
|
||||
model.enums.push(property);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
// If the schema has a type than it can be a basic or generic type.
|
||||
if (definition.type) {
|
||||
const definitionType = getType(definition.type);
|
||||
model.export = 'generic';
|
||||
model.type = definitionType.type;
|
||||
model.base = definitionType.base;
|
||||
model.template = definitionType.template;
|
||||
model.imports.push(...definitionType.imports);
|
||||
return model;
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
60
src/openApi/v3/parser/getModelProperties.ts
Normal file
60
src/openApi/v3/parser/getModelProperties.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
|
||||
import { getComment } from './getComment';
|
||||
import { getType } from './getType';
|
||||
import { Model } from '../../../client/interfaces/Model';
|
||||
import { getModel } from './getModel';
|
||||
|
||||
export function getModelProperties(openApi: OpenApi, definition: OpenApiSchema): 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 propertyReadOnly = !!property.readOnly;
|
||||
if (property.$ref) {
|
||||
const model = getType(property.$ref);
|
||||
models.push({
|
||||
name: propertyName,
|
||||
export: 'reference',
|
||||
type: model.type,
|
||||
base: model.base,
|
||||
template: model.template,
|
||||
link: null,
|
||||
description: getComment(property.description),
|
||||
isProperty: true,
|
||||
isReadOnly: propertyReadOnly,
|
||||
isRequired: propertyRequired,
|
||||
isNullable: false,
|
||||
imports: model.imports,
|
||||
extends: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
});
|
||||
} else {
|
||||
const model = getModel(openApi, property);
|
||||
models.push({
|
||||
name: propertyName,
|
||||
export: model.export,
|
||||
type: model.type,
|
||||
base: model.base,
|
||||
template: model.template,
|
||||
link: model.link,
|
||||
description: getComment(property.description),
|
||||
isProperty: true,
|
||||
isReadOnly: propertyReadOnly,
|
||||
isRequired: propertyRequired,
|
||||
isNullable: false,
|
||||
imports: model.imports,
|
||||
extends: model.extends,
|
||||
enum: model.enum,
|
||||
enums: model.enums,
|
||||
properties: model.properties,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return models;
|
||||
}
|
||||
23
src/openApi/v3/parser/getModelTemplate.spec.ts
Normal file
23
src/openApi/v3/parser/getModelTemplate.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { getModelTemplate } from './getModelTemplate';
|
||||
|
||||
describe('getModelTemplate', () => {
|
||||
it('should return generic for template type', () => {
|
||||
const template = getModelTemplate({
|
||||
type: 'Link<Model>',
|
||||
base: 'Link',
|
||||
template: 'Model',
|
||||
imports: ['Model'],
|
||||
});
|
||||
expect(template).toEqual('<T>');
|
||||
});
|
||||
|
||||
it('should return empty for primary type', () => {
|
||||
const template = getModelTemplate({
|
||||
type: 'string',
|
||||
base: 'string',
|
||||
template: null,
|
||||
imports: [],
|
||||
});
|
||||
expect(template).toEqual('');
|
||||
});
|
||||
});
|
||||
11
src/openApi/v3/parser/getModelTemplate.ts
Normal file
11
src/openApi/v3/parser/getModelTemplate.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Type } from '../../../client/interfaces/Type';
|
||||
|
||||
/**
|
||||
* If our model has a template type, then we want to generalize that!
|
||||
* In that case we should return "<T>" as our template type.
|
||||
* @param modelClass The parsed model class type.
|
||||
* @returns The model template type (<T> or empty).
|
||||
*/
|
||||
export function getModelTemplate(modelClass: Type): string {
|
||||
return modelClass.template ? '<T>' : '';
|
||||
}
|
||||
@ -1,11 +1,19 @@
|
||||
import { Model } from '../../../client/interfaces/Model';
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import {Model} from '../../../client/interfaces/Model';
|
||||
import {OpenApi} from '../interfaces/OpenApi';
|
||||
import {getModel} from './getModel';
|
||||
import {getType} from './getType';
|
||||
|
||||
/**
|
||||
* Parse and return the OpenAPI models.
|
||||
* @param openApi
|
||||
*/
|
||||
export function getModels(openApi: OpenApi): Map<string, Model> {
|
||||
const models = new Map<string, Model>();
|
||||
if (openApi.components) {
|
||||
for (const definitionName in openApi.components.schemas) {
|
||||
if (openApi.components.schemas.hasOwnProperty(definitionName)) {
|
||||
const definition = openApi.components.schemas[definitionName];
|
||||
const definitionType = getType(definitionName);
|
||||
const model = getModel(openApi, definition, false, definitionType.base);
|
||||
models.set(definitionType.base, model);
|
||||
}
|
||||
}
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
65
src/openApi/v3/parser/getOperation.ts
Normal file
65
src/openApi/v3/parser/getOperation.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Service } from '../../../client/interfaces/Service';
|
||||
import { getServiceClassName } from './getServiceClassName';
|
||||
import { OpenApiOperation } from '../interfaces/OpenApiOperation';
|
||||
import { getOperationName } from './getOperationName';
|
||||
import { getOperationPath } from './getOperationPath';
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { getComment } from './getComment';
|
||||
import { Operation } from '../../../client/interfaces/Operation';
|
||||
import { getOperationParameters } from './getOperationParameters';
|
||||
import { getOperationResponses } from './getOperationResponses';
|
||||
import { getOperationResults } from './getOperationResults';
|
||||
import { getOperationErrors } from './getOperationErrors';
|
||||
|
||||
export function getOperation(openApi: OpenApi, url: string, method: string, op: OpenApiOperation): Operation {
|
||||
const serviceName = (op.tags && op.tags[0]) || 'Service';
|
||||
const serviceClassName = getServiceClassName(serviceName);
|
||||
const operationNameFallback = `${method}${serviceClassName}`;
|
||||
const operationName = getOperationName(op.operationId || operationNameFallback);
|
||||
const operationPath = getOperationPath(url);
|
||||
|
||||
// Create a new operation object for this method.
|
||||
const operation: Operation = {
|
||||
service: serviceClassName,
|
||||
name: operationName,
|
||||
summary: getComment(op.summary),
|
||||
description: getComment(op.description),
|
||||
deprecated: op.deprecated || false,
|
||||
method: method,
|
||||
path: operationPath,
|
||||
parameters: [],
|
||||
parametersPath: [],
|
||||
parametersQuery: [],
|
||||
parametersForm: [],
|
||||
parametersHeader: [],
|
||||
parametersBody: null,
|
||||
imports: [],
|
||||
errors: [],
|
||||
results: [],
|
||||
};
|
||||
|
||||
// Parse the operation parameters (path, query, body, etc).
|
||||
if (op.parameters) {
|
||||
const parameters = getOperationParameters(openApi, op.parameters);
|
||||
operation.imports.push(...parameters.imports);
|
||||
operation.parameters.push(...parameters.parameters);
|
||||
operation.parametersPath.push(...parameters.parametersPath);
|
||||
operation.parametersQuery.push(...parameters.parametersQuery);
|
||||
operation.parametersForm.push(...parameters.parametersForm);
|
||||
operation.parametersHeader.push(...parameters.parametersHeader);
|
||||
operation.parametersBody = parameters.parametersBody;
|
||||
}
|
||||
|
||||
// Parse the operation responses.
|
||||
if (op.responses) {
|
||||
const operationResponses = getOperationResponses(openApi, op.responses);
|
||||
const operationResults = getOperationResults(operationResponses);
|
||||
operation.errors = getOperationErrors(operationResponses);
|
||||
operationResults.forEach(operationResult => {
|
||||
operation.results.push(operationResult);
|
||||
operation.imports.push(...operationResult.imports);
|
||||
});
|
||||
}
|
||||
|
||||
return operation;
|
||||
}
|
||||
13
src/openApi/v3/parser/getOperationErrors.ts
Normal file
13
src/openApi/v3/parser/getOperationErrors.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
|
||||
import { OperationError } from '../../../client/interfaces/OperationError';
|
||||
|
||||
export function getOperationErrors(operationResponses: OperationResponse[]): OperationError[] {
|
||||
return operationResponses
|
||||
.filter(operationResponse => {
|
||||
return operationResponse.code >= 300 && operationResponse.description;
|
||||
})
|
||||
.map(response => ({
|
||||
code: response.code,
|
||||
description: response.description!,
|
||||
}));
|
||||
}
|
||||
10
src/openApi/v3/parser/getOperationName.spec.ts
Normal file
10
src/openApi/v3/parser/getOperationName.spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { getOperationName } from './getOperationName';
|
||||
|
||||
describe('getOperationName', () => {
|
||||
it('should produce correct result', () => {
|
||||
expect(getOperationName('')).toEqual('');
|
||||
expect(getOperationName('FooBar')).toEqual('fooBar');
|
||||
expect(getOperationName('Foo Bar')).toEqual('fooBar');
|
||||
expect(getOperationName('foo bar')).toEqual('fooBar');
|
||||
});
|
||||
});
|
||||
11
src/openApi/v3/parser/getOperationName.ts
Normal file
11
src/openApi/v3/parser/getOperationName.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import camelCase from 'camelcase';
|
||||
|
||||
/**
|
||||
* Convert the input value to a correct operation (method) classname.
|
||||
* This converts the input string to camelCase, so the method name follows
|
||||
* the most popular Javascript and Typescript writing style.
|
||||
*/
|
||||
export function getOperationName(value: string): string {
|
||||
const clean = value.replace(/[^\w\s\-]+/g, '_').trim();
|
||||
return camelCase(clean);
|
||||
}
|
||||
59
src/openApi/v3/parser/getOperationParameter.ts
Normal file
59
src/openApi/v3/parser/getOperationParameter.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { OpenApiParameter } from '../interfaces/OpenApiParameter';
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { getComment } from './getComment';
|
||||
import { getOperationParameterName } from './getOperationParameterName';
|
||||
import { OperationParameter } from '../../../client/interfaces/OperationParameter';
|
||||
import { PrimaryType } from './constants';
|
||||
import { getType } from './getType';
|
||||
import { getModel } from './getModel';
|
||||
|
||||
export function getOperationParameter(openApi: OpenApi, parameter: OpenApiParameter): OperationParameter {
|
||||
const operationParameter: OperationParameter = {
|
||||
in: parameter.in,
|
||||
prop: parameter.name,
|
||||
export: 'interface',
|
||||
name: getOperationParameterName(parameter.name),
|
||||
type: PrimaryType.OBJECT,
|
||||
base: PrimaryType.OBJECT,
|
||||
template: null,
|
||||
link: null,
|
||||
description: getComment(parameter.description),
|
||||
default: undefined,
|
||||
isProperty: false,
|
||||
isReadOnly: false,
|
||||
isRequired: parameter.required || false,
|
||||
isNullable: false,
|
||||
imports: [],
|
||||
extends: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
};
|
||||
|
||||
if (parameter.schema) {
|
||||
if (parameter.schema.$ref) {
|
||||
const model = getType(parameter.schema.$ref);
|
||||
operationParameter.export = 'reference';
|
||||
operationParameter.type = model.type;
|
||||
operationParameter.base = model.base;
|
||||
operationParameter.template = model.template;
|
||||
operationParameter.imports.push(...model.imports);
|
||||
return operationParameter;
|
||||
} else {
|
||||
const model = getModel(openApi, parameter.schema);
|
||||
operationParameter.export = model.export;
|
||||
operationParameter.type = model.type;
|
||||
operationParameter.base = model.base;
|
||||
operationParameter.template = model.template;
|
||||
operationParameter.link = model.link;
|
||||
operationParameter.imports.push(...model.imports);
|
||||
operationParameter.extends.push(...model.extends);
|
||||
operationParameter.enum.push(...model.enum);
|
||||
operationParameter.enums.push(...model.enums);
|
||||
operationParameter.properties.push(...model.properties);
|
||||
return operationParameter;
|
||||
}
|
||||
}
|
||||
|
||||
return operationParameter;
|
||||
}
|
||||
15
src/openApi/v3/parser/getOperationParameterDefault.ts
Normal file
15
src/openApi/v3/parser/getOperationParameterDefault.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export function getOperationParameterDefault(value: any): string | null {
|
||||
if (value === null) {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
switch (typeof value) {
|
||||
case 'number':
|
||||
case 'boolean':
|
||||
return JSON.stringify(value);
|
||||
case 'string':
|
||||
return `'${value}'`;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
10
src/openApi/v3/parser/getOperationParameterName.ts
Normal file
10
src/openApi/v3/parser/getOperationParameterName.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import camelCase from 'camelcase';
|
||||
|
||||
/**
|
||||
* Replaces any invalid characters from a parameter name.
|
||||
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
|
||||
*/
|
||||
export function getOperationParameterName(value: string): string {
|
||||
const clean = value.replace(/[^\w\s\-]+/g, '_').trim();
|
||||
return camelCase(clean);
|
||||
}
|
||||
59
src/openApi/v3/parser/getOperationParameters.ts
Normal file
59
src/openApi/v3/parser/getOperationParameters.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { OpenApiParameter } from '../interfaces/OpenApiParameter';
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { getRef } from './getRef';
|
||||
import { OperationParameters } from '../../../client/interfaces/OperationParameters';
|
||||
import { OperationParameter } from '../../../client/interfaces/OperationParameter';
|
||||
import { getOperationParameter } from './getOperationParameter';
|
||||
|
||||
function sortByRequired(a: OperationParameter, b: OperationParameter): number {
|
||||
return a.isRequired && !b.isRequired ? -1 : !a.isRequired && b.isRequired ? 1 : 0;
|
||||
}
|
||||
|
||||
export function getOperationParameters(openApi: OpenApi, parameters: OpenApiParameter[]): OperationParameters {
|
||||
const operationParameters: OperationParameters = {
|
||||
imports: [],
|
||||
parameters: [],
|
||||
parametersPath: [],
|
||||
parametersQuery: [],
|
||||
parametersForm: [],
|
||||
parametersHeader: [],
|
||||
parametersBody: null,
|
||||
};
|
||||
|
||||
// Iterate over the parameters
|
||||
parameters.forEach(parameter => {
|
||||
const paramRef = getRef<OpenApiParameter>(openApi, parameter);
|
||||
const param = getOperationParameter(openApi, paramRef);
|
||||
|
||||
// We ignore the "api-version" param, since we do not want to add this
|
||||
// as the first / default parameter for each of the service calls.
|
||||
if (param.prop !== 'api-version') {
|
||||
switch (parameter.in) {
|
||||
case 'path':
|
||||
operationParameters.parametersPath.push(param);
|
||||
operationParameters.parameters.push(param);
|
||||
operationParameters.imports.push(...param.imports);
|
||||
break;
|
||||
|
||||
case 'query':
|
||||
operationParameters.parametersQuery.push(param);
|
||||
operationParameters.parameters.push(param);
|
||||
operationParameters.imports.push(...param.imports);
|
||||
break;
|
||||
|
||||
case 'header':
|
||||
operationParameters.parametersHeader.push(param);
|
||||
operationParameters.parameters.push(param);
|
||||
operationParameters.imports.push(...param.imports);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
operationParameters.parameters = operationParameters.parameters.sort(sortByRequired);
|
||||
operationParameters.parametersPath = operationParameters.parametersPath.sort(sortByRequired);
|
||||
operationParameters.parametersQuery = operationParameters.parametersQuery.sort(sortByRequired);
|
||||
operationParameters.parametersForm = operationParameters.parametersForm.sort(sortByRequired);
|
||||
operationParameters.parametersHeader = operationParameters.parametersHeader.sort(sortByRequired);
|
||||
return operationParameters;
|
||||
}
|
||||
10
src/openApi/v3/parser/getOperationPath.spec.ts
Normal file
10
src/openApi/v3/parser/getOperationPath.spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
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}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
|
||||
expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
|
||||
expect(getOperationPath('/api/v1/list')).toEqual('/api/v1/list');
|
||||
});
|
||||
});
|
||||
9
src/openApi/v3/parser/getOperationPath.ts
Normal file
9
src/openApi/v3/parser/getOperationPath.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Get the final service path, this replaces the "{api-version}" placeholder
|
||||
* with a new template string placeholder so we can dynamically inject the
|
||||
* OpenAPI version without the need to hardcode this in the URL.
|
||||
* @param path
|
||||
*/
|
||||
export function getOperationPath(path: string): string {
|
||||
return path.replace(/{api-version}/g, '{OpenAPI.VERSION}').replace(/\{(.*?)\}/g, '${$1}');
|
||||
}
|
||||
16
src/openApi/v3/parser/getOperationResponseCode.ts
Normal file
16
src/openApi/v3/parser/getOperationResponseCode.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export function getOperationResponseCode(value: string | 'default'): number | null {
|
||||
// You can specify a "default" response, this is treated as HTTP code 200
|
||||
if (value === 'default') {
|
||||
return 200;
|
||||
}
|
||||
|
||||
// Check if we can parse the code and return of successful.
|
||||
if (/[0-9]+/g.test(value)) {
|
||||
const code = parseInt(value);
|
||||
if (Number.isInteger(code)) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
86
src/openApi/v3/parser/getOperationResponses.ts
Normal file
86
src/openApi/v3/parser/getOperationResponses.ts
Normal file
@ -0,0 +1,86 @@
|
||||
import { OpenApiResponses } from '../interfaces/OpenApiResponses';
|
||||
import { getOperationResponseCode } from './getOperationResponseCode';
|
||||
import { OpenApiResponse } from '../interfaces/OpenApiResponse';
|
||||
import { getRef } from './getRef';
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
|
||||
import { getType } from './getType';
|
||||
import { getModel } from './getModel';
|
||||
import { getComment } from './getComment';
|
||||
import { PrimaryType } from './constants';
|
||||
|
||||
export function getOperationResponses(openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] {
|
||||
const operationResponses: OperationResponse[] = [];
|
||||
|
||||
// Iterate over each response code and get the
|
||||
// status code and response message (if any).
|
||||
for (const code in responses) {
|
||||
if (responses.hasOwnProperty(code)) {
|
||||
const responseOrReference = responses[code];
|
||||
const response = getRef<OpenApiResponse>(openApi, responseOrReference);
|
||||
const responseCode = getOperationResponseCode(code);
|
||||
|
||||
// If there is a response code then we check what data we get back,
|
||||
// if there is no typed data, we just return <any> so the user is still
|
||||
// free to do their own casting if needed.
|
||||
if (responseCode) {
|
||||
const operationResponse: OperationResponse = {
|
||||
name: '',
|
||||
code: responseCode,
|
||||
description: getComment(response.description)!,
|
||||
export: 'generic',
|
||||
type: PrimaryType.OBJECT,
|
||||
base: PrimaryType.OBJECT,
|
||||
template: null,
|
||||
link: null,
|
||||
isProperty: false,
|
||||
isReadOnly: false,
|
||||
isRequired: false,
|
||||
isNullable: false,
|
||||
imports: [],
|
||||
extends: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
};
|
||||
|
||||
// If this response has a schema, then we need to check two things:
|
||||
// if this is a reference then the parameter is just the 'name' of
|
||||
// this reference type. Otherwise it might be a complex schema and
|
||||
// then we need to parse the schema!
|
||||
|
||||
// TODO: Needs content!
|
||||
|
||||
// if (response.schema) {
|
||||
// if (response.schema.$ref) {
|
||||
// const model = getType(response.schema.$ref);
|
||||
// operationResponse.export = 'reference';
|
||||
// operationResponse.type = model.type;
|
||||
// operationResponse.base = model.base;
|
||||
// operationResponse.template = model.template;
|
||||
// operationResponse.imports.push(...model.imports);
|
||||
// } else {
|
||||
// const model = getModel(openApi, response.schema);
|
||||
// operationResponse.export = model.export;
|
||||
// operationResponse.type = model.type;
|
||||
// operationResponse.base = model.base;
|
||||
// operationResponse.template = model.template;
|
||||
// operationResponse.link = model.link;
|
||||
// operationResponse.imports.push(...model.imports);
|
||||
// operationResponse.extends.push(...model.extends);
|
||||
// operationResponse.enum.push(...model.enum);
|
||||
// operationResponse.enums.push(...model.enums);
|
||||
// operationResponse.properties.push(...model.properties);
|
||||
// }
|
||||
// }
|
||||
|
||||
operationResponses.push(operationResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the responses to 2XX success codes come before 4XX and 5XX error codes.
|
||||
return operationResponses.sort((a, b): number => {
|
||||
return a.code < b.code ? -1 : a.code > b.code ? 1 : 0;
|
||||
});
|
||||
}
|
||||
51
src/openApi/v3/parser/getOperationResults.ts
Normal file
51
src/openApi/v3/parser/getOperationResults.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import { PrimaryType } from './constants';
|
||||
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
|
||||
import { Model } from '../../../client/interfaces/Model';
|
||||
|
||||
function areEqual(a: Model, b: Model): boolean {
|
||||
const equal = a.type === b.type && a.base === b.base && a.template === b.template;
|
||||
if (equal && a.link && b.link) {
|
||||
return areEqual(a.link, b.link);
|
||||
}
|
||||
return equal;
|
||||
}
|
||||
|
||||
export function getOperationResults(operationResponses: OperationResponse[]): OperationResponse[] {
|
||||
const operationResults: OperationResponse[] = [];
|
||||
|
||||
operationResponses.forEach(operationResponse => {
|
||||
if (operationResponse.code && operationResponse.code >= 200 && operationResponse.code < 300) {
|
||||
operationResults.push(operationResponse);
|
||||
}
|
||||
});
|
||||
|
||||
if (!operationResults.length) {
|
||||
operationResults.push({
|
||||
name: '',
|
||||
code: 200,
|
||||
description: '',
|
||||
export: 'interface',
|
||||
type: PrimaryType.OBJECT,
|
||||
base: PrimaryType.OBJECT,
|
||||
template: null,
|
||||
link: null,
|
||||
isProperty: false,
|
||||
isReadOnly: false,
|
||||
isRequired: false,
|
||||
isNullable: false,
|
||||
imports: [],
|
||||
extends: [],
|
||||
enum: [],
|
||||
enums: [],
|
||||
properties: [],
|
||||
});
|
||||
}
|
||||
|
||||
return operationResults.filter((operationResult, index, arr) => {
|
||||
return (
|
||||
arr.findIndex(item => {
|
||||
return areEqual(item, operationResult);
|
||||
}) === index
|
||||
);
|
||||
});
|
||||
}
|
||||
26
src/openApi/v3/parser/getRef.ts
Normal file
26
src/openApi/v3/parser/getRef.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { OpenApiReference } from '../interfaces/OpenApiReference';
|
||||
|
||||
export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
|
||||
if (item.$ref) {
|
||||
// Fetch the paths to the definitions, this converts:
|
||||
// "#/definitions/Form" to ["definitions", "Form"]
|
||||
const paths = item.$ref
|
||||
.replace(/^#/g, '')
|
||||
.split('/')
|
||||
.filter(item => item);
|
||||
|
||||
// Try to find the reference by walking down the path,
|
||||
// if we cannot find it, then we throw an error.
|
||||
let result: any = openApi;
|
||||
paths.forEach((path: string): void => {
|
||||
if (result.hasOwnProperty(path)) {
|
||||
result = result[path];
|
||||
} else {
|
||||
throw new Error(`Could not find reference: "${item.$ref}"`);
|
||||
}
|
||||
});
|
||||
return result as T;
|
||||
}
|
||||
return item as T;
|
||||
}
|
||||
13
src/openApi/v3/parser/getServiceClassName.spec.ts
Normal file
13
src/openApi/v3/parser/getServiceClassName.spec.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { getServiceClassName } from './getServiceClassName';
|
||||
|
||||
describe('getServiceClassName', () => {
|
||||
it('should produce correct result', () => {
|
||||
expect(getServiceClassName('')).toEqual('');
|
||||
expect(getServiceClassName('FooBar')).toEqual('FooBarService');
|
||||
expect(getServiceClassName('Foo Bar')).toEqual('FooBarService');
|
||||
expect(getServiceClassName('foo bar')).toEqual('FooBarService');
|
||||
expect(getServiceClassName('FooBarService')).toEqual('FooBarService');
|
||||
expect(getServiceClassName('Foo Bar Service')).toEqual('FooBarService');
|
||||
expect(getServiceClassName('foo bar service')).toEqual('FooBarService');
|
||||
});
|
||||
});
|
||||
14
src/openApi/v3/parser/getServiceClassName.ts
Normal file
14
src/openApi/v3/parser/getServiceClassName.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import camelCase from 'camelcase';
|
||||
|
||||
/**
|
||||
* Convert the input value to a correct service classname. This converts
|
||||
* the input string to PascalCase and appends the "Service" prefix if needed.
|
||||
*/
|
||||
export function getServiceClassName(value: string): string {
|
||||
const clean = value.replace(/[^\w\s\-]+/g, '_').trim();
|
||||
const name = camelCase(clean, { pascalCase: true });
|
||||
if (name && !name.endsWith('Service')) {
|
||||
return `${name}Service`;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
@ -1,11 +1,49 @@
|
||||
import { Service } from '../../../client/interfaces/Service';
|
||||
import { OpenApi } from '../interfaces/OpenApi';
|
||||
import { Method } from './constants';
|
||||
import { getOperation } from './getOperation';
|
||||
|
||||
/**
|
||||
* Parse and return the OpenAPI services.
|
||||
* @param openApi
|
||||
* Get the OpenAPI services
|
||||
*/
|
||||
export function getServices(openApi: OpenApi): Map<string, Service> {
|
||||
const services = new Map<string, Service>();
|
||||
for (const url in openApi.paths) {
|
||||
if (openApi.paths.hasOwnProperty(url)) {
|
||||
const path = openApi.paths[url];
|
||||
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:
|
||||
// Each method contains an OpenAPI operation, we parse the operation
|
||||
const op = path[method]!;
|
||||
const operation = getOperation(openApi, url, method, op);
|
||||
|
||||
// If we have already declared a service, then we should fetch that and
|
||||
// append the new method to it. Otherwise we should create a new service object.
|
||||
const service =
|
||||
services.get(operation.service) ||
|
||||
({
|
||||
name: operation.service,
|
||||
operations: [],
|
||||
imports: [],
|
||||
} as Service);
|
||||
|
||||
// Push the operation in the service
|
||||
service.operations.push(operation);
|
||||
service.imports.push(...operation.imports);
|
||||
services.set(operation.service, service);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return services;
|
||||
}
|
||||
|
||||
59
src/openApi/v3/parser/getType.spec.ts
Normal file
59
src/openApi/v3/parser/getType.spec.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { getType } from './getType';
|
||||
|
||||
describe('getType', () => {
|
||||
it('should convert int', () => {
|
||||
const type = getType('int');
|
||||
expect(type.type).toEqual('number');
|
||||
expect(type.base).toEqual('number');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
});
|
||||
|
||||
it('should convert string', () => {
|
||||
const type = getType('String');
|
||||
expect(type.type).toEqual('string');
|
||||
expect(type.base).toEqual('string');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
});
|
||||
|
||||
it('should convert string array', () => {
|
||||
const type = getType('Array[String]');
|
||||
expect(type.type).toEqual('Array<string>');
|
||||
expect(type.base).toEqual('Array');
|
||||
expect(type.template).toEqual('string');
|
||||
expect(type.imports).toEqual(['Array']);
|
||||
});
|
||||
|
||||
it('should convert template with primary', () => {
|
||||
const type = getType('#/definitions/Link[String]');
|
||||
expect(type.type).toEqual('Link<string>');
|
||||
expect(type.base).toEqual('Link');
|
||||
expect(type.template).toEqual('string');
|
||||
expect(type.imports).toEqual(['Link']);
|
||||
});
|
||||
|
||||
it('should convert template with model', () => {
|
||||
const type = getType('#/definitions/Link[Model]');
|
||||
expect(type.type).toEqual('Link<Model>');
|
||||
expect(type.base).toEqual('Link');
|
||||
expect(type.template).toEqual('Model');
|
||||
expect(type.imports).toEqual(['Link', 'Model']);
|
||||
});
|
||||
|
||||
it('should have double imports', () => {
|
||||
const type = getType('#/definitions/Link[Link]');
|
||||
expect(type.type).toEqual('Link<Link>');
|
||||
expect(type.base).toEqual('Link');
|
||||
expect(type.template).toEqual('Link');
|
||||
expect(type.imports).toEqual(['Link', 'Link']);
|
||||
});
|
||||
|
||||
it('should convert generic', () => {
|
||||
const type = getType('#/definitions/Link', 'Link');
|
||||
expect(type.type).toEqual('T');
|
||||
expect(type.base).toEqual('T');
|
||||
expect(type.template).toEqual(null);
|
||||
expect(type.imports).toEqual([]);
|
||||
});
|
||||
});
|
||||
59
src/openApi/v3/parser/getType.ts
Normal file
59
src/openApi/v3/parser/getType.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { stripNamespace } from './stripNamespace';
|
||||
import { Type } from '../../../client/interfaces/Type';
|
||||
import { getMappedType, hasMappedType } from './getMappedType';
|
||||
import { PrimaryType } from './constants';
|
||||
|
||||
/**
|
||||
* Parse any string value into a type object.
|
||||
* @param value String value like "integer" or "Link[Model]".
|
||||
* @param template Optional template class from parent (needed to process generics)
|
||||
*/
|
||||
export function getType(value?: string, template?: string): Type {
|
||||
const result: Type = {
|
||||
type: PrimaryType.OBJECT,
|
||||
base: PrimaryType.OBJECT,
|
||||
template: null,
|
||||
imports: [],
|
||||
};
|
||||
|
||||
const valueClean = stripNamespace(value || '');
|
||||
|
||||
if (/\[.*\]$/g.test(valueClean)) {
|
||||
const matches = valueClean.match(/(.*?)\[(.*)\]$/);
|
||||
if (matches && matches.length) {
|
||||
const match1 = getType(matches[1]);
|
||||
const match2 = getType(matches[2]);
|
||||
|
||||
if (match2.type) {
|
||||
result.type = `${match1.type}<${match2.type}>`;
|
||||
result.base = match1.type;
|
||||
result.template = match2.type;
|
||||
} else {
|
||||
result.type = match1.type;
|
||||
result.base = match1.type;
|
||||
result.template = match1.type;
|
||||
}
|
||||
|
||||
result.imports.push(...match1.imports);
|
||||
result.imports.push(...match2.imports);
|
||||
}
|
||||
} else if (hasMappedType(valueClean)) {
|
||||
const mapped = getMappedType(valueClean);
|
||||
result.type = mapped;
|
||||
result.base = mapped;
|
||||
} else if (valueClean) {
|
||||
result.type = valueClean;
|
||||
result.base = valueClean;
|
||||
result.imports.push(valueClean);
|
||||
}
|
||||
|
||||
// If the property that we found matched the parent template class
|
||||
// Then ignore this whole property and return it as a "T" template property.
|
||||
if (result.type === template) {
|
||||
result.type = 'T'; // Template;
|
||||
result.base = 'T'; // Template;
|
||||
result.imports = [];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
14
src/openApi/v3/parser/isPrimaryType.spec.ts
Normal file
14
src/openApi/v3/parser/isPrimaryType.spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { isPrimaryType } from './isPrimaryType';
|
||||
|
||||
describe('isPrimaryType', () => {
|
||||
it('should return true for primary types', () => {
|
||||
expect(isPrimaryType('number')).toBeTruthy();
|
||||
expect(isPrimaryType('boolean')).toBeTruthy();
|
||||
expect(isPrimaryType('string')).toBeTruthy();
|
||||
expect(isPrimaryType('any')).toBeTruthy();
|
||||
expect(isPrimaryType('void')).toBeTruthy();
|
||||
expect(isPrimaryType('null')).toBeTruthy();
|
||||
expect(isPrimaryType('Array')).toBeFalsy();
|
||||
expect(isPrimaryType('MyModel')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
19
src/openApi/v3/parser/isPrimaryType.ts
Normal file
19
src/openApi/v3/parser/isPrimaryType.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { PrimaryType } from './constants';
|
||||
|
||||
/**
|
||||
* Check if given type is a primary type.
|
||||
* @param type
|
||||
*/
|
||||
export function isPrimaryType(type: string): type is PrimaryType {
|
||||
switch (type.toLowerCase()) {
|
||||
case PrimaryType.FILE:
|
||||
case PrimaryType.OBJECT:
|
||||
case PrimaryType.BOOLEAN:
|
||||
case PrimaryType.NUMBER:
|
||||
case PrimaryType.STRING:
|
||||
case PrimaryType.VOID:
|
||||
case PrimaryType.NULL:
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -7,7 +7,7 @@
|
||||
{{#if properties}}
|
||||
yup.object{{#unless isProperty}}{{#if name}}<{{{name}}}>{{/if}}{{/unless}}().shape({
|
||||
{{#each properties}}
|
||||
{{{name}}}: yup.lazy(() => {{>validation}}.default(undefined){{#if isNullable}}.isNullable(){{/if}}){{#if isRequired}}.isRequired(){{/if}}{{#unless @last}},{{/unless}}
|
||||
{{{name}}}: yup.lazy(() => {{>validation}}.default(undefined){{#if isNullable}}.nullable(){{/if}}{{#if isRequired}}.required(){{/if}}){{#unless @last}},{{/unless}}
|
||||
{{/each}}
|
||||
}).noUnknown()
|
||||
{{else}}
|
||||
|
||||
@ -1,17 +1,24 @@
|
||||
const OpenAPI = require('../dist');
|
||||
|
||||
OpenAPI.generate(
|
||||
'./test/mock/spec-v2.json',
|
||||
'./test/result/v2/typescript/',
|
||||
'./test/mock/spec-v3.json',
|
||||
'./test/result/v3/typescript/',
|
||||
OpenAPI.Language.TYPESCRIPT,
|
||||
OpenAPI.HttpClient.FETCH,
|
||||
);
|
||||
|
||||
OpenAPI.generate(
|
||||
'./test/mock/spec-v2.json',
|
||||
'./test/result/v2/javascript/',
|
||||
OpenAPI.Language.JAVASCRIPT,
|
||||
OpenAPI.HttpClient.XHR,
|
||||
);
|
||||
// OpenAPI.generate(
|
||||
// './test/mock/spec-v2.json',
|
||||
// './test/result/v2/typescript/',
|
||||
// OpenAPI.Language.TYPESCRIPT,
|
||||
// OpenAPI.HttpClient.FETCH,
|
||||
// );
|
||||
|
||||
OpenAPI.compile('./test/result/v2/typescript/');
|
||||
// OpenAPI.generate(
|
||||
// './test/mock/spec-v2.json',
|
||||
// './test/result/v2/javascript/',
|
||||
// OpenAPI.Language.JAVASCRIPT,
|
||||
// OpenAPI.HttpClient.XHR,
|
||||
// );
|
||||
|
||||
// OpenAPI.compile('./test/result/v2/typescript/');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user