- Working on model parsing

This commit is contained in:
Ferdi Koomen 2019-11-15 00:56:32 +01:00
parent b2aad89629
commit ea0bfd94b3
27 changed files with 551 additions and 296 deletions

View File

@ -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[];
}

View File

@ -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;
}

View File

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

View File

@ -27,6 +27,6 @@ export interface OpenApiParameter {
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
enum?: string[];
enum?: (string | number)[];
multipleOf?: number;
}

View File

@ -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)[];

View 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',
}

View File

@ -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;
}

View 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 [];
}

View 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 [];
}

View File

@ -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(' | ');
}

View File

@ -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;
}

View File

@ -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());
}

View File

@ -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;
}

View File

@ -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: [],
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View 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';
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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}}

View File

@ -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');
}) || []
);

View File

@ -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) {

View File

@ -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
View 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"
}
}
}
}
}
}
}
}
}