From 30e26621a557cd275296238d4b951069be75902a Mon Sep 17 00:00:00 2001 From: Ferdi Koomen Date: Thu, 7 Nov 2019 01:17:39 +0100 Subject: [PATCH] - Added core template files - Working on v2 parsing --- package.json | 1 + src/client/interfaces/ArrayType.d.ts | 7 ++ src/client/interfaces/Client.d.ts | 2 - src/client/interfaces/Model.d.ts | 2 +- .../{ModelEnum.ts => ModelEnum.d.ts} | 0 ...{ModelEnumValue.ts => ModelEnumValue.d.ts} | 0 src/client/interfaces/ModelProperty.d.ts | 2 +- src/client/interfaces/Operation.d.ts | 21 ++++ src/client/interfaces/OperationError.d.ts | 4 + .../interfaces/OperationParameters.d.ts | 11 ++ src/client/interfaces/OperationResponse.d.ts | 4 + src/client/interfaces/OperationResult.d.ts | 4 + src/client/interfaces/Parameter.d.ts | 13 ++ src/client/interfaces/Schema.d.ts | 6 +- src/client/interfaces/Service.d.ts | 4 +- src/client/interfaces/ServiceOperation.d.ts | 23 ---- .../interfaces/ServiceOperationError.d.ts | 4 - .../interfaces/ServiceOperationParameter.ts | 13 -- .../interfaces/ServiceOperationResponse.ts | 5 - src/index.ts | 12 +- src/openApi/v2/index.ts | 2 - src/openApi/v2/interfaces/OpenApiHeader.d.ts | 2 +- src/openApi/v2/interfaces/OpenApiItems.d.ts | 2 +- .../v2/interfaces/OpenApiParameter.d.ts | 7 +- .../v2/interfaces/OpenApiResponse.d.ts | 3 +- .../v2/interfaces/OpenApiResponses.d.ts | 2 +- src/openApi/v2/interfaces/OpenApiSchema.d.ts | 2 +- src/openApi/v2/parser/getArrayType.ts | 45 +++++++ src/openApi/v2/parser/getComment.ts | 6 + src/openApi/v2/parser/getEnumType.ts | 20 +++ .../v2/parser/getEnumTypeFromDescription.ts | 32 +++++ src/openApi/v2/parser/getMappedType.ts | 2 +- src/openApi/v2/parser/getModelProperties.ts | 4 +- src/openApi/v2/parser/getModels.ts | 66 +++++----- src/openApi/v2/parser/getOperation.ts | 64 ++++++++++ src/openApi/v2/parser/getOperationErrors.ts | 11 ++ .../v2/parser/getOperationName.spec.ts | 10 ++ src/openApi/v2/parser/getOperationName.ts | 11 ++ .../v2/parser/getOperationParameters.ts | 74 ++++++++++++ .../v2/parser/getOperationPath.spec.ts | 10 ++ .../parser/getOperationPath.ts} | 2 +- ...nseCode.ts => getOperationResponseCode.ts} | 4 +- .../v2/parser/getOperationResponses.ts | 39 ++++++ src/openApi/v2/parser/getOperationResult.ts | 19 +++ src/openApi/v2/parser/getParameter.ts | 72 +++++++++++ src/openApi/v2/parser/getParameterName.ts | 10 ++ src/openApi/v2/parser/getRef.ts | 27 +++++ src/openApi/v2/parser/getSchemas.ts | 11 -- src/openApi/v2/parser/getServer.spec.ts | 6 + src/openApi/v2/parser/getServer.ts | 8 +- src/openApi/v2/parser/getServiceClassName.ts | 4 +- .../v2/parser/getServiceOperationErrors.ts | 16 --- .../v2/parser/getServiceOperationName.spec.ts | 10 -- .../v2/parser/getServiceOperationName.ts | 11 -- .../v2/parser/getServiceOperationPath.spec.ts | 10 -- .../v2/parser/getServiceOperationPath.ts | 9 -- .../v2/parser/getServiceOperationResponses.ts | 35 ------ .../v2/parser/getServiceOperationResult.ts | 29 ----- src/openApi/v2/parser/getServices.ts | 114 +++++++----------- src/openApi/v2/parser/getType.ts | 15 ++- src/openApi/v3/index.ts | 2 - src/openApi/v3/interfaces/OpenApiSchema.d.ts | 2 +- src/openApi/v3/parser/getMappedType.ts | 2 +- src/openApi/v3/parser/getModels.ts | 2 +- src/openApi/v3/parser/getSchemas.ts | 11 -- src/openApi/v3/parser/getServer.spec.ts | 12 ++ src/openApi/v3/parser/getServer.ts | 21 ++-- .../v3/parser/getServiceClassName.spec.ts | 13 -- src/openApi/v3/parser/getServiceClassName.ts | 14 --- .../v3/parser/getServiceOperationName.spec.ts | 10 -- .../v3/parser/getServiceOperationName.ts | 11 -- src/openApi/v3/parser/getServicePath.spec.ts | 10 -- .../v3/parser/getServiceVersion.spec.ts | 9 -- src/openApi/v3/parser/getServiceVersion.ts | 8 -- src/openApi/v3/parser/getServices.ts | 2 +- src/openApi/v3/parser/getType.ts | 10 +- src/templates/javascript/core/ApiError.js | 51 ++++++++ src/templates/javascript/core/OpenAPI.js | 9 ++ src/templates/javascript/core/getFormData.js | 20 +++ .../javascript/core/getQueryString.js | 29 +++++ src/templates/javascript/core/isSuccess.js | 10 ++ .../javascript/core/isValidRequiredParam.js | 13 ++ src/templates/javascript/core/request.js | 74 ++++++++++++ .../javascript/core/requestUsingFetch.js | 47 ++++++++ .../javascript/core/requestUsingXHR.js | 72 +++++++++++ src/templates/javascript/index.hbs | 10 +- src/templates/javascript/schema.hbs | 2 - src/templates/javascript/service.hbs | 7 +- src/templates/typescript/core/ApiError.ts | 62 ++++++++++ src/templates/typescript/core/Dictionary.ts | 3 + src/templates/typescript/core/OpenAPI.ts | 9 ++ .../typescript/core/RequestOptions.ts | 12 ++ src/templates/typescript/core/Result.ts | 11 ++ src/templates/typescript/core/getFormData.ts | 21 ++++ .../typescript/core/getQueryString.ts | 30 +++++ src/templates/typescript/core/isSuccess.ts | 11 ++ .../typescript/core/isValidRequiredParam.ts | 14 +++ src/templates/typescript/core/request.ts | 72 +++++++++++ .../typescript/core/requestUsingFetch.ts | 48 ++++++++ .../typescript/core/requestUsingXHR.ts | 70 +++++++++++ src/templates/typescript/index.hbs | 12 +- src/templates/typescript/model.hbs | 1 + src/templates/typescript/schema.hbs | 3 - src/templates/typescript/service.hbs | 18 ++- src/utils/getOpenApiSpec.ts | 4 +- src/utils/getOpenApiVersion.ts | 8 +- src/utils/getSortedImports.ts | 12 +- src/utils/getSortedModels.spec.ts | 6 +- src/utils/getSortedModels.ts | 4 +- src/utils/getSortedSchemas.spec.ts | 26 ---- src/utils/getSortedSchemas.ts | 15 --- src/utils/getSortedServices.ts | 4 +- src/utils/readHandlebarsTemplate.ts | 2 +- src/utils/readHandlebarsTemplates.spec.ts | 2 - src/utils/readHandlebarsTemplates.ts | 9 +- src/utils/writeClient.spec.ts | 3 - src/utils/writeClient.ts | 25 ++-- src/utils/writeClientIndex.spec.ts | 2 - src/utils/writeClientIndex.ts | 4 +- src/utils/writeClientModels.ts | 2 +- src/utils/writeClientSchemas.spec.ts | 23 ---- src/utils/writeClientSchemas.ts | 24 ---- src/utils/writeClientServices.ts | 2 +- test/index.js | 20 ++- test/mock/v2/test-addon.json | 2 +- test/mock/v3/test-petstore.yaml | 103 ---------------- yarn.lock | 12 ++ 127 files changed, 1460 insertions(+), 698 deletions(-) create mode 100644 src/client/interfaces/ArrayType.d.ts rename src/client/interfaces/{ModelEnum.ts => ModelEnum.d.ts} (100%) rename src/client/interfaces/{ModelEnumValue.ts => ModelEnumValue.d.ts} (100%) create mode 100644 src/client/interfaces/Operation.d.ts create mode 100644 src/client/interfaces/OperationError.d.ts create mode 100644 src/client/interfaces/OperationParameters.d.ts create mode 100644 src/client/interfaces/OperationResponse.d.ts create mode 100644 src/client/interfaces/OperationResult.d.ts create mode 100644 src/client/interfaces/Parameter.d.ts delete mode 100644 src/client/interfaces/ServiceOperation.d.ts delete mode 100644 src/client/interfaces/ServiceOperationError.d.ts delete mode 100644 src/client/interfaces/ServiceOperationParameter.ts delete mode 100644 src/client/interfaces/ServiceOperationResponse.ts create mode 100644 src/openApi/v2/parser/getArrayType.ts create mode 100644 src/openApi/v2/parser/getComment.ts create mode 100644 src/openApi/v2/parser/getEnumType.ts create mode 100644 src/openApi/v2/parser/getEnumTypeFromDescription.ts create mode 100644 src/openApi/v2/parser/getOperation.ts create mode 100644 src/openApi/v2/parser/getOperationErrors.ts create mode 100644 src/openApi/v2/parser/getOperationName.spec.ts create mode 100644 src/openApi/v2/parser/getOperationName.ts create mode 100644 src/openApi/v2/parser/getOperationParameters.ts create mode 100644 src/openApi/v2/parser/getOperationPath.spec.ts rename src/openApi/{v3/parser/getServicePath.ts => v2/parser/getOperationPath.ts} (85%) rename src/openApi/v2/parser/{getServiceOperationResponseCode.ts => getOperationResponseCode.ts} (71%) create mode 100644 src/openApi/v2/parser/getOperationResponses.ts create mode 100644 src/openApi/v2/parser/getOperationResult.ts create mode 100644 src/openApi/v2/parser/getParameter.ts create mode 100644 src/openApi/v2/parser/getParameterName.ts create mode 100644 src/openApi/v2/parser/getRef.ts delete mode 100644 src/openApi/v2/parser/getSchemas.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationErrors.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationName.spec.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationName.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationPath.spec.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationPath.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationResponses.ts delete mode 100644 src/openApi/v2/parser/getServiceOperationResult.ts delete mode 100644 src/openApi/v3/parser/getSchemas.ts delete mode 100644 src/openApi/v3/parser/getServiceClassName.spec.ts delete mode 100644 src/openApi/v3/parser/getServiceClassName.ts delete mode 100644 src/openApi/v3/parser/getServiceOperationName.spec.ts delete mode 100644 src/openApi/v3/parser/getServiceOperationName.ts delete mode 100644 src/openApi/v3/parser/getServicePath.spec.ts delete mode 100644 src/openApi/v3/parser/getServiceVersion.spec.ts delete mode 100644 src/openApi/v3/parser/getServiceVersion.ts create mode 100644 src/templates/javascript/core/ApiError.js create mode 100644 src/templates/javascript/core/OpenAPI.js create mode 100644 src/templates/javascript/core/getFormData.js create mode 100644 src/templates/javascript/core/getQueryString.js create mode 100644 src/templates/javascript/core/isSuccess.js create mode 100644 src/templates/javascript/core/isValidRequiredParam.js create mode 100644 src/templates/javascript/core/request.js create mode 100644 src/templates/javascript/core/requestUsingFetch.js create mode 100644 src/templates/javascript/core/requestUsingXHR.js delete mode 100644 src/templates/javascript/schema.hbs create mode 100644 src/templates/typescript/core/ApiError.ts create mode 100644 src/templates/typescript/core/Dictionary.ts create mode 100644 src/templates/typescript/core/OpenAPI.ts create mode 100644 src/templates/typescript/core/RequestOptions.ts create mode 100644 src/templates/typescript/core/Result.ts create mode 100644 src/templates/typescript/core/getFormData.ts create mode 100644 src/templates/typescript/core/getQueryString.ts create mode 100644 src/templates/typescript/core/isSuccess.ts create mode 100644 src/templates/typescript/core/isValidRequiredParam.ts create mode 100644 src/templates/typescript/core/request.ts create mode 100644 src/templates/typescript/core/requestUsingFetch.ts create mode 100644 src/templates/typescript/core/requestUsingXHR.ts delete mode 100644 src/templates/typescript/schema.hbs delete mode 100644 src/utils/getSortedSchemas.spec.ts delete mode 100644 src/utils/getSortedSchemas.ts delete mode 100644 src/utils/writeClientSchemas.spec.ts delete mode 100644 src/utils/writeClientSchemas.ts delete mode 100644 test/mock/v3/test-petstore.yaml diff --git a/package.json b/package.json index 849994f7..30540e78 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/client/interfaces/ArrayType.d.ts b/src/client/interfaces/ArrayType.d.ts new file mode 100644 index 00000000..297390da --- /dev/null +++ b/src/client/interfaces/ArrayType.d.ts @@ -0,0 +1,7 @@ +export interface ArrayType { + type: string; + base: string; + template: string | null; + default?: any; + imports: string[]; +} diff --git a/src/client/interfaces/Client.d.ts b/src/client/interfaces/Client.d.ts index ee4fd594..f761f87c 100644 --- a/src/client/interfaces/Client.d.ts +++ b/src/client/interfaces/Client.d.ts @@ -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; - schemas: Map; services: Map; } diff --git a/src/client/interfaces/Model.d.ts b/src/client/interfaces/Model.d.ts index 979ba8cd..093d7d62 100644 --- a/src/client/interfaces/Model.d.ts +++ b/src/client/interfaces/Model.d.ts @@ -5,7 +5,7 @@ export interface Model { name: string; base: string; type: string; - template?: string; + template: string | null; description?: string; extends: string[]; imports: string[]; diff --git a/src/client/interfaces/ModelEnum.ts b/src/client/interfaces/ModelEnum.d.ts similarity index 100% rename from src/client/interfaces/ModelEnum.ts rename to src/client/interfaces/ModelEnum.d.ts diff --git a/src/client/interfaces/ModelEnumValue.ts b/src/client/interfaces/ModelEnumValue.d.ts similarity index 100% rename from src/client/interfaces/ModelEnumValue.ts rename to src/client/interfaces/ModelEnumValue.d.ts diff --git a/src/client/interfaces/ModelProperty.d.ts b/src/client/interfaces/ModelProperty.d.ts index a11706f7..b43c74a5 100644 --- a/src/client/interfaces/ModelProperty.d.ts +++ b/src/client/interfaces/ModelProperty.d.ts @@ -2,7 +2,7 @@ export interface ModelProperty { name: string; type: string; base: string; - template?: string; + template: string | null; description?: string; default?: any; required: boolean; diff --git a/src/client/interfaces/Operation.d.ts b/src/client/interfaces/Operation.d.ts new file mode 100644 index 00000000..914d6e98 --- /dev/null +++ b/src/client/interfaces/Operation.d.ts @@ -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[]; +} diff --git a/src/client/interfaces/OperationError.d.ts b/src/client/interfaces/OperationError.d.ts new file mode 100644 index 00000000..63076f6f --- /dev/null +++ b/src/client/interfaces/OperationError.d.ts @@ -0,0 +1,4 @@ +export interface OperationError { + code: number; + text: string; +} diff --git a/src/client/interfaces/OperationParameters.d.ts b/src/client/interfaces/OperationParameters.d.ts new file mode 100644 index 00000000..54239a9f --- /dev/null +++ b/src/client/interfaces/OperationParameters.d.ts @@ -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; +} diff --git a/src/client/interfaces/OperationResponse.d.ts b/src/client/interfaces/OperationResponse.d.ts new file mode 100644 index 00000000..06fc4054 --- /dev/null +++ b/src/client/interfaces/OperationResponse.d.ts @@ -0,0 +1,4 @@ +export interface OperationResponse { + code: number; + text: string; +} diff --git a/src/client/interfaces/OperationResult.d.ts b/src/client/interfaces/OperationResult.d.ts new file mode 100644 index 00000000..62748e96 --- /dev/null +++ b/src/client/interfaces/OperationResult.d.ts @@ -0,0 +1,4 @@ +export interface OperationResult { + type: string; + imports: string[]; +} diff --git a/src/client/interfaces/Parameter.d.ts b/src/client/interfaces/Parameter.d.ts new file mode 100644 index 00000000..d9d5550b --- /dev/null +++ b/src/client/interfaces/Parameter.d.ts @@ -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[]; +} diff --git a/src/client/interfaces/Schema.d.ts b/src/client/interfaces/Schema.d.ts index c3173ff2..5e73fa7c 100644 --- a/src/client/interfaces/Schema.d.ts +++ b/src/client/interfaces/Schema.d.ts @@ -1,5 +1,7 @@ export interface Schema { - name: string; + type: string; base: string; - imports: []; + template: string | null; + default?: any; + imports: string[]; } diff --git a/src/client/interfaces/Service.d.ts b/src/client/interfaces/Service.d.ts index b272a34c..f7861cd2 100644 --- a/src/client/interfaces/Service.d.ts +++ b/src/client/interfaces/Service.d.ts @@ -1,7 +1,7 @@ -import { ServiceOperation } from './ServiceOperation'; +import { Operation } from './Operation'; export interface Service { name: string; - operations: ServiceOperation[]; + operations: Operation[]; imports: string[]; } diff --git a/src/client/interfaces/ServiceOperation.d.ts b/src/client/interfaces/ServiceOperation.d.ts deleted file mode 100644 index c1176312..00000000 --- a/src/client/interfaces/ServiceOperation.d.ts +++ /dev/null @@ -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; -} diff --git a/src/client/interfaces/ServiceOperationError.d.ts b/src/client/interfaces/ServiceOperationError.d.ts deleted file mode 100644 index 815b44a1..00000000 --- a/src/client/interfaces/ServiceOperationError.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ServiceOperationError { - code: number; - text: string; -} diff --git a/src/client/interfaces/ServiceOperationParameter.ts b/src/client/interfaces/ServiceOperationParameter.ts deleted file mode 100644 index aa5dbe29..00000000 --- a/src/client/interfaces/ServiceOperationParameter.ts +++ /dev/null @@ -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[]; -} diff --git a/src/client/interfaces/ServiceOperationResponse.ts b/src/client/interfaces/ServiceOperationResponse.ts deleted file mode 100644 index a710433d..00000000 --- a/src/client/interfaces/ServiceOperationResponse.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ServiceOperationResponse { - code: number; - text: string; - property: any; -} diff --git a/src/index.ts b/src/index.ts index e276d24a..178e95c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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 diff --git a/src/openApi/v2/index.ts b/src/openApi/v2/index.ts index e5f50606..16b9bc26 100644 --- a/src/openApi/v2/index.ts +++ b/src/openApi/v2/index.ts @@ -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), }; } diff --git a/src/openApi/v2/interfaces/OpenApiHeader.d.ts b/src/openApi/v2/interfaces/OpenApiHeader.d.ts index 83c2a7e0..bd0c6087 100644 --- a/src/openApi/v2/interfaces/OpenApiHeader.d.ts +++ b/src/openApi/v2/interfaces/OpenApiHeader.d.ts @@ -21,6 +21,6 @@ export interface OpenApiHeader { maxItems?: number; minItems?: number; uniqueItems?: boolean; - enum?: (string | number)[]; + enum?: string[]; multipleOf?: number; } diff --git a/src/openApi/v2/interfaces/OpenApiItems.d.ts b/src/openApi/v2/interfaces/OpenApiItems.d.ts index dca05ad6..ea6294ea 100644 --- a/src/openApi/v2/interfaces/OpenApiItems.d.ts +++ b/src/openApi/v2/interfaces/OpenApiItems.d.ts @@ -17,6 +17,6 @@ export interface OpenApiItems { maxItems?: number; minItems?: number; uniqueItems?: number; - enum?: (string | number)[]; + enum?: string[]; multipleOf?: number; } diff --git a/src/openApi/v2/interfaces/OpenApiParameter.d.ts b/src/openApi/v2/interfaces/OpenApiParameter.d.ts index ec8e2cac..74aecc43 100644 --- a/src/openApi/v2/interfaces/OpenApiParameter.d.ts +++ b/src/openApi/v2/interfaces/OpenApiParameter.d.ts @@ -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; } diff --git a/src/openApi/v2/interfaces/OpenApiResponse.d.ts b/src/openApi/v2/interfaces/OpenApiResponse.d.ts index 85adcd1b..050a8bbd 100644 --- a/src/openApi/v2/interfaces/OpenApiResponse.d.ts +++ b/src/openApi/v2/interfaces/OpenApiResponse.d.ts @@ -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; examples?: OpenApiExample; } diff --git a/src/openApi/v2/interfaces/OpenApiResponses.d.ts b/src/openApi/v2/interfaces/OpenApiResponses.d.ts index d8492f79..dc6ef65e 100644 --- a/src/openApi/v2/interfaces/OpenApiResponses.d.ts +++ b/src/openApi/v2/interfaces/OpenApiResponses.d.ts @@ -7,5 +7,5 @@ import { OpenApiResponse } from './OpenApiResponse'; export interface OpenApiResponses { [httpcode: string]: OpenApiResponse & OpenApiReference; - default: OpenApiResponse & OpenApiReference; + default?: OpenApiResponse & OpenApiReference; } diff --git a/src/openApi/v2/interfaces/OpenApiSchema.d.ts b/src/openApi/v2/interfaces/OpenApiSchema.d.ts index 57bfb7f4..26a588ba 100644 --- a/src/openApi/v2/interfaces/OpenApiSchema.d.ts +++ b/src/openApi/v2/interfaces/OpenApiSchema.d.ts @@ -25,7 +25,7 @@ export interface OpenApiSchema { maxProperties?: number; minProperties?: number; required?: string[]; - enum?: (string | number)[]; + enum?: string[]; type?: string; items?: OpenApiSchema & OpenApiReference; allOf?: (OpenApiSchema & OpenApiReference)[]; diff --git a/src/openApi/v2/parser/getArrayType.ts b/src/openApi/v2/parser/getArrayType.ts new file mode 100644 index 00000000..20d73876 --- /dev/null +++ b/src/openApi/v2/parser/getArrayType.ts @@ -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, + }; +} diff --git a/src/openApi/v2/parser/getComment.ts b/src/openApi/v2/parser/getComment.ts new file mode 100644 index 00000000..0b5c9564 --- /dev/null +++ b/src/openApi/v2/parser/getComment.ts @@ -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; +} diff --git a/src/openApi/v2/parser/getEnumType.ts b/src/openApi/v2/parser/getEnumType.ts new file mode 100644 index 00000000..bdad0698 --- /dev/null +++ b/src/openApi/v2/parser/getEnumType.ts @@ -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'; +} diff --git a/src/openApi/v2/parser/getEnumTypeFromDescription.ts b/src/openApi/v2/parser/getEnumTypeFromDescription.ts new file mode 100644 index 00000000..9b73191f --- /dev/null +++ b/src/openApi/v2/parser/getEnumTypeFromDescription.ts @@ -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; +} diff --git a/src/openApi/v2/parser/getMappedType.ts b/src/openApi/v2/parser/getMappedType.ts index 83c1950e..06419037 100644 --- a/src/openApi/v2/parser/getMappedType.ts +++ b/src/openApi/v2/parser/getMappedType.ts @@ -30,7 +30,7 @@ const MAPPINGS = new Map([ * @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; } diff --git a/src/openApi/v2/parser/getModelProperties.ts b/src/openApi/v2/parser/getModelProperties.ts index a589d525..128ac2f2 100644 --- a/src/openApi/v2/parser/getModelProperties.ts +++ b/src/openApi/v2/parser/getModelProperties.ts @@ -1,6 +1,4 @@ -import { ModelProperties } from '../../../client/interfaces/ModelProperties'; - -export function parseModelProperties(): ModelProperties { +export function parseModelProperties(): any { return { imports: [], properties: [], diff --git a/src/openApi/v2/parser/getModels.ts b/src/openApi/v2/parser/getModels.ts index 7b662962..38683211 100644 --- a/src/openApi/v2/parser/getModels.ts +++ b/src/openApi/v2/parser/getModels.ts @@ -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 { - const models = new Map(); + const models: Map = new Map(); // 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; } diff --git a/src/openApi/v2/parser/getOperation.ts b/src/openApi/v2/parser/getOperation.ts new file mode 100644 index 00000000..4abf02c1 --- /dev/null +++ b/src/openApi/v2/parser/getOperation.ts @@ -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; +} diff --git a/src/openApi/v2/parser/getOperationErrors.ts b/src/openApi/v2/parser/getOperationErrors.ts new file mode 100644 index 00000000..94f24c65 --- /dev/null +++ b/src/openApi/v2/parser/getOperationErrors.ts @@ -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, + })); +} diff --git a/src/openApi/v2/parser/getOperationName.spec.ts b/src/openApi/v2/parser/getOperationName.spec.ts new file mode 100644 index 00000000..ba8a5d3d --- /dev/null +++ b/src/openApi/v2/parser/getOperationName.spec.ts @@ -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'); + }); +}); diff --git a/src/openApi/v2/parser/getOperationName.ts b/src/openApi/v2/parser/getOperationName.ts new file mode 100644 index 00000000..83a9abcb --- /dev/null +++ b/src/openApi/v2/parser/getOperationName.ts @@ -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); +} diff --git a/src/openApi/v2/parser/getOperationParameters.ts b/src/openApi/v2/parser/getOperationParameters.ts new file mode 100644 index 00000000..240c0fe3 --- /dev/null +++ b/src/openApi/v2/parser/getOperationParameters.ts @@ -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(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, + }; +} diff --git a/src/openApi/v2/parser/getOperationPath.spec.ts b/src/openApi/v2/parser/getOperationPath.spec.ts new file mode 100644 index 00000000..4ede28f4 --- /dev/null +++ b/src/openApi/v2/parser/getOperationPath.spec.ts @@ -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'); + }); +}); diff --git a/src/openApi/v3/parser/getServicePath.ts b/src/openApi/v2/parser/getOperationPath.ts similarity index 85% rename from src/openApi/v3/parser/getServicePath.ts rename to src/openApi/v2/parser/getOperationPath.ts index c1fd8124..04b6fd4e 100644 --- a/src/openApi/v3/parser/getServicePath.ts +++ b/src/openApi/v2/parser/getOperationPath.ts @@ -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}'); } diff --git a/src/openApi/v2/parser/getServiceOperationResponseCode.ts b/src/openApi/v2/parser/getOperationResponseCode.ts similarity index 71% rename from src/openApi/v2/parser/getServiceOperationResponseCode.ts rename to src/openApi/v2/parser/getOperationResponseCode.ts index 39899034..649229a8 100644 --- a/src/openApi/v2/parser/getServiceOperationResponseCode.ts +++ b/src/openApi/v2/parser/getOperationResponseCode.ts @@ -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; } diff --git a/src/openApi/v2/parser/getOperationResponses.ts b/src/openApi/v2/parser/getOperationResponses.ts new file mode 100644 index 00000000..1ef6118d --- /dev/null +++ b/src/openApi/v2/parser/getOperationResponses.ts @@ -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(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; + }); +} diff --git a/src/openApi/v2/parser/getOperationResult.ts b/src/openApi/v2/parser/getOperationResult.ts new file mode 100644 index 00000000..28419ed8 --- /dev/null +++ b/src/openApi/v2/parser/getOperationResult.ts @@ -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, + }; +} diff --git a/src/openApi/v2/parser/getParameter.ts b/src/openApi/v2/parser/getParameter.ts new file mode 100644 index 00000000..b0ac1ea5 --- /dev/null +++ b/src/openApi/v2/parser/getParameter.ts @@ -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, + }; +} diff --git a/src/openApi/v2/parser/getParameterName.ts b/src/openApi/v2/parser/getParameterName.ts new file mode 100644 index 00000000..bab13644 --- /dev/null +++ b/src/openApi/v2/parser/getParameterName.ts @@ -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); +} diff --git a/src/openApi/v2/parser/getRef.ts b/src/openApi/v2/parser/getRef.ts new file mode 100644 index 00000000..18b25b1a --- /dev/null +++ b/src/openApi/v2/parser/getRef.ts @@ -0,0 +1,27 @@ +import { OpenApi } from '../interfaces/OpenApi'; +import { OpenApiReference } from '../interfaces/OpenApiReference'; + +export function getRef(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; +} diff --git a/src/openApi/v2/parser/getSchemas.ts b/src/openApi/v2/parser/getSchemas.ts deleted file mode 100644 index d209da82..00000000 --- a/src/openApi/v2/parser/getSchemas.ts +++ /dev/null @@ -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 { - const schemas = new Map(); - return schemas; -} diff --git a/src/openApi/v2/parser/getServer.spec.ts b/src/openApi/v2/parser/getServer.spec.ts index 4a9f449a..4eddc72c 100644 --- a/src/openApi/v2/parser/getServer.spec.ts +++ b/src/openApi/v2/parser/getServer.spec.ts @@ -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'); }); diff --git a/src/openApi/v2/parser/getServer.ts b/src/openApi/v2/parser/getServer.ts index 449102e0..6037e0c5 100644 --- a/src/openApi/v2/parser/getServer.ts +++ b/src/openApi/v2/parser/getServer.ts @@ -1,8 +1,10 @@ import { OpenApi } from '../interfaces/OpenApi'; -type Props = Pick; - -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 || ''; diff --git a/src/openApi/v2/parser/getServiceClassName.ts b/src/openApi/v2/parser/getServiceClassName.ts index 832866db..ffaeb49c 100644 --- a/src/openApi/v2/parser/getServiceClassName.ts +++ b/src/openApi/v2/parser/getServiceClassName.ts @@ -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`; } diff --git a/src/openApi/v2/parser/getServiceOperationErrors.ts b/src/openApi/v2/parser/getServiceOperationErrors.ts deleted file mode 100644 index 90d58257..00000000 --- a/src/openApi/v2/parser/getServiceOperationErrors.ts +++ /dev/null @@ -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, - })); -} diff --git a/src/openApi/v2/parser/getServiceOperationName.spec.ts b/src/openApi/v2/parser/getServiceOperationName.spec.ts deleted file mode 100644 index f3d4f0c2..00000000 --- a/src/openApi/v2/parser/getServiceOperationName.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/src/openApi/v2/parser/getServiceOperationName.ts b/src/openApi/v2/parser/getServiceOperationName.ts deleted file mode 100644 index 9f059b3a..00000000 --- a/src/openApi/v2/parser/getServiceOperationName.ts +++ /dev/null @@ -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); -} diff --git a/src/openApi/v2/parser/getServiceOperationPath.spec.ts b/src/openApi/v2/parser/getServiceOperationPath.spec.ts deleted file mode 100644 index a350a1d1..00000000 --- a/src/openApi/v2/parser/getServiceOperationPath.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/src/openApi/v2/parser/getServiceOperationPath.ts b/src/openApi/v2/parser/getServiceOperationPath.ts deleted file mode 100644 index 1cfb106f..00000000 --- a/src/openApi/v2/parser/getServiceOperationPath.ts +++ /dev/null @@ -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}'); -} diff --git a/src/openApi/v2/parser/getServiceOperationResponses.ts b/src/openApi/v2/parser/getServiceOperationResponses.ts deleted file mode 100644 index 5efbefee..00000000 --- a/src/openApi/v2/parser/getServiceOperationResponses.ts +++ /dev/null @@ -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; - }); -} diff --git a/src/openApi/v2/parser/getServiceOperationResult.ts b/src/openApi/v2/parser/getServiceOperationResult.ts deleted file mode 100644 index aeab76b9..00000000 --- a/src/openApi/v2/parser/getServiceOperationResult.ts +++ /dev/null @@ -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, - }; -} diff --git a/src/openApi/v2/parser/getServices.ts b/src/openApi/v2/parser/getServices.ts index e4ed88b0..a7fb28ae 100644 --- a/src/openApi/v2/parser/getServices.ts +++ b/src/openApi/v2/parser/getServices.ts @@ -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, 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 { - const services = new Map(); - 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 = new Map(); + + 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; } diff --git a/src/openApi/v2/parser/getType.ts b/src/openApi/v2/parser/getType.ts index 2f676a1f..ced5a2d3 100644 --- a/src/openApi/v2/parser/getType.ts +++ b/src/openApi/v2/parser/getType.ts @@ -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 { diff --git a/src/openApi/v3/index.ts b/src/openApi/v3/index.ts index c8862a9a..1d063210 100644 --- a/src/openApi/v3/index.ts +++ b/src/openApi/v3/index.ts @@ -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), }; } diff --git a/src/openApi/v3/interfaces/OpenApiSchema.d.ts b/src/openApi/v3/interfaces/OpenApiSchema.d.ts index da7904c4..f3733839 100644 --- a/src/openApi/v3/interfaces/OpenApiSchema.d.ts +++ b/src/openApi/v3/interfaces/OpenApiSchema.d.ts @@ -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)[]; diff --git a/src/openApi/v3/parser/getMappedType.ts b/src/openApi/v3/parser/getMappedType.ts index 83c1950e..06419037 100644 --- a/src/openApi/v3/parser/getMappedType.ts +++ b/src/openApi/v3/parser/getMappedType.ts @@ -30,7 +30,7 @@ const MAPPINGS = new Map([ * @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; } diff --git a/src/openApi/v3/parser/getModels.ts b/src/openApi/v3/parser/getModels.ts index 40db01f6..2b4d9f39 100644 --- a/src/openApi/v3/parser/getModels.ts +++ b/src/openApi/v3/parser/getModels.ts @@ -6,6 +6,6 @@ import { OpenApi } from '../interfaces/OpenApi'; * @param openApi */ export function getModels(openApi: OpenApi): Map { - const models = new Map(); + const models: Map = new Map(); return models; } diff --git a/src/openApi/v3/parser/getSchemas.ts b/src/openApi/v3/parser/getSchemas.ts deleted file mode 100644 index d209da82..00000000 --- a/src/openApi/v3/parser/getSchemas.ts +++ /dev/null @@ -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 { - const schemas = new Map(); - return schemas; -} diff --git a/src/openApi/v3/parser/getServer.spec.ts b/src/openApi/v3/parser/getServer.spec.ts index c9f1b50a..46a1cc0b 100644 --- a/src/openApi/v3/parser/getServer.spec.ts +++ b/src/openApi/v3/parser/getServer.spec.ts @@ -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', diff --git a/src/openApi/v3/parser/getServer.ts b/src/openApi/v3/parser/getServer.ts index 701e2686..8e09f536 100644 --- a/src/openApi/v3/parser/getServer.ts +++ b/src/openApi/v3/parser/getServer.ts @@ -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; - -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 = (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; } diff --git a/src/openApi/v3/parser/getServiceClassName.spec.ts b/src/openApi/v3/parser/getServiceClassName.spec.ts deleted file mode 100644 index 9bcf6e15..00000000 --- a/src/openApi/v3/parser/getServiceClassName.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/src/openApi/v3/parser/getServiceClassName.ts b/src/openApi/v3/parser/getServiceClassName.ts deleted file mode 100644 index 832866db..00000000 --- a/src/openApi/v3/parser/getServiceClassName.ts +++ /dev/null @@ -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; -} diff --git a/src/openApi/v3/parser/getServiceOperationName.spec.ts b/src/openApi/v3/parser/getServiceOperationName.spec.ts deleted file mode 100644 index f3d4f0c2..00000000 --- a/src/openApi/v3/parser/getServiceOperationName.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/src/openApi/v3/parser/getServiceOperationName.ts b/src/openApi/v3/parser/getServiceOperationName.ts deleted file mode 100644 index 9f059b3a..00000000 --- a/src/openApi/v3/parser/getServiceOperationName.ts +++ /dev/null @@ -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); -} diff --git a/src/openApi/v3/parser/getServicePath.spec.ts b/src/openApi/v3/parser/getServicePath.spec.ts deleted file mode 100644 index 2e8ff3a2..00000000 --- a/src/openApi/v3/parser/getServicePath.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/src/openApi/v3/parser/getServiceVersion.spec.ts b/src/openApi/v3/parser/getServiceVersion.spec.ts deleted file mode 100644 index 3e72898b..00000000 --- a/src/openApi/v3/parser/getServiceVersion.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/src/openApi/v3/parser/getServiceVersion.ts b/src/openApi/v3/parser/getServiceVersion.ts deleted file mode 100644 index 9986f1af..00000000 --- a/src/openApi/v3/parser/getServiceVersion.ts +++ /dev/null @@ -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, ''); -} diff --git a/src/openApi/v3/parser/getServices.ts b/src/openApi/v3/parser/getServices.ts index d4c59848..f14d630d 100644 --- a/src/openApi/v3/parser/getServices.ts +++ b/src/openApi/v3/parser/getServices.ts @@ -6,6 +6,6 @@ import { OpenApi } from '../interfaces/OpenApi'; * @param openApi */ export function getServices(openApi: OpenApi): Map { - const services = new Map(); + const services: Map = new Map(); return services; } diff --git a/src/openApi/v3/parser/getType.ts b/src/openApi/v3/parser/getType.ts index 31ae76d3..21037e14 100644 --- a/src/openApi/v3/parser/getType.ts +++ b/src/openApi/v3/parser/getType.ts @@ -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 { diff --git a/src/templates/javascript/core/ApiError.js b/src/templates/javascript/core/ApiError.js new file mode 100644 index 00000000..7bfdbcc6 --- /dev/null +++ b/src/templates/javascript/core/ApiError.js @@ -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); + } +} diff --git a/src/templates/javascript/core/OpenAPI.js b/src/templates/javascript/core/OpenAPI.js new file mode 100644 index 00000000..71f8eca8 --- /dev/null +++ b/src/templates/javascript/core/OpenAPI.js @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* eslint-disable */ + +export let OpenAPI; +(function (OpenAPI) { + OpenAPI.BASE = ''; + OpenAPI.TOKEN = ''; + OpenAPI.VERSION = '{VERSION}'; +})(OpenAPI || (OpenAPI = {})); diff --git a/src/templates/javascript/core/getFormData.js b/src/templates/javascript/core/getFormData.js new file mode 100644 index 00000000..03256b33 --- /dev/null +++ b/src/templates/javascript/core/getFormData.js @@ -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; +} diff --git a/src/templates/javascript/core/getQueryString.js b/src/templates/javascript/core/getQueryString.js new file mode 100644 index 00000000..d31789a1 --- /dev/null +++ b/src/templates/javascript/core/getQueryString.js @@ -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 ''; +} diff --git a/src/templates/javascript/core/isSuccess.js b/src/templates/javascript/core/isSuccess.js new file mode 100644 index 00000000..d42ee321 --- /dev/null +++ b/src/templates/javascript/core/isSuccess.js @@ -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); +} diff --git a/src/templates/javascript/core/isValidRequiredParam.js b/src/templates/javascript/core/isValidRequiredParam.js new file mode 100644 index 00000000..19cf9189 --- /dev/null +++ b/src/templates/javascript/core/isValidRequiredParam.js @@ -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.`); + } +} diff --git a/src/templates/javascript/core/request.js b/src/templates/javascript/core/request.js new file mode 100644 index 00000000..11e71d7d --- /dev/null +++ b/src/templates/javascript/core/request.js @@ -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(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, + }; + } +} diff --git a/src/templates/javascript/core/requestUsingFetch.js b/src/templates/javascript/core/requestUsingFetch.js new file mode 100644 index 00000000..e508691c --- /dev/null +++ b/src/templates/javascript/core/requestUsingFetch.js @@ -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(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; +} diff --git a/src/templates/javascript/core/requestUsingXHR.js b/src/templates/javascript/core/requestUsingXHR.js new file mode 100644 index 00000000..dfbd92c5 --- /dev/null +++ b/src/templates/javascript/core/requestUsingXHR.js @@ -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(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); + }); +} diff --git a/src/templates/javascript/index.hbs b/src/templates/javascript/index.hbs index 49e3aae3..860ed55f 100644 --- a/src/templates/javascript/index.hbs +++ b/src/templates/javascript/index.hbs @@ -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}} diff --git a/src/templates/javascript/schema.hbs b/src/templates/javascript/schema.hbs deleted file mode 100644 index 24d72018..00000000 --- a/src/templates/javascript/schema.hbs +++ /dev/null @@ -1,2 +0,0 @@ -/* istanbul ignore file */ -/* eslint-disable */ diff --git a/src/templates/javascript/service.hbs b/src/templates/javascript/service.hbs index 0f0af42f..607ac5e3 100644 --- a/src/templates/javascript/service.hbs +++ b/src/templates/javascript/service.hbs @@ -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}} diff --git a/src/templates/typescript/core/ApiError.ts b/src/templates/typescript/core/ApiError.ts new file mode 100644 index 00000000..041adcdd --- /dev/null +++ b/src/templates/typescript/core/ApiError.ts @@ -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, 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); + } +} diff --git a/src/templates/typescript/core/Dictionary.ts b/src/templates/typescript/core/Dictionary.ts new file mode 100644 index 00000000..8008973b --- /dev/null +++ b/src/templates/typescript/core/Dictionary.ts @@ -0,0 +1,3 @@ +export interface Dictionary { + [key: string]: T; +} diff --git a/src/templates/typescript/core/OpenAPI.ts b/src/templates/typescript/core/OpenAPI.ts new file mode 100644 index 00000000..1cb106bb --- /dev/null +++ b/src/templates/typescript/core/OpenAPI.ts @@ -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}'; +} diff --git a/src/templates/typescript/core/RequestOptions.ts b/src/templates/typescript/core/RequestOptions.ts new file mode 100644 index 00000000..a1b2cfed --- /dev/null +++ b/src/templates/typescript/core/RequestOptions.ts @@ -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; +} diff --git a/src/templates/typescript/core/Result.ts b/src/templates/typescript/core/Result.ts new file mode 100644 index 00000000..42c51286 --- /dev/null +++ b/src/templates/typescript/core/Result.ts @@ -0,0 +1,11 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export interface Result { + url: string; + ok: boolean; + status: number; + statusText: string; + body: T; +} diff --git a/src/templates/typescript/core/getFormData.ts b/src/templates/typescript/core/getFormData.ts new file mode 100644 index 00000000..695d7119 --- /dev/null +++ b/src/templates/typescript/core/getFormData.ts @@ -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; +} diff --git a/src/templates/typescript/core/getQueryString.ts b/src/templates/typescript/core/getQueryString.ts new file mode 100644 index 00000000..4ecb874b --- /dev/null +++ b/src/templates/typescript/core/getQueryString.ts @@ -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 ''; +} diff --git a/src/templates/typescript/core/isSuccess.ts b/src/templates/typescript/core/isSuccess.ts new file mode 100644 index 00000000..272e7122 --- /dev/null +++ b/src/templates/typescript/core/isSuccess.ts @@ -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; +} diff --git a/src/templates/typescript/core/isValidRequiredParam.ts b/src/templates/typescript/core/isValidRequiredParam.ts new file mode 100644 index 00000000..d65dd37b --- /dev/null +++ b/src/templates/typescript/core/isValidRequiredParam.ts @@ -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.`); + } +} diff --git a/src/templates/typescript/core/request.ts b/src/templates/typescript/core/request.ts new file mode 100644 index 00000000..2cb7d412 --- /dev/null +++ b/src/templates/typescript/core/request.ts @@ -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(options: Readonly): Promise> { + // 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(url, request); + } catch (error) { + return { + url, + ok: false, + status: 0, + statusText: '', + body: error, + }; + } +} diff --git a/src/templates/typescript/core/requestUsingFetch.ts b/src/templates/typescript/core/requestUsingFetch.ts new file mode 100644 index 00000000..ea133038 --- /dev/null +++ b/src/templates/typescript/core/requestUsingFetch.ts @@ -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(url: string, request: Readonly): Promise> { + // 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; +} diff --git a/src/templates/typescript/core/requestUsingXHR.ts b/src/templates/typescript/core/requestUsingXHR.ts new file mode 100644 index 00000000..7203899d --- /dev/null +++ b/src/templates/typescript/core/requestUsingXHR.ts @@ -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(url: string, request: Readonly): Promise> { + 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); + }); +} diff --git a/src/templates/typescript/index.hbs b/src/templates/typescript/index.hbs index bbf3c632..4cba2408 100644 --- a/src/templates/typescript/index.hbs +++ b/src/templates/typescript/index.hbs @@ -2,20 +2,16 @@ /* tslint:disable */ /* eslint-disable */ -const server = '{{{server}}}'; -const version = '{{{version}}}'; +export { ApiError } from './core/ApiError'; +export { isSuccess } from './core/isSuccess'; +export { Dictionary } from './core/Dictionary'; +export { OpenAPI } from './core/OpenAPI'; {{#if models}} {{#each models}} export { {{{base}}} } from './models/{{{base}}}'; {{/each}} {{/if}} -{{#if schemas}} - -{{#each schemas}} -export { {{{base}}} } from './schemas/{{{base}}}'; -{{/each}} -{{/if}} {{#if services}} {{#each services}} diff --git a/src/templates/typescript/model.hbs b/src/templates/typescript/model.hbs index c4de2f5e..6d258690 100644 --- a/src/templates/typescript/model.hbs +++ b/src/templates/typescript/model.hbs @@ -7,6 +7,7 @@ import { {{{this}}} } from '../models/{{{this}}}'; {{/each}} {{/if}} +import { Dictionary } from '../core/Dictionary'; import * as yup from 'yup'; {{#if description}} diff --git a/src/templates/typescript/schema.hbs b/src/templates/typescript/schema.hbs deleted file mode 100644 index d592379e..00000000 --- a/src/templates/typescript/schema.hbs +++ /dev/null @@ -1,3 +0,0 @@ -/* istanbul ignore file */ -/* tslint:disable */ -/* eslint-disable */ diff --git a/src/templates/typescript/service.hbs b/src/templates/typescript/service.hbs index 8547a9f8..1938841c 100644 --- a/src/templates/typescript/service.hbs +++ b/src/templates/typescript/service.hbs @@ -1,7 +1,18 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import * as yup from 'yup'; + +{{#if imports}} +{{#each imports}} +import { {{{this}}} } from '../models/{{{this}}}'; +{{/each}} +{{/if}} +import { ApiError, catchGenericError } from '../core/ApiError'; +import { request } from '../core/request'; +import { Dictionary } from '../core/Dictionary'; +import { isValidRequiredParam } from '../core/isValidRequiredParam'; +import { OpenAPI } from '../core/OpenAPI'; +import { Result } from '../core/Result'; export class {{{name}}} { @@ -26,12 +37,11 @@ export class {{{name}}} { {{#if parameters}} {{#each parameters}} - yup.schema.validate(); isValidRequiredParam({{{name}}}, '{{{name}}}'); {{/each}} {{/if}} - const result = await request<{{{result}}}>({ + const result: Result<{{{result}}}> = await request<{{{result}}}>({ method: '{{{method}}}', path: `{{{path}}}`,{{#if parametersHeader}} headers: { @@ -51,8 +61,8 @@ export class {{{name}}} { },{{/if}}{{#if parametersBody}} body: {{{parametersBody.name}}},{{/if}} }); - {{#if errors}} + if (!result.ok) { switch (result.status) { {{#each errors}} diff --git a/src/utils/getOpenApiSpec.ts b/src/utils/getOpenApiSpec.ts index d527bb74..bde0416e 100644 --- a/src/utils/getOpenApiSpec.ts +++ b/src/utils/getOpenApiSpec.ts @@ -24,9 +24,9 @@ function read(filePath: string): string { * @param filePath */ export function getOpenApiSpec(filePath: string): any { - const content = read(filePath); + const content: string = read(filePath); - const extname = path.extname(filePath).toLowerCase(); + const extname: string = path.extname(filePath).toLowerCase(); switch (extname) { case '.yml': case '.yaml': diff --git a/src/utils/getOpenApiVersion.ts b/src/utils/getOpenApiVersion.ts index 97cd7fde..c9b0e394 100644 --- a/src/utils/getOpenApiVersion.ts +++ b/src/utils/getOpenApiVersion.ts @@ -10,11 +10,11 @@ export enum OpenApiVersion { * @param openApi The loaded spec (can be any object) */ export function getOpenApiVersion(openApi: any): OpenApiVersion | undefined { - const info = openApi.swagger || openApi.openapi; + const info: any = openApi.swagger || openApi.openapi; if (info && typeof info === 'string') { - const c = info.charAt(0); - const v = Number.parseInt(c); - if (!Number.isNaN(v) && (v === OpenApiVersion.V2 || v === OpenApiVersion.V3)) { + const c: string = info.charAt(0); + const v: number = Number.parseInt(c); + if (v === OpenApiVersion.V2 || v === OpenApiVersion.V3) { return v as OpenApiVersion; } } diff --git a/src/utils/getSortedImports.ts b/src/utils/getSortedImports.ts index fa1ac2e6..d6cc2e21 100644 --- a/src/utils/getSortedImports.ts +++ b/src/utils/getSortedImports.ts @@ -4,15 +4,11 @@ */ export function getSortedImports(imports: string[]): string[] { return imports - .filter(name => { - return name && name.trim(); - }) - .filter((name, index, arr) => { - return arr.indexOf(name) === index; - }) + .filter(name => name) + .filter((name, index, arr) => arr.indexOf(name) === index) .sort((a, b) => { - const nameA = a.toLowerCase(); - const nameB = b.toLowerCase(); + const nameA: string = a.toLowerCase(); + const nameB: string = b.toLowerCase(); return nameA.localeCompare(nameB, 'en'); }); } diff --git a/src/utils/getSortedModels.spec.ts b/src/utils/getSortedModels.spec.ts index ec383408..7630f5c4 100644 --- a/src/utils/getSortedModels.spec.ts +++ b/src/utils/getSortedModels.spec.ts @@ -8,7 +8,7 @@ describe('getSortedModels', () => { name: 'John', base: 'John', type: '', - template: '', + template: null, extends: [], imports: [], properties: [], @@ -18,7 +18,7 @@ describe('getSortedModels', () => { name: 'Jane', base: 'Jane', type: '', - template: '', + template: null, extends: [], imports: [], properties: [], @@ -28,7 +28,7 @@ describe('getSortedModels', () => { name: 'Doe', base: 'Doe', type: '', - template: '', + template: null, extends: [], imports: [], properties: [], diff --git a/src/utils/getSortedModels.ts b/src/utils/getSortedModels.ts index af0f1497..81656f4f 100644 --- a/src/utils/getSortedModels.ts +++ b/src/utils/getSortedModels.ts @@ -7,8 +7,8 @@ import { Model } from '../client/interfaces/Model'; export function getSortedModels(models: Map): Model[] { return ( Array.from(models.values()).sort((a, b) => { - const nameA = a.base.toLowerCase(); - const nameB = b.base.toLowerCase(); + const nameA: string = a.base.toLowerCase(); + const nameB: string = b.base.toLowerCase(); return nameA.localeCompare(nameB, 'en'); }) || [] ); diff --git a/src/utils/getSortedSchemas.spec.ts b/src/utils/getSortedSchemas.spec.ts deleted file mode 100644 index a53ccfca..00000000 --- a/src/utils/getSortedSchemas.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Schema } from '../client/interfaces/Schema'; -import { getSortedSchemas } from './getSortedSchemas'; - -describe('getSortedSchemas', () => { - it('should return sorted list', () => { - const schemas = new Map(); - schemas.set('John', { - name: 'John', - base: 'John', - imports: [], - }); - schemas.set('Jane', { - name: 'Jane', - base: 'Jane', - imports: [], - }); - schemas.set('Doe', { - name: 'Doe', - base: 'Doe', - imports: [], - }); - - expect(getSortedSchemas(new Map())).toEqual([]); - expect(getSortedSchemas(schemas)).toEqual([schemas.get('Doe'), schemas.get('Jane'), schemas.get('John')]); - }); -}); diff --git a/src/utils/getSortedSchemas.ts b/src/utils/getSortedSchemas.ts deleted file mode 100644 index eb799544..00000000 --- a/src/utils/getSortedSchemas.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Schema } from '../client/interfaces/Schema'; - -/** - * Convert a given Map to an Array and sort the result the Schema base name. - * @param schemas Map of Schema objects. - */ -export function getSortedSchemas(schemas: Map): Schema[] { - return ( - Array.from(schemas.values()).sort((a, b) => { - const nameA = a.base.toLowerCase(); - const nameB = b.base.toLowerCase(); - return nameA.localeCompare(nameB, 'en'); - }) || [] - ); -} diff --git a/src/utils/getSortedServices.ts b/src/utils/getSortedServices.ts index ecd7d4d0..4f511478 100644 --- a/src/utils/getSortedServices.ts +++ b/src/utils/getSortedServices.ts @@ -7,8 +7,8 @@ import { Service } from '../client/interfaces/Service'; export function getSortedServices(services: Map): Service[] { return ( Array.from(services.values()).sort((a, b) => { - const nameA = a.name.toLowerCase(); - const nameB = b.name.toLowerCase(); + const nameA: string = a.name.toLowerCase(); + const nameB: string = b.name.toLowerCase(); return nameA.localeCompare(nameB, 'en'); }) || [] ); diff --git a/src/utils/readHandlebarsTemplate.ts b/src/utils/readHandlebarsTemplate.ts index 8f8c77a3..3e8dd6a0 100644 --- a/src/utils/readHandlebarsTemplate.ts +++ b/src/utils/readHandlebarsTemplate.ts @@ -7,7 +7,7 @@ import * as handlebars from 'handlebars'; */ export function readHandlebarsTemplate(filePath: string): handlebars.TemplateDelegate { if (fs.existsSync(filePath)) { - const template = fs.readFileSync(filePath, 'utf8').toString(); + const template: string = fs.readFileSync(filePath, 'utf8').toString(); try { return handlebars.compile(template); } catch (e) { diff --git a/src/utils/readHandlebarsTemplates.spec.ts b/src/utils/readHandlebarsTemplates.spec.ts index 7758bd9c..9477ffb3 100644 --- a/src/utils/readHandlebarsTemplates.spec.ts +++ b/src/utils/readHandlebarsTemplates.spec.ts @@ -17,11 +17,9 @@ describe('readHandlebarsTemplates', () => { expect(template).toBeDefined(); expect(template.index).toBeDefined(); expect(template.model).toBeDefined(); - expect(template.schema).toBeDefined(); expect(template.service).toBeDefined(); expect(template.index({ message: 'Hello World!' })).toEqual('Hello World!'); expect(template.model({ message: 'Hello World!' })).toEqual('Hello World!'); - expect(template.schema({ message: 'Hello World!' })).toEqual('Hello World!'); expect(template.service({ message: 'Hello World!' })).toEqual('Hello World!'); }); }); diff --git a/src/utils/readHandlebarsTemplates.ts b/src/utils/readHandlebarsTemplates.ts index 80ca23e6..445c9b85 100644 --- a/src/utils/readHandlebarsTemplates.ts +++ b/src/utils/readHandlebarsTemplates.ts @@ -6,7 +6,6 @@ import * as path from 'path'; export interface Templates { index: handlebars.TemplateDelegate; model: handlebars.TemplateDelegate; - schema: handlebars.TemplateDelegate; service: handlebars.TemplateDelegate; } @@ -16,16 +15,14 @@ export interface Templates { * @param language The language we need to generate (Typescript or Javascript). */ export function readHandlebarsTemplates(language: Language): Templates { - const pathTemplateIndex = path.resolve(__dirname, `../../src/templates/${language}/index.hbs`); - const pathTemplateModel = path.resolve(__dirname, `../../src/templates/${language}/model.hbs`); - const pathTemplateSchema = path.resolve(__dirname, `../../src/templates/${language}/schema.hbs`); - const pathTemplateService = path.resolve(__dirname, `../../src/templates/${language}/service.hbs`); + const pathTemplateIndex: string = path.resolve(__dirname, `../../src/templates/${language}/index.hbs`); + const pathTemplateModel: string = path.resolve(__dirname, `../../src/templates/${language}/model.hbs`); + const pathTemplateService: string = path.resolve(__dirname, `../../src/templates/${language}/service.hbs`); try { return { index: readHandlebarsTemplate(pathTemplateIndex), model: readHandlebarsTemplate(pathTemplateModel), - schema: readHandlebarsTemplate(pathTemplateSchema), service: readHandlebarsTemplate(pathTemplateService), }; } catch (e) { diff --git a/src/utils/writeClient.spec.ts b/src/utils/writeClient.spec.ts index bf914817..f792da52 100644 --- a/src/utils/writeClient.spec.ts +++ b/src/utils/writeClient.spec.ts @@ -5,7 +5,6 @@ import * as fs from 'fs'; import { Client } from '../client/interfaces/Client'; import { Model } from '../client/interfaces/Model'; import { Templates } from './readHandlebarsTemplates'; -import { Schema } from '../client/interfaces/Schema'; import { Service } from '../client/interfaces/Service'; import { Language } from '../index'; @@ -23,14 +22,12 @@ describe('writeClient', () => { server: 'http://localhost:8080', version: 'v1', models: new Map(), - schemas: new Map(), services: new Map(), }; const templates: Templates = { index: () => 'dummy', model: () => 'dummy', - schema: () => 'dummy', service: () => 'dummy', }; diff --git a/src/utils/writeClient.ts b/src/utils/writeClient.ts index ccf7f93c..1d8ec250 100644 --- a/src/utils/writeClient.ts +++ b/src/utils/writeClient.ts @@ -8,9 +8,10 @@ import { Templates } from './readHandlebarsTemplates'; import { writeClientIndex } from './writeClientIndex'; import { getSortedModels } from './getSortedModels'; import { getSortedServices } from './getSortedServices'; -import { writeClientSchemas } from './writeClientSchemas'; -import { getSortedSchemas } from './getSortedSchemas'; import { Language } from '../index'; +import * as fs from 'fs'; +import { getFileName } from './getFileName'; +import * as glob from 'glob'; /** * Write our OpenAPI client, using the given templates at the given output path @@ -20,10 +21,9 @@ import { Language } from '../index'; * @param outputPath */ export function writeClient(client: Client, language: Language, templates: Templates, outputPath: string): void { - const outputPathCore = path.resolve(outputPath, 'core'); - const outputPathModels = path.resolve(outputPath, 'models'); - const outputPathSchemas = path.resolve(outputPath, 'schemas'); - const outputPathServices = path.resolve(outputPath, 'services'); + const outputPathCore: string = path.resolve(outputPath, 'core'); + const outputPathModels: string = path.resolve(outputPath, 'models'); + const outputPathServices: string = path.resolve(outputPath, 'services'); // Clean output directory try { @@ -37,17 +37,26 @@ export function writeClient(client: Client, language: Language, templates: Templ mkdirp.sync(outputPath); mkdirp.sync(outputPathCore); mkdirp.sync(outputPathModels); - mkdirp.sync(outputPathSchemas); mkdirp.sync(outputPathServices); } catch (e) { throw new Error(`Could not create output directories`); } + // Copy all core files + const coreFiles: string = path.resolve(__dirname, `../../src/templates/${language}/core/`); + const coreFilesExt: string = getFileName('*', language); + const coreFilesList: string[] = glob.sync(coreFilesExt, { cwd: coreFiles }); + coreFilesList.forEach(file => + fs.copyFileSync( + path.resolve(coreFiles, file), // From input path + path.resolve(outputPathCore, file) // To output path + ) + ); + // Write the client files try { writeClientIndex(client, language, templates.index, outputPath); writeClientModels(getSortedModels(client.models), language, templates.model, outputPathModels); - writeClientSchemas(getSortedSchemas(client.schemas), language, templates.schema, outputPathSchemas); writeClientServices(getSortedServices(client.services), language, templates.service, outputPathServices); } catch (e) { throw e; diff --git a/src/utils/writeClientIndex.spec.ts b/src/utils/writeClientIndex.spec.ts index 8249938d..b26a141c 100644 --- a/src/utils/writeClientIndex.spec.ts +++ b/src/utils/writeClientIndex.spec.ts @@ -2,7 +2,6 @@ import { writeClientIndex } from './writeClientIndex'; import * as fs from 'fs'; import { Client } from '../client/interfaces/Client'; import { Model } from '../client/interfaces/Model'; -import { Schema } from '../client/interfaces/Schema'; import { Service } from '../client/interfaces/Service'; import { Language } from '../index'; @@ -16,7 +15,6 @@ describe('writeClientIndex', () => { server: 'http://localhost:8080', version: 'v1', models: new Map(), - schemas: new Map(), services: new Map(), }; const template = () => 'dummy'; diff --git a/src/utils/writeClientIndex.ts b/src/utils/writeClientIndex.ts index c8112627..486a2782 100644 --- a/src/utils/writeClientIndex.ts +++ b/src/utils/writeClientIndex.ts @@ -4,7 +4,6 @@ import * as fs from 'fs'; import * as path from 'path'; import { getSortedModels } from './getSortedModels'; import { getSortedServices } from './getSortedServices'; -import { getSortedSchemas } from './getSortedSchemas'; import { Language } from '../index'; import { getFileName } from './getFileName'; @@ -18,7 +17,7 @@ import { getFileName } from './getFileName'; * @param outputPath: */ export function writeClientIndex(client: Client, language: Language, template: handlebars.TemplateDelegate, outputPath: string): void { - const fileName = getFileName('index', language); + const fileName: string = getFileName('index', language); try { fs.writeFileSync( path.resolve(outputPath, fileName), @@ -26,7 +25,6 @@ export function writeClientIndex(client: Client, language: Language, template: h server: client.server, version: client.version, models: getSortedModels(client.models), - schemas: getSortedSchemas(client.schemas), services: getSortedServices(client.services), }) ); diff --git a/src/utils/writeClientModels.ts b/src/utils/writeClientModels.ts index e0d9b5c0..3fe7cbea 100644 --- a/src/utils/writeClientModels.ts +++ b/src/utils/writeClientModels.ts @@ -14,7 +14,7 @@ import { getFileName } from './getFileName'; */ export function writeClientModels(models: Model[], language: Language, template: handlebars.TemplateDelegate, outputPath: string): void { models.forEach(model => { - const fileName = getFileName(model.name, language); + const fileName: string = getFileName(model.name, language); try { fs.writeFileSync(path.resolve(outputPath, fileName), template(model)); } catch (e) { diff --git a/src/utils/writeClientSchemas.spec.ts b/src/utils/writeClientSchemas.spec.ts deleted file mode 100644 index 46930a05..00000000 --- a/src/utils/writeClientSchemas.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as fs from 'fs'; -import { Schema } from '../client/interfaces/Schema'; -import { writeClientSchemas } from './writeClientSchemas'; -import { Language } from '../index'; - -jest.mock('fs'); - -const fsWriteFileSync = fs.writeFileSync as jest.MockedFunction; - -describe('writeClientSchemas', () => { - it('should write to filesystem', () => { - const schemas: Schema[] = [ - { - name: 'Item', - base: 'Item', - imports: [], - }, - ]; - const template = () => 'dummy'; - writeClientSchemas(schemas, Language.TYPESCRIPT, template, '/'); - expect(fsWriteFileSync).toBeCalledWith('/Item.ts', 'dummy'); - }); -}); diff --git a/src/utils/writeClientSchemas.ts b/src/utils/writeClientSchemas.ts deleted file mode 100644 index 26e87116..00000000 --- a/src/utils/writeClientSchemas.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as fs from 'fs'; -import * as handlebars from 'handlebars'; -import { Schema } from '../client/interfaces/Schema'; -import * as path from 'path'; -import { Language } from '../index'; -import { getFileName } from './getFileName'; - -/** - * Generate Schemas using the Handlebar template and write to disk. - * @param schemas: Array of Schemas to write. - * @param language: The output language (Typescript or javascript). - * @param template: The template that is used to write the file. - * @param outputPath: - */ -export function writeClientSchemas(schemas: Schema[], language: Language, template: handlebars.TemplateDelegate, outputPath: string): void { - schemas.forEach(schema => { - const fileName = getFileName(schema.name, language); - try { - fs.writeFileSync(path.resolve(outputPath, fileName), template(schema)); - } catch (e) { - throw new Error(`Could not write schema: "${fileName}"`); - } - }); -} diff --git a/src/utils/writeClientServices.ts b/src/utils/writeClientServices.ts index 8b366f24..980ad12b 100644 --- a/src/utils/writeClientServices.ts +++ b/src/utils/writeClientServices.ts @@ -14,7 +14,7 @@ import { getFileName } from './getFileName'; */ export function writeClientServices(services: Service[], language: Language, template: handlebars.TemplateDelegate, outputPath: string): void { services.forEach(service => { - const fileName = getFileName(service.name, language); + const fileName: string = getFileName(service.name, language); try { fs.writeFileSync(path.resolve(outputPath, fileName), template(service)); } catch (e) { diff --git a/test/index.js b/test/index.js index 575ca2ac..6d023357 100755 --- a/test/index.js +++ b/test/index.js @@ -12,9 +12,23 @@ OpenAPI.generate( ); OpenAPI.generate( - './test/mock/v2/test-petstore.json', - './test/tmp/v2/js/test-petstore', - OpenAPI.Language.JAVASCRIPT, + './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, ); diff --git a/test/mock/v2/test-addon.json b/test/mock/v2/test-addon.json index 030a51aa..b9926617 100644 --- a/test/mock/v2/test-addon.json +++ b/test/mock/v2/test-addon.json @@ -2,7 +2,7 @@ "x-generator": "NSwag v12.3.1.0 (NJsonSchema v9.14.1.0 (Newtonsoft.Json v12.0.0.0))", "swagger": "2.0", "info": { - "title": "Add-on Service API", + "title": "Tridion Add-on Service API", "version": "v1" }, "host": "10.91.90.47:83", diff --git a/test/mock/v3/test-petstore.yaml b/test/mock/v3/test-petstore.yaml deleted file mode 100644 index b1b88ab5..00000000 --- a/test/mock/v3/test-petstore.yaml +++ /dev/null @@ -1,103 +0,0 @@ -swagger: "2.0" -info: - version: 1.0.0 - title: Swagger Petstore - license: - name: MIT -host: petstore.swagger.io -basePath: /v1 -schemes: - - http -consumes: - - application/json -produces: - - application/json -paths: - /pets: - get: - summary: List all pets - operationId: listPets - tags: - - pets - parameters: - - name: limit - in: query - description: How many items to return at one time (max 100) - required: false - type: integer - format: int32 - responses: - "200": - description: A paged array of pets - headers: - x-next: - type: string - description: A link to the next page of responses - schema: - $ref: '#/definitions/Pets' - default: - description: unexpected error - schema: - $ref: '#/definitions/Error' - post: - summary: Create a pet - operationId: createPets - tags: - - pets - responses: - "201": - description: Null response - default: - description: unexpected error - schema: - $ref: '#/definitions/Error' - /pets/{petId}: - get: - summary: Info for a specific pet - operationId: showPetById - tags: - - pets - parameters: - - name: petId - in: path - required: true - description: The id of the pet to retrieve - type: string - responses: - "200": - description: Expected response to a valid request - schema: - $ref: '#/definitions/Pets' - default: - description: unexpected error - schema: - $ref: '#/definitions/Error' -definitions: - Pet: - type: "object" - required: - - id - - name - properties: - id: - type: integer - format: int64 - name: - type: string - tag: - type: string - Pets: - type: array - items: - $ref: '#/definitions/Pet' - Error: - type: "object" - required: - - code - - message - properties: - code: - type: integer - format: int32 - message: - type: string diff --git a/yarn.lock b/yarn.lock index 4970d301..3c4c7eb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2161,6 +2161,18 @@ glob-parent@^5.0.0: dependencies: is-glob "^4.0.1" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: version "7.1.5" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.5.tgz#6714c69bee20f3c3e64c4dd905553e532b40cdc0"