- Working on large cleanup

This commit is contained in:
Ferdi Koomen 2019-11-15 10:23:47 +01:00
parent 35d7ac10c8
commit 6e82ff13a8
56 changed files with 252 additions and 543 deletions

View File

@ -1,7 +0,0 @@
export interface ArrayType {
type: string;
base: string;
template: string | null;
default?: any;
imports: string[];
}

View File

@ -4,6 +4,6 @@ import { Service } from './Service';
export interface Client {
version: string;
server: string;
models: Map<string, Model>;
services: Map<string, Service>;
models: Model[];
services: Service[];
}

7
src/client/interfaces/Enum.d.ts vendored Normal file
View File

@ -0,0 +1,7 @@
import { EnumValue } from './EnumValue';
export interface Enum {
name: string;
type: string;
values: EnumValue[];
}

View File

@ -1,4 +1,4 @@
export interface Shape {
export interface EnumValue {
name: string;
value: string;
}

View File

@ -1,5 +1,18 @@
import { Schema } from './Schema';
import { ModelProperty } from './ModelProperty';
import { Enum } from './Enum';
export interface Model extends Schema {
noop?: any;
export interface Model {
isInterface: boolean;
isType: boolean;
isEnum: boolean;
name: string;
type: string;
base: string;
template: string | null;
validation: string | null;
description: string | null;
extends: string | null;
imports: string[];
enums: Enum[];
properties: ModelProperty[];
}

View File

@ -1,7 +0,0 @@
import { ModelEnumValue } from './ModelEnumValue';
export interface ModelEnum {
name: string;
value: string;
values: ModelEnumValue[];
}

View File

@ -1,6 +0,0 @@
export interface ModelEnumValue {
type: string;
name: string;
description?: string;
value: string | number;
}

View File

@ -1,14 +1,9 @@
export interface ModelProperty {
name: string;
type: string;
base: string;
template: string | null;
description?: string;
default?: any;
required: boolean;
nullable: boolean;
readOnly: boolean;
extends: string[];
imports: string[];
properties: ModelProperty[];
validation: string | null;
description: string | null;
}

View File

@ -1,7 +1,7 @@
import { OperationError } from './OperationError';
import { Parameter } from './Parameter';
import { OperationParameters } from './OperationParameters';
export interface Operation {
export interface Operation extends OperationParameters {
service: string;
name: string;
summary?: string;
@ -9,13 +9,6 @@ export interface Operation {
deprecated?: boolean;
method: string;
path: string;
parameters: Parameter[];
parametersPath: Parameter[];
parametersQuery: Parameter[];
parametersForm: Parameter[];
parametersHeader: Parameter[];
parametersBody: Parameter | null;
errors: OperationError[];
result: string;
imports: string[];
}

View File

@ -1,12 +1,12 @@
export interface Parameter {
export interface OperationParameter {
prop: string;
in: 'path' | 'query' | 'header' | 'formData' | 'body';
name: string;
type: string;
base: string;
template: string | null;
description?: string;
default?: any;
description: string | null;
default: any | undefined;
required: boolean;
nullable: boolean;
imports: string[];

View File

@ -1,11 +1,11 @@
import { Parameter } from './Parameter';
import { OperationParameter } from './OperationParameter';
export interface OperationParameters {
imports: string[];
parameters: Parameter[];
parametersPath: Parameter[];
parametersQuery: Parameter[];
parametersForm: Parameter[];
parametersHeader: Parameter[];
parametersBody: Parameter | null;
parameters: OperationParameter[];
parametersPath: OperationParameter[];
parametersQuery: OperationParameter[];
parametersForm: OperationParameter[];
parametersHeader: OperationParameter[];
parametersBody: OperationParameter | null;
}

View File

@ -1,17 +0,0 @@
import { Shape } from './Symbol';
import { SchemaProperty } from './SchemaProperty';
export interface Schema {
isInterface: boolean; // Schema is interface
isType: boolean; // Schema is type
isEnum: boolean; // Schema is enum
name: string;
type: string;
base: string;
template: string | null;
description?: string;
extends: string[];
imports: string[];
symbols: Shape[]; // TODO: Betere naam!
properties: SchemaProperty[];
}

View File

@ -1,8 +0,0 @@
export interface SchemaProperty {
name: string;
type: string;
required: boolean;
nullable: boolean;
readOnly: boolean;
description?: string;
}

View File

@ -1,6 +0,0 @@
export interface SchemaReference {
type: string;
base: string;
template: string | null;
imports: string[];
}

View File

@ -1,6 +1,7 @@
export enum PrimaryType {
FILE = 'File',
OBJECT = 'any',
ARRAY = 'any[]',
BOOLEAN = 'boolean',
NUMBER = 'number',
STRING = 'string',
@ -13,6 +14,8 @@ export const TYPE_MAPPINGS = new Map<string, PrimaryType>([
['binary', PrimaryType.FILE],
['any', PrimaryType.OBJECT],
['object', PrimaryType.OBJECT],
['list', PrimaryType.ARRAY],
['array', PrimaryType.ARRAY],
['boolean', PrimaryType.BOOLEAN],
['byte', PrimaryType.NUMBER],
['int', PrimaryType.NUMBER],

View File

@ -2,7 +2,14 @@ import { getType } from './getType';
import { PrimaryType } from './constants';
import { Type } from '../../../client/interfaces/Type';
import { OpenApiItems } from '../interfaces/OpenApiItems';
import { ArrayType } from '../../../client/interfaces/ArrayType';
export interface ArrayType {
type: string;
base: string;
template: string | null;
default: any | undefined;
imports: string[];
}
export function getArrayType(items: OpenApiItems): ArrayType {
const result: ArrayType = {

View File

@ -1,6 +1,6 @@
import { EOL } from 'os';
export function getComment(comment: string | undefined): string | undefined {
export function getComment(comment: string | undefined): string | null {
if (comment) {
return comment
.split(/(\r\n|\n|\r)+/g)
@ -9,5 +9,5 @@ export function getComment(comment: string | undefined): string | undefined {
.join(EOL)
.replace(/(\r\n|\n|\r)+/g, '$1 * ');
}
return undefined;
return null;
}

View File

@ -1,13 +1,13 @@
import { Shape } from '../../../client/interfaces/Shape';
import { ModelEnumValue } from '../../../client/interfaces/ModelEnumValue';
export function getEnumSymbols(values?: (string | number)[]): Shape[] {
export function getModelEnum(values?: (string | number)[]): ModelEnumValue[] {
if (Array.isArray(values)) {
return values
.filter((value: string | number, index: number, arr: (string | number)[]) => {
return arr.indexOf(value) === index;
})
.map(
(value: string | number): Shape => {
(value: string | number): ModelEnumValue => {
if (typeof value === 'number') {
return {
name: `NUM_${value}`,

View File

@ -1,13 +1,13 @@
import { Shape } from '../../../client/interfaces/Shape';
import { ModelEnumValue } from '../../../client/interfaces/ModelEnumValue';
export function getEnumSymbolsFromDescription(description: string): Shape[] {
export function getModelEnumFromDescription(description: string): ModelEnumValue[] {
// Check if we can find this special format string:
// None=0,Something=1,AnotherThing=2
if (/^(\w+=[0-9]+,?)+$/g.test(description)) {
const matches: RegExpMatchArray | null = description.match(/(\w+=[0-9]+,?)/g);
if (matches) {
// Grab the values from the description
const symbols: Shape[] = [];
const symbols: ModelEnumValue[] = [];
matches.forEach((match: string): void => {
const name: string = match.split('=')[0];
const value: number = parseInt(match.split('=')[1].replace(/[^0-9]/g, ''));
@ -20,7 +20,7 @@ export function getEnumSymbolsFromDescription(description: string): Shape[] {
});
// Filter out any duplicate names
return symbols.filter((symbol: Shape, index: number, arr: Shape[]): boolean => {
return symbols.filter((symbol: ModelEnumValue, index: number, arr: ModelEnumValue[]): boolean => {
return arr.map(item => item.name).indexOf(symbol.name) === index;
});
}

View File

@ -1,15 +1,8 @@
import { Shape } from '../../../client/interfaces/Shape';
export function getEnumType(symbols: Shape[], addParentheses = false): string {
export function getEnumType(symbols: ModelSymbol[], addParentheses = 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 entries: string[] = symbols
.map(symbol => symbol.value)
.filter((value: string, index: number, arr: string[]): boolean => {
return arr.indexOf(value) === index;
})
.sort();
const entries: string[] = getEnumValues(symbols);
// Add grouping parentheses if needed. This can be handy if enum values
// are used in Arrays, so that you will get the following definition:

View File

@ -0,0 +1,11 @@
export function getEnumValues(symbols: ModelSymbol[]): 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 symbols
.map(symbol => symbol.value)
.filter((value: string, index: number, arr: string[]): boolean => {
return arr.indexOf(value) === index;
})
.sort();
}

View File

@ -1,54 +1,54 @@
import { OpenApi } from '../interfaces/OpenApi';
import { Schema } from '../../../client/interfaces/Schema';
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { getComment } from './getComment';
import { getType } from './getType';
import { Type } from '../../../client/interfaces/Type';
import { getEnumSymbolsFromDescription } from './getEnumSymbolsFromDescription';
import { getEnumType } from './getEnumType';
import { getEnumSymbols } from './getEnumSymbols';
import { PrimaryType } from './constants';
import { Shape } from '../../../client/interfaces/Shape';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { getRef } from './getRef';
import { getSchemaType } from './getSchemaType';
import { getEnumValues } from './getEnumValues';
import { Model } from '../../../client/interfaces/Model';
export function getSchema(openApi: OpenApi, schema: OpenApiSchema, name: string): Schema {
const result: Schema = {
name, // TODO: kan weg zie maar waar deze gebruikt is, of kan model naam worden
export function getModel(openApi: OpenApi, schema: OpenApiSchema, name: string): Model {
const result: Model = {
name,
isInterface: false,
isType: false,
isEnum: false,
type: 'any',
base: 'any',
template: null,
validation: null,
description: getComment(schema.description),
extends: [],
extends: null,
imports: [],
symbols: [],
enums: [],
properties: [],
};
// If the param is a enum then return the values as an inline type.
if (schema.enum) {
const enumSymbols: Shape[] = getEnumSymbols(schema.enum);
const enumSymbols: ModelSymbol[] = getEnumSymbols(schema.enum);
if (enumSymbols.length) {
result.isEnum = true;
result.symbols = enumSymbols;
result.type = getEnumType(result.symbols);
result.type = getEnumType(enumSymbols);
result.base = PrimaryType.STRING;
result.validation = `yup.mixed<${name}>().oneOf([${getEnumValues(enumSymbols).join(', ')}])`;
return result;
}
}
// If the param is a enum then return the values as an inline type.
if (schema.type === 'int' && schema.description) {
const enumSymbols: Shape[] = getEnumSymbolsFromDescription(schema.description);
const enumSymbols: ModelSymbol[] = getEnumSymbolsFromDescription(schema.description);
if (enumSymbols.length) {
result.isEnum = true;
result.symbols = enumSymbols;
result.type = getEnumType(enumSymbols);
result.base = PrimaryType.NUMBER;
result.validation = `yup.mixed<${name}>().oneOf([${getEnumValues(enumSymbols).join(', ')}])`;
return result;
}
}
@ -56,9 +56,6 @@ export function getSchema(openApi: OpenApi, schema: OpenApiSchema, name: string)
// If the schema is an Array type, we check for the child type,
// so we can create a typed array, otherwise this will be a "any[]".
if (schema.type === 'array' && schema.items) {
// TODO: Ik gok dat we straks een lineare parser overhouden die in 1x een bestand kan wegschrijven
// TODO: in plaats van losse tussen stappen, dat maakt het testen ook makkelijker omdat je direct een
// TODO: typescript bestand / output kan valideren?
if (schema.items.$ref) {
const arrayType: Type = getType(schema.items.$ref);
result.imports.push(...arrayType.imports);
@ -66,6 +63,7 @@ export function getSchema(openApi: OpenApi, schema: OpenApiSchema, name: string)
result.type = `${arrayType.type}[]`;
result.base = arrayType.base;
result.template = arrayType.template;
result.validation = `yup.array<${result.name}>().of(${result.base}.schema)`; // TODO: Simple strings!
result.imports.push(...arrayType.imports);
} else {
const array: Schema = getSchema(openApi, schema.items, 'unkown');
@ -74,6 +72,7 @@ export function getSchema(openApi: OpenApi, schema: OpenApiSchema, name: string)
result.type = `${arrayType}[]`;
result.base = arrayType;
result.template = null;
result.validation = `yup.array<${result.name}>().of(${result.base}.schema)`; // TODO: Simple strings!
result.imports.push(...array.imports);
}
return result;

View File

@ -1,7 +0,0 @@
export function parseModelProperties(): any {
return {
imports: [],
properties: [],
enums: [],
};
}

View File

@ -1,4 +1,4 @@
import { Schema } from '../../../client/interfaces/Schema';
import { Model } from '../../../client/interfaces/Model';
// string
// array[test]
@ -6,13 +6,12 @@ import { Schema } from '../../../client/interfaces/Schema';
// foo: string
// bar: string
// }]
export function getSchemaType(schema: Schema): string {
export function getModelType(model: Model): string {
// if (schema.properties) {
// return schema.type
// }
if (schema.type) {
return schema.type;
if (model.type) {
return model.type;
}
return 'any';
}

View File

@ -1,19 +1,15 @@
import { Model } from '../../../client/interfaces/Model';
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { getSchema } from './getSchema';
import { Schema } from '../../../client/interfaces/Schema';
import { getModel } from './getModel';
/**
* Get the OpenAPI models.
*/
export function getModels(openApi: OpenApi): Map<string, Model> {
const models: Map<string, Model> = new Map<string, Model>();
export function getModels(openApi: OpenApi): Model[] {
const models: Model[] = [];
for (const definitionName in openApi.definitions) {
if (openApi.definitions.hasOwnProperty(definitionName)) {
const definition: OpenApiSchema = openApi.definitions[definitionName];
const definitionSchema: Schema = getSchema(openApi, definition, definitionName);
models.set(definitionSchema.name, definitionSchema);
const definitionModel: Model = getModel(openApi, definition, definitionName);
models.push(definitionModel);
}
}
return models;

View File

@ -1,6 +1,5 @@
import { Service } from '../../../client/interfaces/Service';
import { getServiceClassName } from './getServiceClassName';
import { Operation } from '../../../client/interfaces/Operation';
import { OpenApiOperation } from '../interfaces/OpenApiOperation';
import { getOperationName } from './getOperationName';
import { getOperationPath } from './getOperationPath';
@ -8,11 +7,8 @@ import { getOperationParameters } from './getOperationParameters';
import { OpenApi } from '../interfaces/OpenApi';
import { getComment } from './getComment';
import { getOperationResponses } from './getOperationResponses';
import { OperationParameters } from '../../../client/interfaces/OperationParameters';
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { getOperationResponse } from './getOperationResponse';
import { getOperationErrors } from './getOperationErrors';
import { OperationError } from '../../../client/interfaces/OperationError';
export function getOperation(openApi: OpenApi, url: string, method: string, op: OpenApiOperation): Operation {
const serviceName: string = (op.tags && op.tags[0]) || 'Service';

View File

@ -1,6 +1,3 @@
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { OperationError } from '../../../client/interfaces/OperationError';
export function getOperationErrors(responses: OperationResponse[]): OperationError[] {
return responses
.filter((response: OperationResponse): boolean => {

View File

@ -1,12 +1,9 @@
import { OpenApiParameter } from '../interfaces/OpenApiParameter';
import { getType } from './getType';
import { Parameter } from '../../../client/interfaces/Parameter';
import { Type } from '../../../client/interfaces/Type';
import { OpenApi } from '../interfaces/OpenApi';
import { getParameterName } from './getParameterName';
import { getComment } from './getComment';
import { SchemaReference } from '../../../client/interfaces/SchemaReference';
import { getSchemaReference } from './getSchemaReference';
export function getParameter(openApi: OpenApi, parameter: OpenApiParameter): Parameter {
const result: Parameter = {
@ -48,11 +45,12 @@ export function getParameter(openApi: OpenApi, parameter: OpenApiParameter): Par
// this reference type. Otherwise it might be a complex schema and
// then we need to parse the schema!
if (parameter.schema) {
const parameterSchema: SchemaReference = getSchemaReference(openApi, parameter.schema);
result.type = parameterSchema.type;
result.base = parameterSchema.base;
result.template = parameterSchema.template;
result.imports.push(...parameterSchema.imports);
// TODO: Check getSchema
// const parameterSchema: SchemaReference = getSchemaReference(openApi, parameter.schema);
// result.type = parameterSchema.type;
// result.base = parameterSchema.base;
// result.template = parameterSchema.template;
// result.imports.push(...parameterSchema.imports);
}
// Check if this could be a special enum where values are documented in the description.
@ -68,6 +66,7 @@ export function getParameter(openApi: OpenApi, parameter: OpenApiParameter): Par
// If the param is a enum then return the values as an inline type.
if (parameter.enum) {
// TODO: Check getSchema
// result.type = getEnumType(parameter.enum);
// result.base = 'string';
// result.imports = [];

View File

@ -1,7 +1,5 @@
import { OpenApiParameter } from '../interfaces/OpenApiParameter';
import { OperationParameters } from '../../../client/interfaces/OperationParameters';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { Parameter } from '../../../client/interfaces/Parameter';
import { getParameter } from './getParameter';
import { OpenApi } from '../interfaces/OpenApi';
import { getRef } from './getRef';

View File

@ -1,4 +1,3 @@
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { PrimaryType } from './constants';
export function getOperationResponse(responses: OperationResponse[]): OperationResponse {

View File

@ -1,12 +1,9 @@
import { OpenApiResponses } from '../interfaces/OpenApiResponses';
import { getOperationResponseCode } from './getOperationResponseCode';
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { OpenApiResponse } from '../interfaces/OpenApiResponse';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { getRef } from './getRef';
import { OpenApi } from '../interfaces/OpenApi';
import { getSchemaReference } from './getSchemaReference';
import { SchemaReference } from '../../../client/interfaces/SchemaReference';
export function getOperationResponses(openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] {
const results: OperationResponse[] = [];
@ -37,11 +34,11 @@ export function getOperationResponses(openApi: OpenApi, responses: OpenApiRespon
// this reference type. Otherwise it might be a complex schema and
// then we need to parse the schema!
if (response.schema) {
const responseSchema: SchemaReference = getSchemaReference(openApi, response.schema);
result.type = responseSchema.type;
result.base = responseSchema.base;
result.template = responseSchema.template;
result.imports.push(...responseSchema.imports);
// const responseSchema: SchemaReference = getSchemaReference(openApi, response.schema);
// result.type = responseSchema.type;
// result.base = responseSchema.base;
// result.template = responseSchema.template;
// result.imports.push(...responseSchema.imports);
}
results.push(result);

View File

@ -1,30 +0,0 @@
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { OpenApi } from '../interfaces/OpenApi';
import { SchemaProperty } from '../../../client/interfaces/SchemaProperty';
import { getComment } from './getComment';
export function getSchemaProperty(openApi: OpenApi, property: OpenApiSchema & OpenApiReference, name: string, required: boolean): SchemaProperty {
const result: SchemaProperty = {
name: name,
type: 'any',
base: 'any',
template: null,
description: getComment(property.description),
required: required,
nullable: false,
readOnly: property.readOnly || false,
extends: [],
imports: [],
properties: new Map<string, SchemaProperty>(),
};
// console.log(name, property);
// const property: OpenApiSchema & OpenApiReference = schema.properties[propertyName];
// const propertySchema: SchemaReference = getSchemaReference(openApi, property);
// result.imports.push(...propertySchema.imports);
// result.properties.set(propertyName, propertySchema);
return result;
}

View File

@ -1,14 +1,15 @@
import { getType } from './getType';
import { Type } from '../../../client/interfaces/Type';
import { OpenApiSchema } from '../interfaces/OpenApiSchema';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { getRef } from './getRef';
import { Schema } from '../../../client/interfaces/Schema';
import { getSchema } from './getSchema';
import { SchemaReference } from '../../../client/interfaces/SchemaReference';
import { OpenApi } from '../interfaces/OpenApi';
import { PrimaryType } from './constants';
export interface SchemaReference {
type: string;
base: string;
template: string | null;
imports: string[];
}
export function getSchemaReference(openApi: OpenApi, schema: OpenApiSchema & OpenApiReference): SchemaReference {
const result: SchemaReference = {
type: PrimaryType.OBJECT,
@ -18,18 +19,18 @@ export function getSchemaReference(openApi: OpenApi, schema: OpenApiSchema & Ope
};
if (schema.$ref) {
const itemSchemaType: Type = getType(schema.$ref);
result.type = itemSchemaType.type;
result.base = itemSchemaType.base;
result.template = itemSchemaType.template;
result.imports.push(...itemSchemaType.imports);
// const itemSchemaType: Type = getType(schema.$ref);
// result.type = itemSchemaType.type;
// result.base = itemSchemaType.base;
// result.template = itemSchemaType.template;
// result.imports.push(...itemSchemaType.imports);
} else {
const item: OpenApiSchema = getRef<OpenApiSchema>(openApi, schema);
const itemSchema: Schema = getSchema(openApi, item, 'unknown');
result.type = itemSchema.type;
result.base = itemSchema.base;
result.template = itemSchema.template;
result.imports.push(...itemSchema.imports);
// const item: OpenApiSchema = getRef<OpenApiSchema>(openApi, schema);
// const itemSchema: Schema = getSchema(openApi, item, 'unknown');
// result.type = itemSchema.type;
// result.base = itemSchema.base;
// result.template = itemSchema.template;
// result.imports.push(...itemSchema.imports);
}
return result;

View File

@ -1,15 +1,12 @@
import { Service } from '../../../client/interfaces/Service';
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiPath } from '../interfaces/OpenApiPath';
import { OpenApiOperation } from '../interfaces/OpenApiOperation';
import { getOperation } from './getOperation';
import { Operation } from '../../../client/interfaces/Operation';
import { Method } from './constants';
/**
* Get the OpenAPI services
*/
export function getServices(openApi: OpenApi): Map<string, Service> {
export function getServices(openApi: OpenApi): Service[] {
const services: Map<string, Service> = new Map<string, Service>();
for (const url in openApi.paths) {
if (openApi.paths.hasOwnProperty(url)) {
@ -48,5 +45,5 @@ export function getServices(openApi: OpenApi): Map<string, Service> {
}
}
}
return services;
return Array.from(services.values());
}

View File

@ -29,7 +29,7 @@ export function getType(value: string | undefined, template: string | null = nul
// If the first match is a generic array then construct a correct array type,
// for example the type "Array[Model]" becomes "Model[]".
if (match1.type === 'any[]') {
if (match1.type === PrimaryType.ARRAY) {
result.type = `${match2.type}[]`;
result.base = `${match2.type}`;
match1.imports = [];

View File

@ -1,23 +0,0 @@
import { getMappedType } from './getMappedType';
describe('getMappedType', () => {
it('should map types to the basics', () => {
expect(getMappedType('File')).toEqual('File');
expect(getMappedType('Array')).toEqual('any[]');
expect(getMappedType('List')).toEqual('any[]');
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('');
});
});

View File

@ -1,42 +0,0 @@
const MAPPINGS = new Map<string, string>([
['file', 'File'],
['binary', 'File'],
['array', 'any[]'],
['list', 'any[]'],
['object', 'any'],
['any', 'any'],
['boolean', 'boolean'],
['byte', 'number'],
['int', 'number'],
['int32', 'number'],
['int64', '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.
* @param type
*/
export function getMappedType(type: string): string {
const mapped: string | undefined = MAPPINGS.get(type.toLowerCase());
if (mapped) {
return mapped;
}
return type;
}
export function hasMappedType(type: string): boolean {
return MAPPINGS.has(type.toLowerCase());
}

View File

@ -5,7 +5,7 @@ import { OpenApi } from '../interfaces/OpenApi';
* Parse and return the OpenAPI models.
* @param openApi
*/
export function getModels(openApi: OpenApi): Map<string, Model> {
const models: Map<string, Model> = new Map<string, Model>();
export function getModels(openApi: OpenApi): Model[] {
const models: Model[] = [];
return models;
}

View File

@ -5,7 +5,7 @@ import { OpenApi } from '../interfaces/OpenApi';
* Parse and return the OpenAPI services.
* @param openApi
*/
export function getServices(openApi: OpenApi): Map<string, Service> {
const services: Map<string, Service> = new Map<string, Service>();
export function getServices(openApi: OpenApi): Service[] {
const services: Service[] = [];
return services;
}

View File

@ -1,51 +0,0 @@
import { getType } from './getType';
describe('getType', () => {
it('should convert int', () => {
const type = getType('int', null);
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', null);
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]', null);
expect(type.type).toEqual('string[]');
expect(type.base).toEqual('string');
expect(type.template).toEqual(null);
expect(type.imports).toEqual([]);
});
it('should convert template with primary', () => {
const type = getType('#/components/schemas/Link[String]', null);
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('#/components/schemas/Link[Model]', null);
expect(type.type).toEqual('Link<Model>');
expect(type.base).toEqual('Link');
expect(type.template).toEqual('Model');
expect(type.imports).toEqual(['Link', 'Model']);
});
it('should convert generic', () => {
const type = getType('#/components/schemas/Link', 'Link');
expect(type.type).toEqual('T');
expect(type.base).toEqual('T');
expect(type.template).toEqual(null);
expect(type.imports).toEqual([]);
});
});

View File

@ -1,74 +0,0 @@
import { stripNamespace } from './stripNamespace';
import { Type } from '../../../client/interfaces/Type';
import { getMappedType, hasMappedType } from './getMappedType';
/**
* Parse any 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 | null = null): Type {
let propertyType = 'any';
let propertyBase = 'any';
let propertyTemplate: string | null = null;
let propertyImports: string[] = [];
// Remove definitions prefix and cleanup string.
const valueTrimmed: string = stripNamespace(value || '');
// Check of we have an Array type or generic type, for instance: "Link[Model]".
if (/\[.*\]$/g.test(valueTrimmed)) {
// Find the first and second type
const match: RegExpMatchArray | null = valueTrimmed.match(/(.*?)\[(.*)\]$/);
if (match) {
// Both of the types can be complex types so parse each of them.
const match1: Type = getType(match[1]);
const match2: Type = getType(match[2]);
// If the first match is a generic array then construct a correct array type, for example:
// The type "Array[Model]" becomes "Model[]".
if (match1.type === 'any[]') {
propertyType = `${match2.type}[]`;
propertyBase = `${match2.type}`;
match1.imports = [];
} else if (match2.type === '') {
// Primary types like number[] or string[]
propertyType = match1.type;
propertyBase = match1.type;
propertyTemplate = match1.type;
match2.imports = [];
} else {
propertyType = `${match1.type}<${match2.type}>`;
propertyBase = match1.type;
propertyTemplate = match2.type;
}
// Either way we need to add the found imports
propertyImports.push(...match1.imports);
propertyImports.push(...match2.imports);
}
} else if (hasMappedType(valueTrimmed)) {
const mapped: string = getMappedType(valueTrimmed);
propertyType = mapped;
propertyBase = mapped;
} else {
propertyType = valueTrimmed;
propertyBase = valueTrimmed;
propertyImports.push(valueTrimmed);
}
// 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 (propertyType === template) {
propertyType = 'T'; // Template;
propertyBase = 'T'; // Template;
propertyImports = [];
}
return {
type: propertyType,
base: propertyBase,
template: propertyTemplate,
imports: propertyImports,
};
}

View File

@ -1,15 +0,0 @@
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('object')).toBeTruthy();
expect(isPrimaryType('void')).toBeTruthy();
expect(isPrimaryType('null')).toBeTruthy();
expect(isPrimaryType('Array')).toBeFalsy();
expect(isPrimaryType('MyModel')).toBeFalsy();
});
});

View File

@ -1,17 +0,0 @@
/**
* Check if given type is a primary type.
* @param type
*/
export function isPrimaryType(type: string): boolean {
switch (type.toLowerCase()) {
case 'number':
case 'boolean':
case 'string':
case 'object':
case 'any':
case 'void':
case 'null':
return true;
}
return false;
}

View File

@ -8,7 +8,9 @@
import { {{{this}}} } from '../models/{{{this}}}';
{{/each}}
{{/if}}
{{#if validation}}
import * as yup from 'yup';
{{/if}}
{{#if description}}
/**
@ -42,24 +44,25 @@ export namespace {{{name}}} {
}
{{/each}}
export const schema = yup.object<{{{name}}}{{{template}}}>().shape({
// Add properties
});
{{#if validation}}
export const schema = {{{validation}}};
export function validate(value: {{{name}}}{{{template}}}): Promise<{{{name}}}{{{template}}}> {
export function validate(value: any): Promise<{{{name}}}{{{template}}}> {
return schema.validate(value, { strict: true });
}
export function validateSync(value: {{{name}}}{{{template}}}): {{{name}}}{{{template}}} {
export function validateSync(value: any): {{{name}}}{{{template}}} {
return schema.validateSync(value, { strict: true });
}
{{/if}}
}
{{/if}}
{{#if isType}}
export type {{{name}}} = {{{type}}}{{#if nullable}} | null{{/if}};
{{#if validation}}
export namespace {{{name}}} {
export const schema = yup.string();
export const schema = {{{validation}}};
export function validate(value: any): Promise<{{{name}}}{{#if nullable}} | null{{/if}}> {
return schema.validate(value, { strict: true });
@ -70,15 +73,17 @@ export namespace {{{name}}} {
}
}
{{/if}}
{{/if}}
{{#if isEnum}}
export enum {{{name}}} {
{{#each symbols}}
{{{name}}} = {{{value}}},
{{/each}}
}
{{#if validation}}
export namespace {{{name}}} {
export const schema = yup.string();
export const schema = {{{validation}}};
export function validate(value: any): Promise<{{{name}}}> {
return schema.validate(value, { strict: true });
@ -89,3 +94,4 @@ export namespace {{{name}}} {
}
}
{{/if}}
{{/if}}

View File

@ -0,0 +1,9 @@
import { getSortedImports } from './getSortedImports';
import { Model } from '../client/interfaces/Model';
export function cleanupModels(models: Model[]): Model[] {
models.forEach((models: Model): void => {
models.imports = getSortedImports(models.imports);
});
return models;
}

View File

@ -2,13 +2,12 @@ import { Service } from '../client/interfaces/Service';
import { getSortedImports } from './getSortedImports';
import { Operation } from '../client/interfaces/Operation';
export function cleanupServices(services: Map<string, Service>): Map<string, Service> {
export function cleanupServices(services: Service[]): Service[] {
services.forEach((service: Service): void => {
const names: Map<string, number> = new Map<string, number>();
service.imports = getSortedImports(service.imports);
// Append postfix number to duplicate operation names and sort them.
service.operations = service.operations
.map(
(operation: Operation): Operation => {

View File

@ -3,39 +3,55 @@ import { Model } from '../client/interfaces/Model';
describe('getSortedModels', () => {
it('should return sorted list', () => {
const models = new Map<string, Model>();
models.set('John', {
name: 'John',
base: 'John',
type: '',
template: null,
extends: [],
imports: [],
properties: [],
enums: [],
});
models.set('Jane', {
name: 'Jane',
base: 'Jane',
type: '',
template: null,
extends: [],
imports: [],
properties: [],
enums: [],
});
models.set('Doe', {
name: 'Doe',
base: 'Doe',
type: '',
template: null,
extends: [],
imports: [],
properties: [],
enums: [],
});
const models: Model[] = [
{
isInterface: false,
isType: false,
isEnum: false,
name: 'John',
type: 'John',
base: 'John',
template: null,
validation: null,
description: null,
extends: null,
imports: [],
properties: [],
enums: [],
},
{
isInterface: false,
isType: false,
isEnum: false,
name: 'Jane',
type: 'Jane',
base: 'Jane',
template: null,
validation: null,
description: null,
extends: null,
imports: [],
properties: [],
enums: [],
},
{
isInterface: false,
isType: false,
isEnum: false,
name: 'Doe',
type: 'Doe',
base: 'Doe',
template: null,
validation: null,
description: null,
extends: null,
imports: [],
properties: [],
enums: [],
},
];
expect(getSortedModels(new Map<string, Model>())).toEqual([]);
expect(getSortedModels(models)).toEqual([models.get('Doe'), models.get('Jane'), models.get('John')]);
expect(getSortedModels([])).toEqual([]);
expect(getSortedModels(models)).toEqual(models.reverse());
});
});

View File

@ -1,15 +1,9 @@
import { Model } from '../client/interfaces/Model';
/**
* Convert a given Map to an Array and sort the result the Model base name.
* @param models Map of Model objects.
*/
export function getSortedModels(models: Map<string, Model>): Model[] {
return (
Array.from(models.values()).sort((a, b) => {
const nameA: string = a.name.toLowerCase();
const nameB: string = b.name.toLowerCase();
return nameA.localeCompare(nameB, 'en');
}) || []
);
export function getSortedModels(models: Model[]): Model[] {
return models.sort((a, b) => {
const nameA: string = a.name.toLowerCase();
const nameB: string = b.name.toLowerCase();
return nameA.localeCompare(nameB, 'en');
});
}

View File

@ -3,24 +3,25 @@ import { Service } from '../client/interfaces/Service';
describe('getSortedServices', () => {
it('should return sorted list', () => {
const services = new Map<string, Service>();
services.set('John', {
name: 'John',
operations: [],
imports: [],
});
services.set('Jane', {
name: 'Jane',
operations: [],
imports: [],
});
services.set('Doe', {
name: 'Doe',
operations: [],
imports: [],
});
const services: Service[] = [
{
name: 'John',
operations: [],
imports: [],
},
{
name: 'Jane',
operations: [],
imports: [],
},
{
name: 'Doe',
operations: [],
imports: [],
},
];
expect(getSortedServices(new Map<string, Service>())).toEqual([]);
expect(getSortedServices(services)).toEqual([services.get('Doe'), services.get('Jane'), services.get('John')]);
expect(getSortedServices([])).toEqual([]);
expect(getSortedServices(services)).toEqual(services.reverse());
});
});

View File

@ -1,15 +1,9 @@
import { Service } from '../client/interfaces/Service';
/**
* Convert a given Map to an Array and sort the result the service base name.
* @param services Map of Service objects.
*/
export function getSortedServices(services: Map<string, Service>): Service[] {
return (
Array.from(services.values()).sort((a, b) => {
const nameA: string = a.name.toLowerCase();
const nameB: string = b.name.toLowerCase();
return nameA.localeCompare(nameB, 'en');
}) || []
);
export function getSortedServices(services: Service[]): Service[] {
return services.sort((a, b) => {
const nameA: string = a.name.toLowerCase();
const nameB: string = b.name.toLowerCase();
return nameA.localeCompare(nameB, 'en');
});
}

View File

@ -24,8 +24,8 @@ describe('writeClient', () => {
const client: Client = {
server: 'http://localhost:8080',
version: 'v1',
models: new Map<string, Model>(),
services: new Map<string, Service>(),
models: [],
services: [],
};
const templates: Templates = {

View File

@ -13,6 +13,7 @@ import * as fs from 'fs';
import { getFileName } from './getFileName';
import * as glob from 'glob';
import { cleanupServices } from './cleanupServices';
import { cleanupModels } from './cleanupModels';
/**
* Write our OpenAPI client, using the given templates at the given output path
@ -58,7 +59,7 @@ export function writeClient(client: Client, language: Language, templates: Templ
try {
// TODO: Cleanup models
writeClientIndex(client, language, templates.index, outputPath);
writeClientModels(getSortedModels(client.models), language, templates.model, outputPathModels);
writeClientModels(getSortedModels(cleanupModels(client.models)), language, templates.model, outputPathModels);
writeClientServices(getSortedServices(cleanupServices(client.services)), language, templates.service, outputPathServices);
} catch (e) {
throw e;

View File

@ -14,8 +14,8 @@ describe('writeClientIndex', () => {
const client: Client = {
server: 'http://localhost:8080',
version: 'v1',
models: new Map<string, Model>(),
services: new Map<string, Service>(),
models: [],
services: [],
};
const template = () => 'dummy';
writeClientIndex(client, Language.TYPESCRIPT, template, '/');

View File

@ -11,11 +11,16 @@ describe('writeClientModels', () => {
it('should write to filesystem', () => {
const models: Model[] = [
{
isInterface: false,
isType: false,
isEnum: false,
name: 'Item',
type: 'Item',
base: 'Item',
type: '',
template: '',
extends: [],
template: null,
validation: null,
description: null,
extends: null,
imports: [],
properties: [],
enums: [],

View File

@ -16,13 +16,7 @@ export function writeClientModels(models: Model[], language: Language, template:
models.forEach(model => {
const fileName: string = getFileName(model.name, language);
try {
fs.writeFileSync(
path.resolve(outputPath, fileName),
template({
...model,
// properties: Array.from(model.properties.values()), // TODO in cleanup?
})
);
fs.writeFileSync(path.resolve(outputPath, fileName), template(model));
} catch (e) {
throw new Error(`Could not write model: "${fileName}"`);
}