diff --git a/src/client/interfaces/Schema.d.ts b/src/client/interfaces/Schema.d.ts index c52b4470..d6c5df39 100644 --- a/src/client/interfaces/Schema.d.ts +++ b/src/client/interfaces/Schema.d.ts @@ -1,15 +1,17 @@ +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; - default?: any; - required: boolean; - nullable: boolean; - readOnly: boolean; extends: string[]; imports: string[]; - properties: Map; + symbols: Shape[]; // TODO: Betere naam! + properties: SchemaProperty[]; } diff --git a/src/client/interfaces/SchemaProperty.d.ts b/src/client/interfaces/SchemaProperty.d.ts index e703d479..bb91c71c 100644 --- a/src/client/interfaces/SchemaProperty.d.ts +++ b/src/client/interfaces/SchemaProperty.d.ts @@ -1,13 +1,8 @@ export interface SchemaProperty { name: string; type: string; - base: string; - template: string | null; - description?: string; required: boolean; nullable: boolean; readOnly: boolean; - extends: string[]; - imports: string[]; - properties: Map; + description?: string; } diff --git a/src/client/interfaces/Shape.ts b/src/client/interfaces/Shape.ts new file mode 100644 index 00000000..7cf2a3fb --- /dev/null +++ b/src/client/interfaces/Shape.ts @@ -0,0 +1,4 @@ +export interface Shape { + name: string; + value: string; +} diff --git a/src/openApi/v2/interfaces/OpenApiParameter.d.ts b/src/openApi/v2/interfaces/OpenApiParameter.d.ts index 74aecc43..6cb85a95 100644 --- a/src/openApi/v2/interfaces/OpenApiParameter.d.ts +++ b/src/openApi/v2/interfaces/OpenApiParameter.d.ts @@ -27,6 +27,6 @@ export interface OpenApiParameter { maxItems?: number; minItems?: number; uniqueItems?: boolean; - enum?: string[]; + enum?: (string | number)[]; multipleOf?: number; } diff --git a/src/openApi/v2/interfaces/OpenApiSchema.d.ts b/src/openApi/v2/interfaces/OpenApiSchema.d.ts index 26a588ba..57bfb7f4 100644 --- a/src/openApi/v2/interfaces/OpenApiSchema.d.ts +++ b/src/openApi/v2/interfaces/OpenApiSchema.d.ts @@ -25,7 +25,7 @@ export interface OpenApiSchema { maxProperties?: number; minProperties?: number; required?: string[]; - enum?: string[]; + enum?: (string | number)[]; type?: string; items?: OpenApiSchema & OpenApiReference; allOf?: (OpenApiSchema & OpenApiReference)[]; diff --git a/src/openApi/v2/parser/constants.ts b/src/openApi/v2/parser/constants.ts new file mode 100644 index 00000000..04041dd7 --- /dev/null +++ b/src/openApi/v2/parser/constants.ts @@ -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([ + ['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', +} diff --git a/src/openApi/v2/parser/getArrayType.ts b/src/openApi/v2/parser/getArrayType.ts index d0e9b1e2..6c4e7342 100644 --- a/src/openApi/v2/parser/getArrayType.ts +++ b/src/openApi/v2/parser/getArrayType.ts @@ -1,13 +1,13 @@ import { getType } from './getType'; +import { PrimaryType } from './constants'; import { Type } from '../../../client/interfaces/Type'; import { OpenApiItems } from '../interfaces/OpenApiItems'; -import { getEnumType } from './getEnumType'; import { ArrayType } from '../../../client/interfaces/ArrayType'; export function getArrayType(items: OpenApiItems): ArrayType { const result: ArrayType = { - type: 'any', - base: 'any', + type: PrimaryType.OBJECT, + base: PrimaryType.OBJECT, template: null, default: items.default, imports: [], @@ -32,11 +32,11 @@ export function getArrayType(items: OpenApiItems): ArrayType { } } - if (items.enum) { - result.type = getEnumType(items.enum, true); - result.base = 'string'; - result.imports = []; - } + // if (items.enum) { + // result.type = getEnumType(items.enum, true); + // result.base = 'string'; + // result.imports = []; + // } return result; } diff --git a/src/openApi/v2/parser/getEnumSymbols.ts b/src/openApi/v2/parser/getEnumSymbols.ts new file mode 100644 index 00000000..c60f2c1c --- /dev/null +++ b/src/openApi/v2/parser/getEnumSymbols.ts @@ -0,0 +1,24 @@ +import { Shape } from '../../../client/interfaces/Shape'; + +export function getEnumSymbols(values?: (string | number)[]): Shape[] { + 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 => { + return typeof value === 'number' + ? { + name: `NUM_${value}`, + value: String(value), + } + : { + name: value.replace(/([a-z])([A-Z]+)/g, '$1_$2').toUpperCase(), + value: `'${value}'`, + }; + } + ); + } + return []; +} diff --git a/src/openApi/v2/parser/getEnumSymbolsFromDescription.ts b/src/openApi/v2/parser/getEnumSymbolsFromDescription.ts new file mode 100644 index 00000000..f052877b --- /dev/null +++ b/src/openApi/v2/parser/getEnumSymbolsFromDescription.ts @@ -0,0 +1,27 @@ +import { Shape } from '../../../client/interfaces/Shape'; + +export function getEnumSymbolsFromDescription(description: string): Shape[] { + // 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[] = []; + matches.forEach((match: string): void => { + const name: string = match.split('=')[0]; + const value: number = parseInt(match.split('=')[1].replace(/[^0-9]/g, '')); + if (name && Number.isInteger(value)) { + symbols.push({ name, value: String(value) }); + } + }); + + // Filter out any duplicate names + return symbols.filter((symbol: Shape, index: number, arr: Shape[]): boolean => { + return arr.map(item => item.name).indexOf(symbol.name) === index; + }); + } + } + + return []; +} diff --git a/src/openApi/v2/parser/getEnumType.ts b/src/openApi/v2/parser/getEnumType.ts index e1690c5b..c55f0e1d 100644 --- a/src/openApi/v2/parser/getEnumType.ts +++ b/src/openApi/v2/parser/getEnumType.ts @@ -1,22 +1,22 @@ -export function getEnumType(values?: string[], addParentheses = false): string { - if (Array.isArray(values)) { - // Filter out empty and double enum values. - // Plus make sure we put quotes around strings! - const entries: string[] = values - .filter(name => name) - .filter((name: string, index: number, arr: string[]) => { - return arr.indexOf(name) === index; - }) - .map(value => `'${String(value)}'`); +import { Shape } from '../../../client/interfaces/Shape'; - // 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 (entries.length > 1 && addParentheses) { - return `(${entries.join(' | ')})`; - } +export function getEnumType(symbols: Shape[], 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(); - return entries.join(' | '); + // 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 (entries.length > 1 && addParentheses) { + return `(${entries.join(' | ')})`; } - return 'string'; + + return entries.join(' | '); } diff --git a/src/openApi/v2/parser/getEnumTypeFromDescription.ts b/src/openApi/v2/parser/getEnumTypeFromDescription.ts deleted file mode 100644 index aa2ebc10..00000000 --- a/src/openApi/v2/parser/getEnumTypeFromDescription.ts +++ /dev/null @@ -1,36 +0,0 @@ -export function getEnumTypeFromDescription(description: string, addParentheses: boolean = false): string | null { - // 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 values: number[] = []; - matches.forEach((match: string): void => { - const value = parseInt(match.split('=')[1].replace(/[^0-9]/g, '')); - if (Number.isInteger(value)) { - values.push(value); - } - }); - - // Filter and sort the values - const entries: string[] = values - .sort() - .filter((name: number, index: number, arr: number[]) => { - return arr.indexOf(name) === index; - }) - .map(value => String(value)); - - // 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 (entries.length > 1 && addParentheses) { - return `(${entries.join(' | ')})`; - } - - return entries.join(' | '); - } - } - - return null; -} diff --git a/src/openApi/v2/parser/getMappedType.ts b/src/openApi/v2/parser/getMappedType.ts index 06419037..67bb6d92 100644 --- a/src/openApi/v2/parser/getMappedType.ts +++ b/src/openApi/v2/parser/getMappedType.ts @@ -1,36 +1,10 @@ -const MAPPINGS = new Map([ - ['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'], -]); +import { PrimaryType, TYPE_MAPPINGS } from './constants'; /** * 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()); +export function getMappedType(type: string): PrimaryType | string { + const mapped: string | undefined = TYPE_MAPPINGS.get(type.toLowerCase()); if (mapped) { return mapped; } @@ -38,5 +12,5 @@ export function getMappedType(type: string): string { } export function hasMappedType(type: string): boolean { - return MAPPINGS.has(type.toLowerCase()); + return TYPE_MAPPINGS.has(type.toLowerCase()); } diff --git a/src/openApi/v2/parser/getModels.ts b/src/openApi/v2/parser/getModels.ts index 1c319752..aad7994f 100644 --- a/src/openApi/v2/parser/getModels.ts +++ b/src/openApi/v2/parser/getModels.ts @@ -3,35 +3,18 @@ import { OpenApi } from '../interfaces/OpenApi'; import { OpenApiSchema } from '../interfaces/OpenApiSchema'; import { getSchema } from './getSchema'; import { Schema } from '../../../client/interfaces/Schema'; -import { Type } from '../../../client/interfaces/Type'; -import { getType } from '../../v3/parser/getType'; -import { getModelTemplate } from './getModelTemplate'; /** * Get the OpenAPI models. */ export function getModels(openApi: OpenApi): Map { const models: Map = new Map(); - - // Iterate over the definitions for (const definitionName in openApi.definitions) { if (openApi.definitions.hasOwnProperty(definitionName)) { const definition: OpenApiSchema = openApi.definitions[definitionName]; - const definitionSchema: Schema = getSchema(openApi, definition); - const modelClass: Type = getType(definitionName); - const modelTemplate: string = getModelTemplate(modelClass); - - if (models.has(modelClass.base)) { - continue; - } - - definitionSchema.base = modelClass.base; - definitionSchema.type = modelClass.type; - definitionSchema.template = modelTemplate; - - models.set(modelClass.base, definitionSchema); + const definitionSchema: Schema = getSchema(openApi, definition, definitionName); + models.set(definitionSchema.name, definitionSchema); } } - return models; } diff --git a/src/openApi/v2/parser/getOperationResponse.ts b/src/openApi/v2/parser/getOperationResponse.ts index 76941090..eb052d2d 100644 --- a/src/openApi/v2/parser/getOperationResponse.ts +++ b/src/openApi/v2/parser/getOperationResponse.ts @@ -1,11 +1,12 @@ import { OperationResponse } from '../../../client/interfaces/OperationResponse'; +import { PrimaryType } from './constants'; export function getOperationResponse(responses: OperationResponse[]): OperationResponse { const response: OperationResponse = { code: 200, text: '', - type: 'any', - base: 'any', + type: PrimaryType.OBJECT, + base: PrimaryType.OBJECT, template: null, imports: [], }; diff --git a/src/openApi/v2/parser/getParameter.ts b/src/openApi/v2/parser/getParameter.ts index a373b7e2..26e1d086 100644 --- a/src/openApi/v2/parser/getParameter.ts +++ b/src/openApi/v2/parser/getParameter.ts @@ -4,10 +4,6 @@ import { Parameter } from '../../../client/interfaces/Parameter'; import { Type } from '../../../client/interfaces/Type'; import { OpenApi } from '../interfaces/OpenApi'; import { getParameterName } from './getParameterName'; -import { getArrayType } from './getArrayType'; -import { ArrayType } from '../../../client/interfaces/ArrayType'; -import { getEnumType } from './getEnumType'; -import { getEnumTypeFromDescription } from './getEnumTypeFromDescription'; import { getComment } from './getComment'; import { SchemaReference } from '../../../client/interfaces/SchemaReference'; import { getSchemaReference } from './getSchemaReference'; @@ -38,11 +34,12 @@ export function getParameter(openApi: OpenApi, parameter: OpenApiParameter): Par // If the parameter is an Array type, we check for the child type, // so we can create a typed array, otherwise this will be a "any[]". if (parameter.type === 'array' && parameter.items) { - const arrayType: ArrayType = getArrayType(parameter.items); - result.type = `${arrayType.type}[]`; - result.base = arrayType.base; - result.template = arrayType.template; - result.imports.push(...arrayType.imports); + // TODO: Check getSchema + // const arrayType: ArrayType = getArrayType(parameter.items); + // result.type = `${arrayType.type}[]`; + // result.base = arrayType.base; + // result.template = arrayType.template; + // result.imports.push(...arrayType.imports); } } @@ -58,21 +55,22 @@ export function getParameter(openApi: OpenApi, parameter: OpenApiParameter): Par result.imports.push(...parameterSchema.imports); } - // If the param is a enum then return the values as an inline type. - if (parameter.enum) { - result.type = getEnumType(parameter.enum); - result.base = 'string'; - result.imports = []; + // Check if this could be a special enum where values are documented in the description. + if (parameter.enum && parameter.description && parameter.type === 'int') { + // TODO: Check getSchema + // const enumType: string | null = getEnumTypeFromDescription(parameter.description); + // if (enumType) { + // result.type = enumType; + // result.base = 'number'; + // result.imports = []; + // } } - // Check if this could be a special enum where values are documented in the description. - if (parameter.description && parameter.type === 'int') { - const enumType: string | null = getEnumTypeFromDescription(parameter.description); - if (enumType) { - result.type = enumType; - result.base = 'number'; - result.imports = []; - } + // If the param is a enum then return the values as an inline type. + if (parameter.enum) { + // result.type = getEnumType(parameter.enum); + // result.base = 'string'; + // result.imports = []; } return result; diff --git a/src/openApi/v2/parser/getSchema.ts b/src/openApi/v2/parser/getSchema.ts index 4317c2c8..d111a8ba 100644 --- a/src/openApi/v2/parser/getSchema.ts +++ b/src/openApi/v2/parser/getSchema.ts @@ -1,71 +1,85 @@ import { OpenApi } from '../interfaces/OpenApi'; import { Schema } from '../../../client/interfaces/Schema'; import { OpenApiSchema } from '../interfaces/OpenApiSchema'; -import { getType } from './getType'; import { getComment } from './getComment'; +import { getType } from './getType'; import { Type } from '../../../client/interfaces/Type'; +import { getEnumSymbolsFromDescription } from './getEnumSymbolsFromDescription'; import { getEnumType } from './getEnumType'; -import { getEnumTypeFromDescription } from './getEnumTypeFromDescription'; +import { getEnumSymbols } from './getEnumSymbols'; +import { PrimaryType } from './constants'; +import { Shape } from '../../../client/interfaces/Shape'; import { OpenApiReference } from '../interfaces/OpenApiReference'; -import { SchemaReference } from '../../../client/interfaces/SchemaReference'; -import { getSchemaReference } from './getSchemaReference'; -import { SchemaProperty } from '../../../client/interfaces/SchemaProperty'; -import { getSchemaProperty } from './getSchemaProperty'; +import { getRef } from './getRef'; +import { getSchemaType } from './getSchemaType'; -// TODO: I think we can convert this into getModel and getModelProperties -// but we need to think about what will happen when a simple type is used as a model -// needs a test case -export function getSchema(openApi: OpenApi, schema: OpenApiSchema): Schema { +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 + isInterface: false, + isType: false, + isEnum: false, type: 'any', base: 'any', template: null, description: getComment(schema.description), - default: schema.default, // TODO: Unused? - required: false, // TODO: Unused? - nullable: false, // TODO: Unused? - readOnly: schema.readOnly || false, // TODO: Unused? extends: [], imports: [], - properties: new Map(), + symbols: [], + properties: [], }; - // If the schema has a type than it can be a basic or generic type. - if (schema.type) { - const schemaType: Type = getType(schema.type); - result.type = schemaType.type; - result.base = schemaType.base; - result.template = schemaType.template; - result.imports.push(...schemaType.imports); - - // 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) { - const arrayType: SchemaReference = getSchemaReference(openApi, schema.items); - result.type = `${arrayType.type}[]`; - result.base = arrayType.base; - result.template = arrayType.template; - result.imports.push(...arrayType.imports); + // If the param is a enum then return the values as an inline type. + if (schema.enum) { + const enumSymbols: Shape[] = getEnumSymbols(schema.enum); + if (enumSymbols.length) { + result.isEnum = true; + result.symbols = enumSymbols; + result.type = getEnumType(result.symbols); + result.base = PrimaryType.STRING; + return result; } } // If the param is a enum then return the values as an inline type. - if (schema.enum) { - result.type = getEnumType(schema.enum); - result.base = 'string'; - result.imports = []; - } - - // Check if this could be a special enum where values are documented in the description. - if (schema.description && schema.type === 'int') { - const enumType: string | null = getEnumTypeFromDescription(schema.description); - if (enumType) { - result.type = enumType; - result.base = 'number'; - result.imports = []; + if (schema.type === 'int' && schema.description) { + const enumSymbols: Shape[] = getEnumSymbolsFromDescription(schema.description); + if (enumSymbols.length) { + result.isEnum = true; + result.symbols = enumSymbols; + result.type = getEnumType(enumSymbols); + result.base = PrimaryType.NUMBER; + return result; } } + // 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); + result.isType = true; + result.type = `${arrayType.type}[]`; + result.base = arrayType.base; + result.template = arrayType.template; + result.imports.push(...arrayType.imports); + } else { + const array: Schema = getSchema(openApi, schema.items, 'unkown'); + const arrayType: string = getSchemaType(array); + result.isType = true; + result.type = `${arrayType}[]`; + result.base = arrayType; + result.template = null; + result.imports.push(...array.imports); + } + return result; + } + + /* // Check if this model extends other models if (schema.allOf) { schema.allOf.forEach((parent: OpenApiSchema & OpenApiReference): void => { @@ -87,15 +101,60 @@ export function getSchema(openApi: OpenApi, schema: OpenApiSchema): Schema { } }); } + */ - for (const propertyName in schema.properties) { - if (schema.properties.hasOwnProperty(propertyName)) { - const propertyRef: OpenApiSchema & OpenApiReference = schema.properties[propertyName]; - const propertyRequired: boolean = (schema.required && schema.required.includes(propertyName)) || false; - const property: SchemaProperty = getSchemaProperty(openApi, propertyRef, propertyName, propertyRequired); - result.imports.push(...property.imports); - result.properties.set(propertyName, property); + // TODO: Properties kunnen weer simple REFS zijn, of zelfs geneste properties, testcase nodig! + + if (schema.type === 'object' && schema.properties) { + result.isInterface = true; + result.type = 'interface'; + result.base = 'interface'; + result.template = null; + + for (const propertyName in schema.properties) { + if (schema.properties.hasOwnProperty(propertyName)) { + const propertyRequired: boolean = (schema.required && schema.required.includes(propertyName)) || false; + const propertyRef: OpenApiSchema & OpenApiReference = schema.properties[propertyName]; + + if (propertyRef.$ref) { + const propertyType: Type = getType(propertyRef.$ref); + result.imports.push(...propertyType.imports); + result.properties.push({ + name: propertyName, + type: propertyType.type, + required: propertyRequired, + nullable: false, + readOnly: false, + }); + } else { + const property: OpenApiSchema = getRef(openApi, propertyRef); + const propertySchema: Schema = getSchema(openApi, property, propertyName); + const propertyType: string = getSchemaType(propertySchema); + result.imports.push(...propertySchema.imports); + result.properties.push({ + name: propertyName, + type: propertyType, + required: propertyRequired, + nullable: false, + readOnly: property.readOnly || false, + description: property.description, + }); + } + } } + + return result; + } + + // If the schema has a type than it can be a basic or generic type. + if (schema.type) { + const schemaType: Type = getType(schema.type); + result.isType = true; + result.type = schemaType.type; + result.base = schemaType.base; + result.template = schemaType.template; + result.imports.push(...schemaType.imports); + return result; } return result; diff --git a/src/openApi/v2/parser/getSchemaReference.ts b/src/openApi/v2/parser/getSchemaReference.ts index 4c166a69..f47542de 100644 --- a/src/openApi/v2/parser/getSchemaReference.ts +++ b/src/openApi/v2/parser/getSchemaReference.ts @@ -7,11 +7,12 @@ 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 function getSchemaReference(openApi: OpenApi, schema: OpenApiSchema & OpenApiReference): SchemaReference { const result: SchemaReference = { - type: 'any', - base: 'any', + type: PrimaryType.OBJECT, + base: PrimaryType.OBJECT, template: null, imports: [], }; @@ -24,7 +25,7 @@ export function getSchemaReference(openApi: OpenApi, schema: OpenApiSchema & Ope result.imports.push(...itemSchemaType.imports); } else { const item: OpenApiSchema = getRef(openApi, schema); - const itemSchema: Schema = getSchema(openApi, item); + const itemSchema: Schema = getSchema(openApi, item, 'unknown'); result.type = itemSchema.type; result.base = itemSchema.base; result.template = itemSchema.template; diff --git a/src/openApi/v2/parser/getSchemaType.ts b/src/openApi/v2/parser/getSchemaType.ts new file mode 100644 index 00000000..87ff0898 --- /dev/null +++ b/src/openApi/v2/parser/getSchemaType.ts @@ -0,0 +1,18 @@ +import { Schema } from '../../../client/interfaces/Schema'; + +// string +// array[test] +// array[{ +// foo: string +// bar: string +// }] + +export function getSchemaType(schema: Schema): string { + // if (schema.properties) { + // return schema.type + // } + if (schema.type) { + return schema.type; + } + return 'any'; +} diff --git a/src/openApi/v2/parser/getServices.ts b/src/openApi/v2/parser/getServices.ts index 16ee8ac3..f08b8b59 100644 --- a/src/openApi/v2/parser/getServices.ts +++ b/src/openApi/v2/parser/getServices.ts @@ -4,45 +4,44 @@ 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 { const services: Map = new Map(); - for (const url in openApi.paths) { if (openApi.paths.hasOwnProperty(url)) { const path: OpenApiPath = openApi.paths[url]; for (const method in path) { if (path.hasOwnProperty(method)) { - // Check supported methods switch (method) { - case 'get': - case 'put': - case 'post': - case 'delete': - case 'options': - case 'head': - case 'patch': + 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: OpenApiOperation = path[method]!; - const operation: Operation = getOperation(openApi, url, method, op); + // const op: OpenApiOperation = path[method]!; + // const operation: 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); + // 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); + // service.operations.push(operation); + // service.imports.push(...operation.imports); + // services.set(operation.service, service); break; } } diff --git a/src/openApi/v2/parser/getType.ts b/src/openApi/v2/parser/getType.ts index 53e12c75..b5d2f4dd 100644 --- a/src/openApi/v2/parser/getType.ts +++ b/src/openApi/v2/parser/getType.ts @@ -1,6 +1,7 @@ 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. @@ -9,8 +10,8 @@ import { getMappedType, hasMappedType } from './getMappedType'; */ export function getType(value: string | undefined, template: string | null = null): Type { const result: Type = { - type: 'any', - base: 'any', + type: PrimaryType.OBJECT, + base: PrimaryType.OBJECT, template: null, imports: [], }; @@ -52,7 +53,7 @@ export function getType(value: string | undefined, template: string | null = nul const mapped: string = getMappedType(valueClean); result.type = mapped; result.base = mapped; - } else { + } else if (valueClean) { result.type = valueClean; result.base = valueClean; result.imports.push(valueClean); diff --git a/src/openApi/v2/parser/isPrimaryType.spec.ts b/src/openApi/v2/parser/isPrimaryType.spec.ts index 86dcf76e..ed3e1774 100644 --- a/src/openApi/v2/parser/isPrimaryType.spec.ts +++ b/src/openApi/v2/parser/isPrimaryType.spec.ts @@ -6,7 +6,6 @@ describe('isPrimaryType', () => { 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(); diff --git a/src/openApi/v2/parser/isPrimaryType.ts b/src/openApi/v2/parser/isPrimaryType.ts index 9274763a..dfb9e778 100644 --- a/src/openApi/v2/parser/isPrimaryType.ts +++ b/src/openApi/v2/parser/isPrimaryType.ts @@ -1,16 +1,18 @@ +import { PrimaryType } from './constants'; + /** * Check if given type is a primary type. * @param type */ -export function isPrimaryType(type: string): boolean { +export function isPrimaryType(type: string): type is PrimaryType { switch (type.toLowerCase()) { - case 'number': - case 'boolean': - case 'string': - case 'object': - case 'any': - case 'void': - case 'null': + 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; diff --git a/src/templates/typescript/model.hbs b/src/templates/typescript/model.hbs index 10fcbd3b..59b5ef70 100644 --- a/src/templates/typescript/model.hbs +++ b/src/templates/typescript/model.hbs @@ -2,58 +2,37 @@ /* tslint:disable */ /* eslint-disable */ /* prettier-ignore */ - {{#if imports}} + {{#each imports}} import { {{{this}}} } from '../models/{{{this}}}'; {{/each}} {{/if}} -import { Dictionary } from '../core/Dictionary'; -import * as yup from 'yup'; - {{#if description}} + /** * {{{description}}} */ {{/if}} -{{#if properties}} -export interface {{{base}}}{{{template}}}{{#if extend}} extends {{{extend}}}{{/if}} { +{{#if isInterface}} +export interface {{{name}}}{{{template}}}{{#if extend}} extends {{{extend}}}{{/if}} { {{#each properties}} {{#if description}} /** - * {{{description}}} - */ + * {{{description}}} + */ {{/if}} {{#if readOnly}}readonly {{/if}}{{{name}}}{{#unless required}}?{{/unless}}: {{{type}}}{{#if nullable}} | null{{/if}}; {{/each}} } - {{/if}} -export namespace {{{base}}} { - - {{#each enums}} - {{#if description}} - /** - * {{{description}}} - */ - {{/if}} - export enum {{{name}}} { - {{#each values}} - {{{name}}} = {{{value}}}, - {{/each}} - }; - +{{#if isType}} +export type {{{name}}} = {{{type}}}{{#if nullable}} | null{{/if}}; +{{/if}} +{{#if isEnum}} +export enum {{{name}}} { + {{#each symbols}} + {{{name}}} = {{{value}}}, {{/each}} - - export const schema = yup.object<{{{base}}}{{{template}}}>().shape({ - // Add properties - }); - - export function validate(value: {{{base}}}{{{template}}}): Promise<{{{base}}}{{{template}}}> { - return schema.validate(value, { strict: true }); - } - - export function validateSync(value: {{{base}}}{{{template}}}): {{{base}}}{{{template}}} { - return schema.validateSync(value, { strict: true }); - } } +{{/if}} diff --git a/src/utils/getSortedModels.ts b/src/utils/getSortedModels.ts index 81656f4f..08e880ae 100644 --- a/src/utils/getSortedModels.ts +++ b/src/utils/getSortedModels.ts @@ -7,8 +7,8 @@ import { Model } from '../client/interfaces/Model'; export function getSortedModels(models: Map): Model[] { return ( Array.from(models.values()).sort((a, b) => { - const nameA: string = a.base.toLowerCase(); - const nameB: string = b.base.toLowerCase(); + const nameA: string = a.name.toLowerCase(); + const nameB: string = b.name.toLowerCase(); return nameA.localeCompare(nameB, 'en'); }) || [] ); diff --git a/src/utils/writeClientModels.ts b/src/utils/writeClientModels.ts index d6ef7c3e..37fb7d72 100644 --- a/src/utils/writeClientModels.ts +++ b/src/utils/writeClientModels.ts @@ -14,13 +14,13 @@ import { getFileName } from './getFileName'; */ export function writeClientModels(models: Model[], language: Language, template: handlebars.TemplateDelegate, outputPath: string): void { models.forEach(model => { - const fileName: string = getFileName(model.base, language); + 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? + // properties: Array.from(model.properties.values()), // TODO in cleanup? }) ); } catch (e) { diff --git a/test/index.js b/test/index.js index 6d023357..906fb0ca 100755 --- a/test/index.js +++ b/test/index.js @@ -5,32 +5,32 @@ const OpenAPI = require('../dist'); OpenAPI.generate( - './test/mock/v2/test-petstore.json', - './test/tmp/v2/ts/test-petstore', + './test/mock/v2/spec.json', + './test/tmp/v2/spec', OpenAPI.Language.TYPESCRIPT, OpenAPI.HttpClient.FETCH, ); -OpenAPI.generate( - './test/mock/v2/test-addon.json', - './test/tmp/v2/ts/test-addon', - OpenAPI.Language.TYPESCRIPT, - OpenAPI.HttpClient.FETCH, -); - -OpenAPI.generate( - './test/mock/v2/test-docs.json', - './test/tmp/v2/ts/test-docs', - OpenAPI.Language.TYPESCRIPT, - OpenAPI.HttpClient.FETCH, -); - -OpenAPI.generate( - './test/mock/v2/test-sites.json', - './test/tmp/v2/ts/test-sites', - OpenAPI.Language.TYPESCRIPT, - OpenAPI.HttpClient.FETCH, -); +// OpenAPI.generate( +// './test/mock/v2/test-addon.json', +// './test/tmp/v2/ts/test-addon', +// OpenAPI.Language.TYPESCRIPT, +// OpenAPI.HttpClient.FETCH, +// ); +// +// OpenAPI.generate( +// './test/mock/v2/test-docs.json', +// './test/tmp/v2/ts/test-docs', +// OpenAPI.Language.TYPESCRIPT, +// OpenAPI.HttpClient.FETCH, +// ); +// +// OpenAPI.generate( +// './test/mock/v2/test-sites.json', +// './test/tmp/v2/ts/test-sites', +// OpenAPI.Language.TYPESCRIPT, +// OpenAPI.HttpClient.FETCH, +// ); // OpenAPI.generate( // './test/mock/v2/test-petstore.yaml', diff --git a/test/mock/v2/spec.json b/test/mock/v2/spec.json new file mode 100644 index 00000000..7af3f6ba --- /dev/null +++ b/test/mock/v2/spec.json @@ -0,0 +1,181 @@ +{ + "swagger": "2.0", + "info": { + "version": "v9.0", + "title": "swagger" + }, + "host": "localhost:8080", + "basePath": "/api", + "schemes": [ + "http" + ], + "paths": { + }, + "definitions": { + "SimpleInteger": { + "description": "This is a simple number", + "type": "integer" + }, + "SimpleBoolean": { + "description": "This is a simple boolean", + "type": "boolean" + }, + "SimpleString": { + "description": "This is a simple string", + "type": "string" + }, + "SimpleEnumWithStrings": { + "description": "This is a simple enum with strings", + "enum": [ + "Success", + "Warning", + "Error" + ] + }, + "SimpleEnumWithNumbers": { + "description": "This is a simple enum with numbers", + "enum": [ + 1, + 2, + 3 + ] + }, + "SimpleEnumFromDescription": { + "description": "Success=1,Warning=2,Error=3", + "type": "int" + }, + "SimpleArrayWithNumbers": { + "description": "This is a simple array with numbers", + "type": "array", + "items": { + "type": "integer" + } + }, + "SimpleArrayWithBooleans": { + "description": "This is a simple array with booleans", + "type": "array", + "items": { + "type": "boolean" + } + }, + "SimpleArrayWithStrings": { + "description": "This is a simple array with strings", + "type": "array", + "items": { + "type": "string" + } + }, + "SimpleArrayWithReference": { + "description": "This is a simple array with a reference", + "type": "array", + "items": { + "$ref": "#/definitions/ModelWithString" + } + }, + "SimpleArrayWithProperties": { + "description": "This is a simple array with a properties", + "type": "array", + "items": { + "properties": { + "foo": { + "type": "string" + }, + "bar": { + "type": "string" + } + } + } + }, + "ModelWithInteger": { + "description": "This is a model with one number property", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple number property", + "type": "integer" + } + } + }, + "ModelWithBoolean": { + "description": "This is a model with one boolean property", + "type": "object", + "properties": { + "prop": { + "description": "This is a simple boolean property", + "type": "boolean" + } + } + }, + "ModelWithString": { + "type": "object", + "description": "This is a model with one string property", + "properties": { + "prop": { + "description": "This is a simple string property", + "type": "string" + } + } + }, + "ModelWithEnum": { + "type": "object", + "description": "This is a model with one enum", + "properties": { + "prop": { + "description": "This is a simple enum with strings", + "enum": [ + "Success", + "Warning", + "Error" + ] + } + } + }, + "ModelWithEnumFromDescription": { + "type": "object", + "description": "This is a model with one enum", + "properties": { + "prop": { + "type": "integer", + "description": "Success=1,Warning=2,Error=3" + } + } + }, + "ModelWithReference": { + "type": "object", + "description": "This is a model with one property containing a reference", + "properties": { + "prop": { + "$ref": "#/definitions/ModelWithString" + } + } + }, + "ModelWithCircularReference": { + "type": "object", + "description": "This is a model with one property containing a circular reference", + "properties": { + "prop": { + "$ref": "#/definitions/ModelWithCircularReference" + } + } + }, + "ModelWithNestedProperties": { + "type": "object", + "description": "This is a model with one nested property", + "properties": { + "first": { + "type": "object", + "properties": { + "second": { + "type": "object", + "properties": { + "third": { + "type": "string" + } + } + } + } + } + } + } + } +}