mirror of
https://github.com/ferdikoomen/openapi-typescript-codegen.git
synced 2025-12-08 20:16:21 +00:00
- Working on model parsing
This commit is contained in:
parent
b2aad89629
commit
ea0bfd94b3
12
src/client/interfaces/Schema.d.ts
vendored
12
src/client/interfaces/Schema.d.ts
vendored
@ -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<string, SchemaProperty>;
|
||||
symbols: Shape[]; // TODO: Betere naam!
|
||||
properties: SchemaProperty[];
|
||||
}
|
||||
|
||||
7
src/client/interfaces/SchemaProperty.d.ts
vendored
7
src/client/interfaces/SchemaProperty.d.ts
vendored
@ -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<string, SchemaProperty>;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
4
src/client/interfaces/Shape.ts
Normal file
4
src/client/interfaces/Shape.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface Shape {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
@ -27,6 +27,6 @@ export interface OpenApiParameter {
|
||||
maxItems?: number;
|
||||
minItems?: number;
|
||||
uniqueItems?: boolean;
|
||||
enum?: string[];
|
||||
enum?: (string | number)[];
|
||||
multipleOf?: number;
|
||||
}
|
||||
|
||||
2
src/openApi/v2/interfaces/OpenApiSchema.d.ts
vendored
2
src/openApi/v2/interfaces/OpenApiSchema.d.ts
vendored
@ -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)[];
|
||||
|
||||
44
src/openApi/v2/parser/constants.ts
Normal file
44
src/openApi/v2/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',
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
24
src/openApi/v2/parser/getEnumSymbols.ts
Normal file
24
src/openApi/v2/parser/getEnumSymbols.ts
Normal file
@ -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 [];
|
||||
}
|
||||
27
src/openApi/v2/parser/getEnumSymbolsFromDescription.ts
Normal file
27
src/openApi/v2/parser/getEnumSymbolsFromDescription.ts
Normal file
@ -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 [];
|
||||
}
|
||||
@ -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(' | ');
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -1,36 +1,10 @@
|
||||
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'],
|
||||
]);
|
||||
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());
|
||||
}
|
||||
|
||||
@ -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<string, Model> {
|
||||
const models: Map<string, Model> = new Map<string, Model>();
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -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: [],
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<string, SchemaProperty>(),
|
||||
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<OpenApiSchema>(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;
|
||||
|
||||
@ -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<OpenApiSchema>(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;
|
||||
|
||||
18
src/openApi/v2/parser/getSchemaType.ts
Normal file
18
src/openApi/v2/parser/getSchemaType.ts
Normal file
@ -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';
|
||||
}
|
||||
@ -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<string, Service> {
|
||||
const services: Map<string, Service> = new Map<string, Service>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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}}
|
||||
|
||||
@ -7,8 +7,8 @@ import { Model } from '../client/interfaces/Model';
|
||||
export function getSortedModels(models: Map<string, Model>): 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');
|
||||
}) || []
|
||||
);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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',
|
||||
|
||||
181
test/mock/v2/spec.json
Normal file
181
test/mock/v2/spec.json
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user