- Added core template files

- Working on v2 parsing
This commit is contained in:
Ferdi Koomen 2019-11-07 01:17:39 +01:00
parent c4bc1e1787
commit 30e26621a5
127 changed files with 1460 additions and 698 deletions

View File

@ -74,6 +74,7 @@
"eslint": "6.6.0",
"eslint-config-prettier": "6.5.0",
"eslint-plugin-prettier": "3.1.1",
"glob": "7.1.6",
"handlebars": "4.5.1",
"jest": "24.9.0",
"jest-cli": "24.9.0",

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

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

View File

@ -1,11 +1,9 @@
import { Model } from './Model';
import { Service } from './Service';
import { Schema } from './Schema';
export interface Client {
version: string;
server: string;
models: Map<string, Model>;
schemas: Map<string, Schema>;
services: Map<string, Service>;
}

View File

@ -5,7 +5,7 @@ export interface Model {
name: string;
base: string;
type: string;
template?: string;
template: string | null;
description?: string;
extends: string[];
imports: string[];

View File

@ -2,7 +2,7 @@ export interface ModelProperty {
name: string;
type: string;
base: string;
template?: string;
template: string | null;
description?: string;
default?: any;
required: boolean;

21
src/client/interfaces/Operation.d.ts vendored Normal file
View File

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

View File

@ -0,0 +1,4 @@
export interface OperationError {
code: number;
text: string;
}

View File

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

View File

@ -0,0 +1,4 @@
export interface OperationResponse {
code: number;
text: string;
}

View File

@ -0,0 +1,4 @@
export interface OperationResult {
type: string;
imports: string[];
}

13
src/client/interfaces/Parameter.d.ts vendored Normal file
View File

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

View File

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

View File

@ -1,7 +1,7 @@
import { ServiceOperation } from './ServiceOperation';
import { Operation } from './Operation';
export interface Service {
name: string;
operations: ServiceOperation[];
operations: Operation[];
imports: string[];
}

View File

@ -1,23 +0,0 @@
import { ServiceOperationError } from './ServiceOperationError';
import { ServiceOperationParameter } from './ServiceOperationParameter';
import { Model } from './Model';
import { ServiceOperationResponse } from './ServiceOperationResponse';
export interface ServiceOperation {
name: string;
summary?: string;
description?: string;
deprecated?: boolean;
method: string;
path: string;
parameters: ServiceOperationParameter[];
parametersPath: ServiceOperationParameter[];
parametersQuery: ServiceOperationParameter[];
parametersForm: ServiceOperationParameter[];
parametersHeader: ServiceOperationParameter[];
parametersBody: ServiceOperationParameter | null;
models: Model[];
errors: ServiceOperationError[];
response: ServiceOperationResponse | null;
result: string;
}

View File

@ -1,4 +0,0 @@
export interface ServiceOperationError {
code: number;
text: string;
}

View File

@ -1,13 +0,0 @@
export interface ServiceOperationParameter {
name: string;
type: string;
base: string;
template: string;
description: string;
default?: any;
required: boolean;
nullable: boolean;
// extends: string[];
// imports: string[];
// properties: ModelProperty[];
}

View File

@ -1,5 +0,0 @@
export interface ServiceOperationResponse {
code: number;
text: string;
property: any;
}

View File

@ -31,12 +31,12 @@ export function generate(input: string, output: string, language: Language = Lan
const inputPath = path.resolve(process.cwd(), input);
const outputPath = path.resolve(process.cwd(), output);
console.log(chalk.bold.green('Generate:'));
console.log(chalk.grey(' Input:'), input);
console.log(chalk.grey(' Output:'), output);
console.log(chalk.grey(' Language:'), language);
console.log(chalk.grey(' HTTP client:'), httpClient);
console.log(os.EOL);
// console.log(chalk.bold.green('Generate:'));
// console.log(chalk.grey(' Input:'), input);
// console.log(chalk.grey(' Output:'), output);
// console.log(chalk.grey(' Language:'), language);
// console.log(chalk.grey(' HTTP client:'), httpClient);
// console.log(os.EOL);
try {
// Load the specification, read the OpenAPI version and load the

View File

@ -3,7 +3,6 @@ import { Client } from '../../client/interfaces/Client';
import { getServer } from './parser/getServer';
import { getServices } from './parser/getServices';
import { getModels } from './parser/getModels';
import { getSchemas } from './parser/getSchemas';
/**
* Parse the OpenAPI specification to a Client model that contains
@ -15,7 +14,6 @@ export function parse(openApi: OpenApi): Client {
version: openApi.info.version,
server: getServer(openApi),
models: getModels(openApi),
schemas: getSchemas(openApi),
services: getServices(openApi),
};
}

View File

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

View File

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

View File

@ -1,5 +1,6 @@
import { OpenApiItems } from './OpenApiItems';
import { OpenApiSchema } from './OpenApiSchema';
import { OpenApiReference } from './OpenApiReference';
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#parameterObject
@ -9,8 +10,8 @@ export interface OpenApiParameter {
in: 'path' | 'query' | 'header' | 'formData' | 'body';
description?: string;
required?: boolean;
schema?: OpenApiSchema;
type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'file';
schema?: OpenApiSchema & OpenApiReference;
type?: string;
format?: 'int32' | 'int64' | 'float' | 'double' | 'string' | 'boolean' | 'byte' | 'binary' | 'date' | 'date-time' | 'password';
allowEmptyValue?: boolean;
items?: OpenApiItems;
@ -26,6 +27,6 @@ export interface OpenApiParameter {
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
enum?: (string | number)[];
enum?: string[];
multipleOf?: number;
}

View File

@ -2,13 +2,14 @@ import { Dictionary } from '../../../utils/types';
import { OpenApiExample } from './OpenApiExample';
import { OpenApiHeader } from './OpenApiHeader';
import { OpenApiSchema } from './OpenApiSchema';
import { OpenApiReference } from './OpenApiReference';
/**
* https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#responseObject
*/
export interface OpenApiResponse {
description: string;
schema?: OpenApiSchema;
schema?: OpenApiSchema & OpenApiReference;
headers?: Dictionary<OpenApiHeader>;
examples?: OpenApiExample;
}

View File

@ -7,5 +7,5 @@ import { OpenApiResponse } from './OpenApiResponse';
export interface OpenApiResponses {
[httpcode: string]: OpenApiResponse & OpenApiReference;
default: OpenApiResponse & OpenApiReference;
default?: OpenApiResponse & OpenApiReference;
}

View File

@ -25,7 +25,7 @@ export interface OpenApiSchema {
maxProperties?: number;
minProperties?: number;
required?: string[];
enum?: (string | number)[];
enum?: string[];
type?: string;
items?: OpenApiSchema & OpenApiReference;
allOf?: (OpenApiSchema & OpenApiReference)[];

View File

@ -0,0 +1,45 @@
import { getType } from './getType';
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 {
let itemsType = 'any';
let itemsBase = 'any';
let itemsTemplate: string | null = null;
const itemsImports: string[] = [];
// If the parameter has a type than it can be a basic or generic type.
if (items.type) {
const itemsData: Type = getType(items.type);
itemsType = itemsData.type;
itemsBase = itemsData.base;
itemsTemplate = itemsData.template;
itemsImports.push(...itemsData.imports);
// 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 (items.type === 'array' && items.items) {
console.log('templated array', items.items);
// Parse the child types and create a correct Array type, for example "string[]" or "ActivityData[]"
// const child: ParsedProperty = parseProperty(parameter.items, template);
// parameterType = `${child.type}[]`;
// parameterBase = child.base;
// parameterTemplate = child.template;
// parameterImports.push(...child.imports);
}
}
if (items.enum) {
itemsType = getEnumType(items.enum, true);
}
return {
type: itemsType,
base: itemsBase,
template: itemsTemplate,
default: items.default,
imports: itemsImports,
};
}

View File

@ -0,0 +1,6 @@
export function getComment(comment: string | undefined): string | undefined {
if (comment) {
return comment.replace(/(\r\n|\n|\r)+/g, '$1 * ');
}
return undefined;
}

View File

@ -0,0 +1,20 @@
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, index, arr) => 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 'string';
}

View File

@ -0,0 +1,32 @@
export function getEnumTypeFromDescription(description: string, addParentheses = false): string | null {
// Check if we can find this special format string:
// None=0,Something=1,AnotherThing=2
const matches: RegExpMatchArray | null = description.match(/((\w+)=([0-9]+)(?:,|$))/g);
if (matches) {
// Grab the values from the description
const values: number[] = [];
for (let i = 0, n = matches.length; i < n; i++) {
const value = parseInt(matches[i].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, index, arr) => 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

@ -30,7 +30,7 @@ const MAPPINGS = new Map<string, string>([
* @param type
*/
export function getMappedType(type: string): string {
const mapped = MAPPINGS.get(type.toLowerCase());
const mapped: string | undefined = MAPPINGS.get(type.toLowerCase());
if (mapped) {
return mapped;
}

View File

@ -1,6 +1,4 @@
import { ModelProperties } from '../../../client/interfaces/ModelProperties';
export function parseModelProperties(): ModelProperties {
export function parseModelProperties(): any {
return {
imports: [],
properties: [],

View File

@ -1,49 +1,47 @@
import { Model } from '../../../client/interfaces/Model';
import { OpenApi } from '../interfaces/OpenApi';
import { getType } from './getType';
/**
* Parse and return the OpenAPI models.
* @param openApi
* Get the OpenAPI models.
*/
export function getModels(openApi: OpenApi): Map<string, Model> {
const models = new Map<string, Model>();
const models: Map<string, Model> = new Map<string, Model>();
// Iterate over the definitions
const definitions = openApi.definitions;
const { definitions } = openApi;
for (const definitionName in definitions) {
if (definitions.hasOwnProperty(definitionName)) {
const definition = definitions[definitionName];
const required = definition.required || [];
const modelClass = getType(definitionName);
// const definition: OpenApiSchema = openApi.definitions[definitionName];
// const required: string[] = definition.required || [];
// const modelClass: Type = getType(definitionName);
// Check if we haven't already parsed the model
if (!models.has(modelClass.base)) {
// // Create a new model object
// const model: Model = {
// name: modelClass.base,
// base: modelClass.base,
// type: modelClass.type,
// template: getModelTemplate(modelClass),
// description: null,
// extends: [],
// imports: [],
// properties: [],
// enums: [],
// };
//
// const properties = definition.properties;
// for (const propertyName in properties) {
// if (properties.hasOwnProperty(propertyName)) {
// const property = properties[propertyName];
// const propertyRequired = required.includes(propertyName);
// getModelProperty(propertyName, property);
// }
// }
//
// models.set(modelClass.base, model);
}
// if (!models.has(modelClass.base)) {
// // Create a new model object
// const model: Model = {
// name: modelClass.base,
// base: modelClass.base,
// type: modelClass.type,
// template: getModelTemplate(modelClass),
// description: null,
// extends: [],
// imports: [],
// properties: [],
// enums: [],
// };
//
// const properties = definition.properties;
// for (const propertyName in properties) {
// if (properties.hasOwnProperty(propertyName)) {
// const property = properties[propertyName];
// const propertyRequired = required.includes(propertyName);
// getModelProperty(propertyName, property);
// }
// }
//
// models.set(modelClass.base, model);
// }
}
}
return models;
}

View File

@ -0,0 +1,64 @@
import { Service } from '../../../client/interfaces/Service';
import { getServiceClassName } from './getServiceClassName';
import { Operation } from '../../../client/interfaces/Operation';
import { OpenApiOperation } from '../interfaces/OpenApiOperation';
import { getOperationName } from './getOperationName';
import { getOperationPath } from './getOperationPath';
import { getOperationParameters } from './getOperationParameters';
import { OpenApi } from '../interfaces/OpenApi';
import { getComment } from './getComment';
import { getOperationResponses } from './getOperationResponses';
import { OperationParameters } from '../../../client/interfaces/OperationParameters';
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
export function getOperation(openApi: OpenApi, url: string, method: string, op: OpenApiOperation): Operation {
const serviceName = (op.tags && op.tags[0]) || 'Service';
const serviceClassName = getServiceClassName(serviceName);
const operationNameFallback = `${method}${serviceClassName}`;
const operationName = getOperationName(op.operationId || operationNameFallback);
const operationPath = getOperationPath(url);
// Create a new operation object for this method.
const operation: Operation = {
service: serviceClassName,
name: operationName,
summary: getComment(op.summary),
description: getComment(op.description),
deprecated: op.deprecated,
method: method,
path: operationPath,
parameters: [],
parametersPath: [],
parametersQuery: [],
parametersForm: [],
parametersHeader: [],
parametersBody: null,
imports: [],
errors: [],
result: 'void',
};
// Parse the operation parameters (path, query, body, etc).
if (op.parameters) {
const parameters: OperationParameters = getOperationParameters(openApi, op.parameters);
operation.imports.push(...parameters.imports);
operation.parameters.push(...parameters.parameters);
operation.parametersPath.push(...parameters.parametersPath);
operation.parametersQuery.push(...parameters.parametersQuery);
operation.parametersForm.push(...parameters.parametersForm);
operation.parametersHeader.push(...parameters.parametersHeader);
operation.parametersBody = parameters.parametersBody;
}
// Parse the operation responses.
if (op.responses) {
const responses: OperationResponse[] = getOperationResponses(openApi, op.responses);
// const result: OperationResponse = getOperationResult(responses);
// const errors = getOperationErrors(responses);
// operation.imports.push(...result.imports);
// operation.errors = errors;
// operation.result = result.type;
}
return operation;
}

View File

@ -0,0 +1,11 @@
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { OperationError } from '../../../client/interfaces/OperationError';
export function getOperationErrors(responses: OperationResponse[]): OperationError[] {
return responses
.filter((response: OperationResponse): boolean => response.code >= 300 && response.text !== undefined && response.text !== '')
.map(response => ({
code: response.code,
text: response.text,
}));
}

View File

@ -0,0 +1,10 @@
import { getOperationName } from './getOperationName';
describe('getOperationName', () => {
it('should produce correct result', () => {
expect(getOperationName('')).toEqual('');
expect(getOperationName('FooBar')).toEqual('fooBar');
expect(getOperationName('Foo Bar')).toEqual('fooBar');
expect(getOperationName('foo bar')).toEqual('fooBar');
});
});

View File

@ -0,0 +1,11 @@
import camelCase from 'camelcase';
/**
* Convert the input value to a correct operation (method) classname.
* This converts the input string to camelCase, so the method name follows
* the most popular Javascript and Typescript writing style.
*/
export function getOperationName(value: string): string {
const clean = value.replace(/[^\w\s\-]+/g, '_').trim();
return camelCase(clean);
}

View File

@ -0,0 +1,74 @@
import { OpenApiParameter } from '../interfaces/OpenApiParameter';
import { OperationParameters } from '../../../client/interfaces/OperationParameters';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { Parameter } from '../../../client/interfaces/Parameter';
import { getParameter } from './getParameter';
import { OpenApi } from '../interfaces/OpenApi';
import { getRef } from './getRef';
function sortByRequired(a: Parameter, b: Parameter): number {
return a.required && !b.required ? -1 : !a.required && b.required ? 1 : 0;
}
export function getOperationParameters(openApi: OpenApi, parametersOrReferences: (OpenApiParameter & OpenApiReference)[]): OperationParameters {
const imports: string[] = [];
const parameters: Parameter[] = [];
const parametersPath: Parameter[] = [];
const parametersQuery: Parameter[] = [];
const parametersForm: Parameter[] = [];
const parametersHeader: Parameter[] = [];
let parametersBody: Parameter | null = null;
// Iterate over the parameters
for (let i = 0, n = parametersOrReferences.length; i < n; i++) {
const parameterOrReference: OpenApiParameter & OpenApiReference = parametersOrReferences[i];
const parameter: OpenApiParameter = getRef<OpenApiParameter>(openApi, parameterOrReference);
const param: Parameter = getParameter(openApi, parameter);
// We ignore the "api-version" param, since we do not want to add this
// as the first / default parameter for each of the service calls.
if (param.prop !== 'api-version') {
switch (parameter.in) {
case 'path':
parametersPath.push(param);
parameters.push(param);
imports.push(...param.imports);
break;
case 'query':
parametersQuery.push(param);
parameters.push(param);
imports.push(...param.imports);
break;
case 'header':
parametersHeader.push(param);
parameters.push(param);
imports.push(...param.imports);
break;
case 'formData':
parametersForm.push(param);
parameters.push(param);
imports.push(...param.imports);
break;
case 'body':
parametersBody = param;
parameters.push(param);
imports.push(...param.imports);
break;
}
}
}
return {
imports,
parameters: parameters.sort(sortByRequired),
parametersPath: parametersPath.sort(sortByRequired),
parametersQuery: parametersQuery.sort(sortByRequired),
parametersForm: parametersForm.sort(sortByRequired),
parametersHeader: parametersHeader.sort(sortByRequired),
parametersBody: parametersBody,
};
}

View File

@ -0,0 +1,10 @@
import { getOperationPath } from './getOperationPath';
describe('getOperationPath', () => {
it('should produce correct result', () => {
expect(getOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}');
expect(getOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getOperationPath('/api/v1/list')).toEqual('/api/v1/list');
});
});

View File

@ -4,6 +4,6 @@
* OpenAPI version without the need to hardcode this in the URL.
* @param path
*/
export function getServicePath(path: string): string {
export function getOperationPath(path: string): string {
return path.replace(/{api-version}/g, '{OpenAPI.VERSION}').replace(/\{(.*?)\}/g, '${$1}');
}

View File

@ -1,4 +1,4 @@
export function getServiceOperationResponsesCode(value: string | 'default'): number | null {
export function getOperationResponseCode(value: string | 'default'): number | null {
// You can specify a "default" response, this is treated as HTTP code 200
if (value === 'default') {
return 200;
@ -6,7 +6,7 @@ export function getServiceOperationResponsesCode(value: string | 'default'): num
// Check if we can parse the code and return of successful.
if (/[0-9]+/g.test(value)) {
const code = parseInt(value);
const code: number = parseInt(value);
if (Number.isInteger(code)) {
return code;
}

View File

@ -0,0 +1,39 @@
import { OpenApiResponses } from '../interfaces/OpenApiResponses';
import { getOperationResponseCode } from './getOperationResponseCode';
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
import { OpenApiResponse } from '../interfaces/OpenApiResponse';
import { OpenApiReference } from '../interfaces/OpenApiReference';
import { getRef } from './getRef';
import { OpenApi } from '../interfaces/OpenApi';
export function getOperationResponses(openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] {
const result: OperationResponse[] = [];
// Iterate over each response code and get the
// status code and response message (if any).
for (const code in responses) {
if (responses.hasOwnProperty(code)) {
const responseOrReference: OpenApiResponse & OpenApiReference = responses[code];
const response: OpenApiResponse = getRef<OpenApiResponse>(openApi, responseOrReference);
const responseCode: number | null = getOperationResponseCode(code);
const responseText: string = response.description || '';
// TODO:
if (response.schema) {
console.log('response.schema', response.schema);
}
if (responseCode) {
result.push({
code: responseCode,
text: responseText,
});
}
}
}
// Sort the responses to 2XX success codes come before 4XX and 5XX error codes.
return result.sort((a, b): number => {
return a.code < b.code ? -1 : a.code > b.code ? 1 : 0;
});
}

View File

@ -0,0 +1,19 @@
import { OperationResponse } from '../../../client/interfaces/OperationResponse';
export function getOperationResult(responses: OperationResponse[]): OperationResponse {
const resultCode = 200;
const resultTes: string[] = [];
// Fetch the first valid (2XX range) response code and return that type.
const result: OperationResponse | undefined = responses.find(response => response.code && response.code >= 200 && response.code < 300 && response.property);
if (result && result.property) {
resultType = result.property.type;
resultImports = [...result.property.imports];
}
return {
type: resultType,
imports: resultImports,
};
}

View File

@ -0,0 +1,72 @@
import { OpenApiParameter } from '../interfaces/OpenApiParameter';
import { getType } from './getType';
import { Parameter } from '../../../client/interfaces/Parameter';
import { Type } from '../../../client/interfaces/Type';
import { OpenApi } from '../interfaces/OpenApi';
import { getParameterName } from './getParameterName';
import { getArrayType } from './getArrayType';
import { ArrayType } from '../../../client/interfaces/ArrayType';
import { getEnumType } from './getEnumType';
import { getEnumTypeFromDescription } from './getEnumTypeFromDescription';
import { getComment } from './getComment';
export function getParameter(openApi: OpenApi, parameter: OpenApiParameter): Parameter {
let parameterType = 'any';
let parameterBase = 'any';
let parameterTemplate: string | null = null;
const parameterImports: string[] = [];
// If the parameter has a type than it can be a basic or generic type.
if (parameter.type) {
const parameterData: Type = getType(parameter.type);
parameterType = parameterData.type;
parameterBase = parameterData.base;
parameterTemplate = parameterData.template;
parameterImports.push(...parameterData.imports);
// 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);
parameterType = `${arrayType.type}[]`;
parameterBase = arrayType.base;
parameterTemplate = arrayType.template;
parameterImports.push(...arrayType.imports);
}
}
// If this parameter has a schema, then we should treat it as an embedded parameter.
// We can just parse the schema name ($ref) and use that as the parameter type.
if (parameter.schema) {
// TODO: console.log('parameter.schema', parameter.schema);
}
// If the param is a enum then return the values as an inline type.
if (parameter.enum) {
parameterType = getEnumType(parameter.enum);
parameterBase = 'string';
}
// 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) {
parameterType = enumType;
parameterBase = 'number';
}
}
return {
in: parameter.in,
prop: parameter.name,
name: getParameterName(parameter.name),
type: parameterType,
base: parameterBase,
template: parameterTemplate,
description: getComment(parameter.description),
default: parameter.default,
required: parameter.required || false,
nullable: false,
imports: parameterImports,
};
}

View File

@ -0,0 +1,10 @@
import camelCase from 'camelcase';
/**
* Replaces any invalid characters from a parameter name.
* For example: 'filter.someProperty' becomes 'filterSomeProperty'.
*/
export function getParameterName(value: string): string {
const clean = value.replace(/[^\w\s\-]+/g, '_').trim();
return camelCase(clean);
}

View File

@ -0,0 +1,27 @@
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiReference } from '../interfaces/OpenApiReference';
export function getRef<T>(openApi: OpenApi, item: T & OpenApiReference): T {
if (item.$ref) {
// Fetch the paths to the definitions, this converts:
// "#/definitions/Form" to ["definitions", "Form"]
const paths = item.$ref
.replace(/^#/g, '')
.split('/')
.filter(item => item);
// Try to find the reference by walking down the path,
// if we cannot find it, then we throw an error.
let result: any = openApi;
for (let i = 0, n = paths.length; i < n; i++) {
const path: string = paths[i];
if (result.hasOwnProperty(path)) {
result = result[path];
} else {
throw new Error(`Could not find reference: "${item.$ref}"`);
}
}
return result as T;
}
return item as T;
}

View File

@ -1,11 +0,0 @@
import { Schema } from '../../../client/interfaces/Schema';
import { OpenApi } from '../interfaces/OpenApi';
/**
* Parse and return the OpenAPI schemas.
* @param openApi
*/
export function getSchemas(openApi: OpenApi): Map<string, Schema> {
const schemas = new Map<string, Schema>();
return schemas;
}

View File

@ -4,9 +4,15 @@ describe('getServer', () => {
it('should produce correct result', () => {
expect(
getServer({
swagger: '2.0',
info: {
title: 'dummy',
version: '1.0',
},
host: 'localhost:8080',
basePath: '/api',
schemes: ['http', 'https'],
paths: {},
})
).toEqual('http://localhost:8080/api');
});

View File

@ -1,8 +1,10 @@
import { OpenApi } from '../interfaces/OpenApi';
type Props = Pick<OpenApi, 'schemes' | 'host' | 'basePath'>;
export function getServer(openApi: Props): string {
/**
* Get the base server url.
* @param openApi
*/
export function getServer(openApi: OpenApi): string {
const scheme = (openApi.schemes && openApi.schemes[0]) || 'http';
const host = openApi.host;
const basePath = openApi.basePath || '';

View File

@ -3,10 +3,10 @@ import camelCase from 'camelcase';
/**
* Convert the input value to a correct service classname. This converts
* the input string to PascalCase and appends the "Service" prefix if needed.
* @param value
*/
export function getServiceClassName(value: string): string {
const name = camelCase(value, { pascalCase: true });
const clean = value.replace(/[^\w\s\-]+/g, '_').trim();
const name: string = camelCase(clean, { pascalCase: true });
if (name && !name.endsWith('Service')) {
return `${name}Service`;
}

View File

@ -1,16 +0,0 @@
import { ServiceOperationResponse } from '../../../client/interfaces/ServiceOperationResponse';
import { ServiceOperationError } from '../../../client/interfaces/ServiceOperationError';
/**
* Get list of service errors.
* @param responses List of parsed service responses.
* @returns List of service errors containing the error code and message.
*/
export function getServiceOperationErrors(responses: ServiceOperationResponse[]): ServiceOperationError[] {
return responses
.filter((response: ServiceOperationResponse): boolean => response.code >= 300 && response.text !== undefined && response.text !== '')
.map(response => ({
code: response.code,
text: response.text,
}));
}

View File

@ -1,10 +0,0 @@
import { getServiceOperationName } from './getServiceOperationName';
describe('getServiceOperationName', () => {
it('should produce correct result', () => {
expect(getServiceOperationName('')).toEqual('');
expect(getServiceOperationName('FooBar')).toEqual('fooBar');
expect(getServiceOperationName('Foo Bar')).toEqual('fooBar');
expect(getServiceOperationName('foo bar')).toEqual('fooBar');
});
});

View File

@ -1,11 +0,0 @@
import camelCase from 'camelcase';
/**
* Convert the input value to a correct operation (method) classname. This converts
* the input string to cascalCase, so the method name follows the most popular
* Javascript and Typescript writing style.
* @param value
*/
export function getServiceOperationName(value: string): string {
return camelCase(value);
}

View File

@ -1,10 +0,0 @@
import { getServiceOperationPath } from './getServiceOperationPath';
describe('getServiceOperationPath', () => {
it('should produce correct result', () => {
expect(getServiceOperationPath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}');
expect(getServiceOperationPath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getServiceOperationPath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getServiceOperationPath('/api/v1/list')).toEqual('/api/v1/list');
});
});

View File

@ -1,9 +0,0 @@
/**
* Get the final service path, this replaces the "{api-version}" placeholder
* with a new template string placeholder so we can dynamically inject the
* OpenAPI version without the need to hardcode this in the URL.
* @param path
*/
export function getServiceOperationPath(path: string): string {
return path.replace(/{api-version}/g, '{OpenAPI.VERSION}').replace(/\{(.*?)\}/g, '${$1}');
}

View File

@ -1,35 +0,0 @@
import { OpenApiResponses } from '../interfaces/OpenApiResponses';
import { ServiceOperationResponse } from '../../../client/interfaces/ServiceOperationResponse';
import { getServiceOperationResponsesCode } from './getServiceOperationResponseCode';
/**
* Parse the service response object into a list with status codes and response messages.
* @param responses Swagger responses.
* @returns List of status codes and response messages.
*/
export function getServiceOperationResponses(responses: OpenApiResponses): ServiceOperationResponse[] {
const result: ServiceOperationResponse[] = [];
// Iterate over each response code.
for (const code in responses) {
if (responses.hasOwnProperty(code)) {
// Get the status code and response message (if any).
const response = responses[code];
const responseCode = getServiceOperationResponsesCode(code);
const responseText = response.description || '';
if (responseCode) {
result.push({
code: responseCode,
text: responseText,
property: undefined,
});
}
}
}
// Sort the responses to 2XX success codes come before 4XX and 5XX error codes.
return result.sort((a, b): number => {
return a.code < b.code ? -1 : a.code > b.code ? 1 : 0;
});
}

View File

@ -1,29 +0,0 @@
import { ServiceOperationResponse } from '../../../client/interfaces/ServiceOperationResponse';
export interface ServiceOperationResult {
type: string;
imports: string[];
}
/**
* Parse service result.
* @param responses List of service responses.
* @returns Object containing the result type and needed imports.
*/
export function getServiceOperationResult(responses: ServiceOperationResponse[]): ServiceOperationResult {
let resultType = 'any';
let resultImports: string[] = [];
// Fetch the first valid (2XX range) response code and return that type.
const result = responses.find(response => response.code && response.code >= 200 && response.code < 300 && response.property);
if (result) {
resultType = result.property.type;
resultImports = [...result.property.imports];
}
return {
type: resultType,
imports: resultImports,
};
}

View File

@ -1,78 +1,54 @@
import { Service } from '../../../client/interfaces/Service';
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiPath } from '../interfaces/OpenApiPath';
import { OpenApiOperation } from '../interfaces/OpenApiOperation';
import { getServiceClassName } from './getServiceClassName';
import { getServiceOperationName } from './getServiceOperationName';
import { getServiceOperationPath } from './getServiceOperationPath';
import { ServiceOperation } from '../../../client/interfaces/ServiceOperation';
import { getServiceOperationResponses } from './getServiceOperationResponses';
import { getServiceOperationResult } from './getServiceOperationResult';
import { getServiceOperationErrors } from './getServiceOperationErrors';
function getMethod(url: string, services: Map<string, Service>, op: OpenApiOperation, method: string): void {
const serviceName = (op.tags && op.tags[0]) || 'Service';
const serviceClassName: string = getServiceClassName(serviceName);
const serviceOperationNameFallback = `${method}${serviceClassName}`;
const serviceOperationName: string = getServiceOperationName(op.operationId || serviceOperationNameFallback);
const servicePath: string = getServiceOperationPath(url);
// 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(serviceClassName) ||
({
name: serviceClassName,
imports: [],
operations: [],
} as Service);
// Create a new operation object for this method.
const operation: ServiceOperation = {
name: serviceOperationName,
summary: op.summary,
description: op.description,
deprecated: op.deprecated,
method: method,
path: servicePath,
parameters: [],
parametersPath: [],
parametersQuery: [],
parametersForm: [],
parametersHeader: [],
parametersBody: null,
models: [],
errors: [],
response: null,
result: 'any',
};
if (op.responses) {
const responses = getServiceOperationResponses(op.responses);
const result = getServiceOperationResult(responses);
operation.errors = getServiceOperationErrors(responses);
operation.result = result.type;
service.imports.push(...result.imports);
}
service.operations.push(operation);
services.set(serviceClassName, service);
}
import { getOperation } from './getOperation';
import { Operation } from '../../../client/interfaces/Operation';
/**
* Parse and return the OpenAPI services.
* @param openApi
* Get the OpenAPI services
*/
export function getServices(openApi: OpenApi): Map<string, Service> {
const services = new Map<string, Service>();
Object.keys(openApi.paths).forEach(url => {
const path = openApi.paths[url];
path.get && getMethod(url, services, path.get, 'get');
path.put && getMethod(url, services, path.put, 'put');
path.post && getMethod(url, services, path.post, 'post');
path.delete && getMethod(url, services, path.delete, 'delete');
path.options && getMethod(url, services, path.options, 'options');
path.head && getMethod(url, services, path.head, 'head');
path.patch && getMethod(url, services, path.patch, 'patch');
});
const services: Map<string, Service> = new Map<string, Service>();
const { paths } = openApi;
for (const url in paths) {
if (paths.hasOwnProperty(url)) {
const path: OpenApiPath = 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':
// Each method contains an OpenAPI operation, we parse the operation
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);
// Push the operation in the service
service.operations.push(operation);
service.imports.push(...operation.imports);
services.set(operation.service, service);
break;
}
}
}
}
}
return services;
}

View File

@ -3,7 +3,7 @@ import { Type } from '../../../client/interfaces/Type';
import { getMappedType, hasMappedType } from './getMappedType';
/**
* Parse any value into a type object.
* Parse any string value into a type object.
* @param value String value like "integer" or "Link[Model]".
* @param template Optional template class from parent (needed to process generics)
*/
@ -14,16 +14,15 @@ export function getType(value: string, template: string | null = null): Type {
let propertyImports: string[] = [];
// Remove definitions prefix and cleanup string.
const valueTrimmed = stripNamespace(value || '');
const valueTrimmed: string = stripNamespace(value || '');
// Check of we have an Array type or generic type, for instance: "Link[Model]".
if (/\[.*\]$/g.test(valueTrimmed)) {
// Find the first and second type
const match = valueTrimmed.match(/(.*?)\[(.*)\]$/);
if (match) {
const matches: RegExpMatchArray | null = valueTrimmed.match(/(.*?)\[(.*)\]$/);
if (matches) {
// Both of the types can be complex types so parse each of them.
const match1 = getType(match[1]);
const match2 = getType(match[2]);
const match1: Type = getType(matches[1]);
const match2: Type = getType(matches[2]);
// If the first match is a generic array then construct a correct array type,
// for example the type "Array[Model]" becomes "Model[]".
@ -48,7 +47,7 @@ export function getType(value: string, template: string | null = null): Type {
propertyImports.push(...match2.imports);
}
} else if (hasMappedType(valueTrimmed)) {
const mapped = getMappedType(valueTrimmed);
const mapped: string = getMappedType(valueTrimmed);
propertyType = mapped;
propertyBase = mapped;
} else {

View File

@ -3,7 +3,6 @@ import { Client } from '../../client/interfaces/Client';
import { getServer } from './parser/getServer';
import { getModels } from './parser/getModels';
import { getServices } from './parser/getServices';
import { getSchemas } from './parser/getSchemas';
/**
* Parse the OpenAPI specification to a Client model that contains
@ -15,7 +14,6 @@ export function parse(openApi: OpenApi): Client {
version: openApi.info.version,
server: getServer(openApi),
models: getModels(openApi),
schemas: getSchemas(openApi),
services: getServices(openApi),
};
}

View File

@ -23,7 +23,7 @@ export interface OpenApiSchema {
maxProperties?: number;
minProperties?: number;
required?: string[];
enum?: (string | number)[];
enum?: string[];
type?: string;
allOf?: (OpenApiSchema & OpenApiReference)[];
oneOf?: (OpenApiSchema & OpenApiReference)[];

View File

@ -30,7 +30,7 @@ const MAPPINGS = new Map<string, string>([
* @param type
*/
export function getMappedType(type: string): string {
const mapped = MAPPINGS.get(type.toLowerCase());
const mapped: string | undefined = MAPPINGS.get(type.toLowerCase());
if (mapped) {
return mapped;
}

View File

@ -6,6 +6,6 @@ import { OpenApi } from '../interfaces/OpenApi';
* @param openApi
*/
export function getModels(openApi: OpenApi): Map<string, Model> {
const models = new Map<string, Model>();
const models: Map<string, Model> = new Map<string, Model>();
return models;
}

View File

@ -1,11 +0,0 @@
import { Schema } from '../../../client/interfaces/Schema';
import { OpenApi } from '../interfaces/OpenApi';
/**
* Parse and return the OpenAPI schemas.
* @param openApi
*/
export function getSchemas(openApi: OpenApi): Map<string, Schema> {
const schemas = new Map<string, Schema>();
return schemas;
}

View File

@ -4,6 +4,12 @@ describe('getServer', () => {
it('should produce correct result', () => {
expect(
getServer({
openapi: '3.0',
info: {
title: 'dummy',
version: '1.0',
},
paths: {},
servers: [
{
url: 'https://localhost:8080/api',
@ -16,6 +22,12 @@ describe('getServer', () => {
it('should produce correct result with variables', () => {
expect(
getServer({
openapi: '3.0',
info: {
title: 'dummy',
version: '1.0',
},
paths: {},
servers: [
{
url: '{scheme}://localhost:{port}/api',

View File

@ -1,13 +1,16 @@
import { OpenApi } from '../interfaces/OpenApi';
import { OpenApiServer } from '../interfaces/OpenApiServer';
import { Dictionary } from '../../../utils/types';
import { OpenApiServerVariable } from '../interfaces/OpenApiServerVariable';
type Props = Pick<OpenApi, 'servers'>;
export function getServer(openApi: Props): string {
const server = openApi.servers && openApi.servers[0];
const variables = (server && server.variables) || {};
let url = (server && server.url) || '';
Object.entries(variables).forEach(variable => {
url = url.replace(`{${variable[0]}}`, variable[1].default);
});
export function getServer(openApi: OpenApi): string {
const server: OpenApiServer | undefined = openApi.servers && openApi.servers[0];
const variables: Dictionary<OpenApiServerVariable> = (server && server.variables) || {};
let url: string = (server && server.url) || '';
for (const variable in variables) {
if (variables.hasOwnProperty(variable)) {
url = url.replace(`{${variable}}`, variables[variable].default);
}
}
return url;
}

View File

@ -1,13 +0,0 @@
import { getServiceClassName } from './getServiceClassName';
describe('getServiceClassName', () => {
it('should produce correct result', () => {
expect(getServiceClassName('')).toEqual('');
expect(getServiceClassName('FooBar')).toEqual('FooBarService');
expect(getServiceClassName('Foo Bar')).toEqual('FooBarService');
expect(getServiceClassName('foo bar')).toEqual('FooBarService');
expect(getServiceClassName('FooBarService')).toEqual('FooBarService');
expect(getServiceClassName('Foo Bar Service')).toEqual('FooBarService');
expect(getServiceClassName('foo bar service')).toEqual('FooBarService');
});
});

View File

@ -1,14 +0,0 @@
import camelCase from 'camelcase';
/**
* Convert the input value to a correct service classname. This converts
* the input string to PascalCase and appends the "Service" prefix if needed.
* @param value
*/
export function getServiceClassName(value: string): string {
const name = camelCase(value, { pascalCase: true });
if (name && !name.endsWith('Service')) {
return `${name}Service`;
}
return name;
}

View File

@ -1,10 +0,0 @@
import { getServiceOperationName } from './getServiceOperationName';
describe('getServiceOperationName', () => {
it('should produce correct result', () => {
expect(getServiceOperationName('')).toEqual('');
expect(getServiceOperationName('FooBar')).toEqual('fooBar');
expect(getServiceOperationName('Foo Bar')).toEqual('fooBar');
expect(getServiceOperationName('foo bar')).toEqual('fooBar');
});
});

View File

@ -1,11 +0,0 @@
import camelCase from 'camelcase';
/**
* Convert the input value to a correct operation (method) classname. This converts
* the input string to cascalCase, so the method name follows the most popular
* Javascript and Typescript writing style.
* @param value
*/
export function getServiceOperationName(value: string): string {
return camelCase(value);
}

View File

@ -1,10 +0,0 @@
import { getServicePath } from './getServicePath';
describe('getServicePath', () => {
it('should produce correct result', () => {
expect(getServicePath('/api/v{api-version}/list/{id}/{type}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}/${type}');
expect(getServicePath('/api/v{api-version}/list/{id}')).toEqual('/api/v${OpenAPI.VERSION}/list/${id}');
expect(getServicePath('/api/v1/list/{id}')).toEqual('/api/v1/list/${id}');
expect(getServicePath('/api/v1/list')).toEqual('/api/v1/list');
});
});

View File

@ -1,9 +0,0 @@
import { getServiceVersion } from './getServiceVersion';
describe('getServiceVersion', () => {
it('should produce correct result', () => {
expect(getServiceVersion('1.0')).toEqual('1.0');
expect(getServiceVersion('v1.0')).toEqual('1.0');
expect(getServiceVersion('V1.0')).toEqual('1.0');
});
});

View File

@ -1,8 +0,0 @@
/**
* Convert the service version to 'normal' version.
* This basically removes any "v" prefix from the version string.
* @param version
*/
export function getServiceVersion(version = '1.0'): string {
return version.replace(/^v/gi, '');
}

View File

@ -6,6 +6,6 @@ import { OpenApi } from '../interfaces/OpenApi';
* @param openApi
*/
export function getServices(openApi: OpenApi): Map<string, Service> {
const services = new Map<string, Service>();
const services: Map<string, Service> = new Map<string, Service>();
return services;
}

View File

@ -14,16 +14,16 @@ export function getType(value: string, template: string | null = null): Type {
let propertyImports: string[] = [];
// Remove definitions prefix and cleanup string.
const valueTrimmed = stripNamespace(value || '');
const valueTrimmed: string = stripNamespace(value || '');
// Check of we have an Array type or generic type, for instance: "Link[Model]".
if (/\[.*\]$/g.test(valueTrimmed)) {
// Find the first and second type
const match = valueTrimmed.match(/(.*?)\[(.*)\]$/);
const match: RegExpMatchArray | null = valueTrimmed.match(/(.*?)\[(.*)\]$/);
if (match) {
// Both of the types can be complex types so parse each of them.
const match1 = getType(match[1]);
const match2 = getType(match[2]);
const match1: Type = getType(match[1]);
const match2: Type = getType(match[2]);
// If the first match is a generic array then construct a correct array type, for example:
// The type "Array[Model]" becomes "Model[]".
@ -48,7 +48,7 @@ export function getType(value: string, template: string | null = null): Type {
propertyImports.push(...match2.imports);
}
} else if (hasMappedType(valueTrimmed)) {
const mapped = getMappedType(valueTrimmed);
const mapped: string = getMappedType(valueTrimmed);
propertyType = mapped;
propertyBase = mapped;
} else {

View File

@ -0,0 +1,51 @@
/* istanbul ignore file */
/* eslint-disable */
import { isSuccess } from "./isSuccess";
export class ApiError extends Error {
constructor(result, message) {
super(message);
this.url = result.url;
this.status = result.status;
this.statusText = result.statusText;
this.body = result.body;
}
}
(function (ApiError) {
let Message;
(function (Message) {
Message.BAD_REQUEST = 'Bad Request';
Message.UNAUTHORIZED = 'Unauthorized';
Message.FORBIDDEN = 'Forbidden';
Message.NOT_FOUND = 'Not Found';
Message.INTERNAL_SERVER_ERROR = 'Internal Server Error';
Message.BAD_GATEWAY = 'Bad Gateway';
Message.SERVICE_UNAVAILABLE = 'Service Unavailable';
Message.GENERIC_ERROR = 'Generic Error';
})(Message = ApiError.Message || (ApiError.Message = {}));
})(ApiError || (ApiError = {}));
/**
* Catch common errors (based on status code).
* @param result
*/
export function catchGenericError(result) {
switch (result.status) {
case 400: throw new ApiError(result, ApiError.Message.BAD_REQUEST);
case 401: throw new ApiError(result, ApiError.Message.UNAUTHORIZED);
case 403: throw new ApiError(result, ApiError.Message.FORBIDDEN);
case 404: throw new ApiError(result, ApiError.Message.NOT_FOUND);
case 500: throw new ApiError(result, ApiError.Message.INTERNAL_SERVER_ERROR);
case 502: throw new ApiError(result, ApiError.Message.BAD_GATEWAY);
case 503: throw new ApiError(result, ApiError.Message.SERVICE_UNAVAILABLE);
}
if (!isSuccess(result.status)) {
throw new ApiError(result, ApiError.Message.GENERIC_ERROR);
}
}

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* eslint-disable */
export let OpenAPI;
(function (OpenAPI) {
OpenAPI.BASE = '';
OpenAPI.TOKEN = '';
OpenAPI.VERSION = '{VERSION}';
})(OpenAPI || (OpenAPI = {}));

View File

@ -0,0 +1,20 @@
/* istanbul ignore file */
/* eslint-disable */
/**
* Get FormData from object. This method is needed to upload
* multipart form data to the REST API.
* @param params Key value based object.
*/
export function getFormData(params) {
const formData = new FormData();
for (const key in params) {
if (typeof params[key] !== 'undefined') {
const value = params[key];
if (value !== undefined && value !== null) {
formData.append(key, value);
}
}
}
return formData;
}

View File

@ -0,0 +1,29 @@
/* istanbul ignore file */
/* eslint-disable */
/**
* Get query string from query parameters object. This method also
* supports multi-value items by creating a key for each item.
* @param params Key value based object.
*/
export function getQueryString(params) {
const qs = [];
for (const key in params) {
if (typeof params[key] !== 'undefined') {
const value = params[key];
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
for (let i = 0, n = value.length; i < n; i++) {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value[i]))}`);
}
} else {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
}
}
}
if (qs.length > 0) {
return `?${qs.join('&')}`;
}
return '';
}

View File

@ -0,0 +1,10 @@
/* istanbul ignore file */
/* eslint-disable */
/**
* Check success response code.
* @param status Status code
*/
export function isSuccess(status) {
return (status >= 200 && status < 300);
}

View File

@ -0,0 +1,13 @@
/* istanbul ignore file */
/* eslint-disable */
/**
* Check if a parameter is valid.
* @param param The parameter value.
* @param name The parameter name.
*/
export function isValidRequiredParam(param, name) {
if (param === undefined || param === null) {
throw new Error(`Required parameter '${name}' was undefined or null.`);
}
}

View File

@ -0,0 +1,74 @@
/* istanbul ignore file */
/* eslint-disable */
import {getFormData} from './getFormData';
import {getQueryString} from './getQueryString';
import {OpenAPI} from './OpenAPI';
import {requestUsingFetch} from './requestUsingFetch';
/**
* Create the request.
* @param options Request method options.
* @returns Result object (see above)
*/
export async function request<T>(options) {
// Create the request URL
let url = `${OpenAPI.BASE}${options.path}`;
// Create request headers
const headers = new Headers({
...options.headers,
Accept: 'application/json',
});
// Create request settings
const request = {
headers,
method: options.method,
credentials: 'same-origin',
};
// If we have a bearer token then we set the authentication header.
if (OpenAPI.TOKEN !== null && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
}
// Add the query parameters (if defined).
if (options.query) {
url += getQueryString(options.query);
}
// Append formData as body
if (options.formData) {
request.body = getFormData(options.formData);
} else if (options.body) {
// If this is blob data, then pass it directly to the body and set content type.
// Otherwise we just convert request data to JSON string (needed for fetch api)
if (options.body instanceof Blob) {
request.body = options.body;
if (options.body.type) {
headers.append('Content-Type', options.body.type);
}
} else {
request.body = JSON.stringify(options.body);
headers.append('Content-Type', 'application/json');
}
}
try {
return await requestUsingFetch(url, request);
} catch (error) {
return {
url,
ok: false,
status: 0,
statusText: '',
body: error,
};
}
}

View File

@ -0,0 +1,47 @@
/* istanbul ignore file */
/* eslint-disable */
/**
* Request content using the new Fetch API. This is the default API that is used and
* is create for all JSON, XML and text objects. However it is limited to UTF-8.
* This is a problem for some of the Docs content, since that requires UTF-16!
* @param url The url to request.
* @param request The request object, containing method, headers, body, etc.
*/
export async function requestUsingFetch<T>(url, request) {
// Fetch response using fetch API.
const response = await fetch(url, request);
// Create result object.
const result = {
url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
body: null,
};
// Try to parse the content for any response status code.
// We check the "Content-Type" header to see if we need to parse the
// content as json or as plain text.
const contentType = response.headers.get('Content-Type');
if (contentType) {
switch (contentType.toLowerCase()) {
case 'application/json':
case 'application/json; charset=utf-8':
result.body = await response.json();
break;
case 'text/plain':
case 'text/xml':
case 'text/xml; charset=utf-8':
case 'text/xml; charset=utf-16':
result.body = await response.text();
break;
}
}
return result;
}

View File

@ -0,0 +1,72 @@
/* istanbul ignore file */
/* eslint-disable */
import { Result } from './Result';
import {isSuccess} from "./isSuccess";
/**
* Request content using the new legacy XMLHttpRequest API. This method is usefull
* when we want to request UTF-16 content, since it natively supports loading UTF-16.
* We could do the same with the Fetch API, but then we will need to conver the
* content using JavaScript... And that is very very slow.
* @param url The url to request.
* @param request The request object, containing method, headers, body, etc.
*/
export async function requestUsingXHR<T>(url, request) {
return new Promise(resole => {
const xhr = new XMLHttpRequest();
// Open the request, remember to do this before adding any headers,
// because the request needs to be initialized!
xhr.open(request.method, url, true);
// Add the headers (required when dealing with JSON)
const headers = request.headers as Headers;
headers.forEach((value, key) => {
xhr.setRequestHeader(key, value);
});
// Register the readystate handler, this will fire when the request is done.
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
// Create result object.
const result = {
url,
ok: isSuccess(xhr.status),
status: xhr.status,
statusText: xhr.statusText,
body: null,
};
// Try to parse the content for any response status code.
// We check the "Content-Type" header to see if we need to parse the
// content as json or as plain text.
const contentType = xhr.getResponseHeader('Content-Type');
if (contentType) {
switch (contentType.toLowerCase()) {
case 'application/json':
case 'application/json; charset=utf-8':
result.body = JSON.parse(xhr.responseText);
break;
case 'text/plain':
case 'text/xml':
case 'text/xml; charset=utf-8':
case 'text/xml; charset=utf-16':
result.body = xhr.responseText;
break;
}
}
// Done!
resolve(result);
}
};
// Start the request!
xhr.send(request.body);
});
}

View File

@ -1,20 +1,14 @@
/* istanbul ignore file */
/* eslint-disable */
const server = '{{{server}}}';
const version = '{{{version}}}';
export { ApiError } from './core/ApiError';
export { OpenAPI } from './core/OpenAPI';
{{#if models}}
{{#each models}}
export { {{{basename}}} } from './models/{{{basename}}}';
{{/each}}
{{/if}}
{{#if schemas}}
{{#each schemas}}
export { {{{basename}}} } from './schemas/{{{basename}}}';
{{/each}}
{{/if}}
{{#if services}}
{{#each services}}

View File

@ -1,2 +0,0 @@
/* istanbul ignore file */
/* eslint-disable */

View File

@ -2,6 +2,11 @@
/* eslint-disable */
import * as yup from 'yup';
import { ApiError, catchGenericError } from '../core/ApiError';
import { request } from '../core/request';
import { isValidRequiredParam } from '../core/isValidRequiredParam';
import { OpenAPI } from '../core/OpenAPI';
export class {{{name}}} {
{{#each operations}}
@ -50,8 +55,8 @@ export class {{{name}}} {
},{{/if}}{{#if parametersBody}}
body: {{{parametersBody.name}}},{{/if}}
});
{{#if errors}}
if (!result.ok) {
switch (result.status) {
{{#each errors}}

View File

@ -0,0 +1,62 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { isSuccess } from './isSuccess';
import { Result } from './Result';
export class ApiError extends Error {
public readonly url: string;
public readonly status: number;
public readonly statusText: string;
public readonly body: any;
constructor(result: Readonly<Result>, message: string) {
super(message);
this.url = result.url;
this.status = result.status;
this.statusText = result.statusText;
this.body = result.body;
}
}
export namespace ApiError {
export enum Message {
BAD_REQUEST = 'Bad Request',
UNAUTHORIZED = 'Unauthorized',
FORBIDDEN = 'Forbidden',
NOT_FOUND = 'Not Found',
INTERNAL_SERVER_ERROR = 'Internal Server Error',
BAD_GATEWAY = 'Bad Gateway',
SERVICE_UNAVAILABLE = 'Service Unavailable',
GENERIC_ERROR = 'Generic Error',
}
}
/**
* Catch common errors (based on status code).
* @param result
*/
export function catchGenericError(result: Result): void {
switch (result.status) {
case 400:
throw new ApiError(result, ApiError.Message.BAD_REQUEST);
case 401:
throw new ApiError(result, ApiError.Message.UNAUTHORIZED);
case 403:
throw new ApiError(result, ApiError.Message.FORBIDDEN);
case 404:
throw new ApiError(result, ApiError.Message.NOT_FOUND);
case 500:
throw new ApiError(result, ApiError.Message.INTERNAL_SERVER_ERROR);
case 502:
throw new ApiError(result, ApiError.Message.BAD_GATEWAY);
case 503:
throw new ApiError(result, ApiError.Message.SERVICE_UNAVAILABLE);
}
if (!isSuccess(result.status)) {
throw new ApiError(result, ApiError.Message.GENERIC_ERROR);
}
}

View File

@ -0,0 +1,3 @@
export interface Dictionary<T> {
[key: string]: T;
}

View File

@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export namespace OpenAPI {
export let BASE: string = '';
export let TOKEN: string = '';
export let VERSION: string = '{VERSION}';
}

View File

@ -0,0 +1,12 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface RequestOptions {
method: string;
path: string;
headers?: { [key: string]: any };
query?: { [key: string]: any };
formData?: { [key: string]: any };
body?: any;
}

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
export interface Result<T = any> {
url: string;
ok: boolean;
status: number;
statusText: string;
body: T;
}

View File

@ -0,0 +1,21 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Get FormData from object. This method is needed to upload
* multipart form data to the REST API.
* @param params Key value based object.
*/
export function getFormData(params: { [key: string]: any }): FormData {
const formData: FormData = new FormData();
for (const key in params) {
if (typeof params[key] !== 'undefined') {
const value: any = params[key];
if (value !== undefined && value !== null) {
formData.append(key, value);
}
}
}
return formData;
}

View File

@ -0,0 +1,30 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Get query string from query parameters object. This method also
* supports multi-value items by creating a key for each item.
* @param params Key value based object.
*/
export function getQueryString(params: { [key: string]: any }): string {
const qs: string[] = [];
for (const key in params) {
if (typeof params[key] !== 'undefined') {
const value: any = params[key];
if (value !== undefined && value !== null) {
if (Array.isArray(value)) {
for (let i = 0, n = value.length; i < n; i++) {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value[i]))}`);
}
} else {
qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);
}
}
}
}
if (qs.length > 0) {
return `?${qs.join('&')}`;
}
return '';
}

View File

@ -0,0 +1,11 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Check success response code.
* @param status Status code
*/
export function isSuccess(status: number): boolean {
return status >= 200 && status < 300;
}

View File

@ -0,0 +1,14 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
/**
* Check if a parameter is valid.
* @param param The parameter value.
* @param name The parameter name.
*/
export function isValidRequiredParam(param: any, name: string): void {
if (param === undefined || param === null) {
throw new Error(`Required parameter '${name}' was undefined or null.`);
}
}

View File

@ -0,0 +1,72 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { getFormData } from './getFormData';
import { getQueryString } from './getQueryString';
import { OpenAPI } from './OpenAPI';
import { RequestOptions } from './RequestOptions';
import { requestUsingFetch } from './requestUsingFetch';
import { Result } from './Result';
/**
* Create the request.
* @param options Request method options.
* @returns Result object (see above)
*/
export async function request<T>(options: Readonly<RequestOptions>): Promise<Result<T>> {
// Create the request URL
let url: string = `${OpenAPI.BASE}${options.path}`;
// Create request headers
const headers: Headers = new Headers({
...options.headers,
Accept: 'application/json',
});
// Create request settings
const request: RequestInit = {
headers,
method: options.method,
credentials: 'same-origin',
};
// If we have a bearer token then we set the authentication header.
if (OpenAPI.TOKEN !== null && OpenAPI.TOKEN !== '') {
headers.append('Authorization', `Bearer ${OpenAPI.TOKEN}`);
}
// Add the query parameters (if defined).
if (options.query) {
url += getQueryString(options.query);
}
// Append formData as body
if (options.formData) {
request.body = getFormData(options.formData);
} else if (options.body) {
// If this is blob data, then pass it directly to the body and set content type.
// Otherwise we just convert request data to JSON string (needed for fetch api)
if (options.body instanceof Blob) {
request.body = options.body;
if (options.body.type) {
headers.append('Content-Type', options.body.type);
}
} else {
request.body = JSON.stringify(options.body);
headers.append('Content-Type', 'application/json');
}
}
try {
return await requestUsingFetch<T>(url, request);
} catch (error) {
return {
url,
ok: false,
status: 0,
statusText: '',
body: error,
};
}
}

View File

@ -0,0 +1,48 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { Result } from './Result';
/**
* Request content using the new Fetch API. This is the default API that is used and
* is create for all JSON, XML and text objects. However it is limited to UTF-8.
* This is a problem for some of the Docs content, since that requires UTF-16!
* @param url The url to request.
* @param request The request object, containing method, headers, body, etc.
*/
export async function requestUsingFetch<T>(url: string, request: Readonly<RequestInit>): Promise<Result<T>> {
// Fetch response using fetch API.
const response: Response = await fetch(url, request);
// Create result object.
const result: Result = {
url,
ok: response.ok,
status: response.status,
statusText: response.statusText,
body: null,
};
// Try to parse the content for any response status code.
// We check the "Content-Type" header to see if we need to parse the
// content as json or as plain text.
const contentType: string | null = response.headers.get('Content-Type');
if (contentType) {
switch (contentType.toLowerCase()) {
case 'application/json':
case 'application/json; charset=utf-8':
result.body = await response.json();
break;
case 'text/plain':
case 'text/xml':
case 'text/xml; charset=utf-8':
case 'text/xml; charset=utf-16':
result.body = await response.text();
break;
}
}
return result;
}

View File

@ -0,0 +1,70 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { Result } from './Result';
import { isSuccess } from './isSuccess';
/**
* Request content using the new legacy XMLHttpRequest API. This method is usefull
* when we want to request UTF-16 content, since it natively supports loading UTF-16.
* We could do the same with the Fetch API, but then we will need to conver the
* content using JavaScript... And that is very very slow.
* @param url The url to request.
* @param request The request object, containing method, headers, body, etc.
*/
export async function requestUsingXHR<T>(url: string, request: Readonly<RequestInit>): Promise<Result<T>> {
return new Promise(resolve => {
const xhr: XMLHttpRequest = new XMLHttpRequest();
// Open the request, remember to do this before adding any headers,
// because the request needs to be initialized!
xhr.open(request.method!, url, true);
// Add the headers (required when dealing with JSON)
const headers: Headers = request.headers as Headers;
headers.forEach((value: string, key: string): void => {
xhr.setRequestHeader(key, value);
});
// Register the readystate handler, this will fire when the request is done.
xhr.onreadystatechange = () => {
if (xhr.readyState === XMLHttpRequest.DONE) {
// Create result object.
const result: Result = {
url,
ok: isSuccess(xhr.status),
status: xhr.status,
statusText: xhr.statusText,
body: null,
};
// Try to parse the content for any response status code.
// We check the "Content-Type" header to see if we need to parse the
// content as json or as plain text.
const contentType: string | null = xhr.getResponseHeader('Content-Type');
if (contentType) {
switch (contentType.toLowerCase()) {
case 'application/json':
case 'application/json; charset=utf-8':
result.body = JSON.parse(xhr.responseText);
break;
case 'text/plain':
case 'text/xml':
case 'text/xml; charset=utf-8':
case 'text/xml; charset=utf-16':
result.body = xhr.responseText;
break;
}
}
// Done!
resolve(result);
}
};
// Start the request!
xhr.send(request.body);
});
}

Some files were not shown because too many files have changed in this diff Show More