···11+import type { OpenApi as OpenApiV2 } from '../../v2/interfaces/OpenApi';
22+import type { OpenApi as OpenApiV3 } from '../../v3/interfaces/OpenApi';
33+44+export type OpenApi = OpenApiV2 | OpenApiV3;
···11+/**
22+ * Convert the service version to 'normal' version.
33+ * This basically removes any "v" prefix from the version string.
44+ * @param version
55+ */
66+export function getServiceVersion(version = '1.0'): string {
77+ return String(version).replace(/^v/gi, '');
88+}
+13
src/openApi/common/parser/sort.ts
···11+/**
22+ * Sort list of values and ensure that required parameters are first so that we do not generate
33+ * invalid types. Optional parameters cannot be positioned after required ones.
44+ */
55+export function toSortedByRequired<T extends { isRequired: boolean; default?: string }>(values: T[]): T[] {
66+ return values.sort((a, b) => {
77+ const aNeedsValue = a.isRequired && a.default === undefined;
88+ const bNeedsValue = b.isRequired && b.default === undefined;
99+ if (aNeedsValue && !bNeedsValue) return -1;
1010+ if (bNeedsValue && !aNeedsValue) return 1;
1111+ return 0;
1212+ });
1313+}
+20
src/openApi/index.ts
···11+import type { Client } from '../types/client';
22+import type { Config } from '../types/config';
33+import { OpenApi } from './common/interfaces/OpenApi';
44+import { parse as parseV2 } from './v2/index';
55+import { parse as parseV3 } from './v3/index';
66+77+/**
88+ * Parse the OpenAPI specification to a Client model that contains
99+ * all the models, services and schema's we should output.
1010+ * @param openApi The OpenAPI spec that we have loaded from disk.
1111+ * @param options {@link Config} passed to the `createClient()` method
1212+ */
1313+export function parse(openApi: OpenApi, config: Config): Client {
1414+ if ('openapi' in openApi) {
1515+ return parseV3(openApi, config);
1616+ } else if ('swagger' in openApi) {
1717+ return parseV2(openApi, config);
1818+ }
1919+ throw new Error(`Unsupported Open API specification: ${JSON.stringify(openApi, null, 2)}`);
2020+}
+1-1
src/openApi/v2/index.ts
···11import type { Client } from '../../types/client';
22import type { Config } from '../../types/config';
33+import { getServiceVersion } from '../common/parser/service';
34import type { OpenApi } from './interfaces/OpenApi';
45import { getModels } from './parser/getModels';
56import { getServer } from './parser/getServer';
67import { getServices } from './parser/getServices';
77-import { getServiceVersion } from './parser/getServiceVersion';
8899/**
1010 * Parse the OpenAPI specification to a Client model that contains
+3-10
src/openApi/v2/parser/getOperation.ts
···11-import type { Operation, OperationParameter, OperationParameters } from '../../../types/client';
11+import type { Operation, OperationParameters } from '../../../types/client';
22import type { Config } from '../../../types/config';
33import { getOperationName } from '../../../utils/operation';
44+import { toSortedByRequired } from '../../common/parser/sort';
45import type { OpenApi } from '../interfaces/OpenApi';
56import type { OpenApiOperation } from '../interfaces/OpenApiOperation';
67import { getOperationErrors } from './getOperationErrors';
···910import { getOperationResponses } from './getOperationResponses';
1011import { getOperationResults } from './getOperationResults';
1112import { getServiceName } from './getServiceName';
1212-1313-const sortByRequired = (a: OperationParameter, b: OperationParameter): number => {
1414- const aNeedsValue = a.isRequired && a.default === undefined;
1515- const bNeedsValue = b.isRequired && b.default === undefined;
1616- if (aNeedsValue && !bNeedsValue) return -1;
1717- if (bNeedsValue && !aNeedsValue) return 1;
1818- return 0;
1919-};
20132114export const getOperation = (
2215 openApi: OpenApi,
···7972 });
8073 }
81748282- operation.parameters = operation.parameters.sort(sortByRequired);
7575+ operation.parameters = toSortedByRequired(operation.parameters);
83768477 return operation;
8578};
+1-1
src/openApi/v2/parser/getOperationParameter.ts
···22import { getEnums } from '../../../utils/getEnums';
33import { getPattern } from '../../../utils/getPattern';
44import { getType } from '../../../utils/type';
55+import { getRef } from '../../common/parser/getRef';
56import type { OpenApi } from '../interfaces/OpenApi';
67import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
78import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
89import { getModel } from './getModel';
910import { getOperationParameterDefault } from './getOperationParameterDefault';
1011import { getOperationParameterName } from './getOperationParameterName';
1111-import { getRef } from './getRef';
12121313export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParameter): OperationParameter => {
1414 const operationParameter: OperationParameter = {
+1-1
src/openApi/v2/parser/getOperationParameters.ts
···11import type { OperationParameters } from '../../../types/client';
22+import { getRef } from '../../common/parser/getRef';
23import type { OpenApi } from '../interfaces/OpenApi';
34import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
45import { getOperationParameter } from './getOperationParameter';
55-import { getRef } from './getRef';
6677export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiParameter[]): OperationParameters => {
88 const operationParameters: OperationParameters = {
+1-1
src/openApi/v2/parser/getOperationResponse.ts
···11import type { OperationResponse } from '../../../types/client';
22import { getPattern } from '../../../utils/getPattern';
33import { getType } from '../../../utils/type';
44+import { getRef } from '../../common/parser/getRef';
45import type { OpenApi } from '../interfaces/OpenApi';
56import type { OpenApiResponse } from '../interfaces/OpenApiResponse';
67import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
78import { getModel } from './getModel';
88-import { getRef } from './getRef';
991010export const getOperationResponse = (
1111 openApi: OpenApi,
+1-1
src/openApi/v2/parser/getOperationResponses.ts
···11import type { OperationResponse } from '../../../types/client';
22+import { getRef } from '../../common/parser/getRef';
23import type { OpenApi } from '../interfaces/OpenApi';
34import type { OpenApiResponse } from '../interfaces/OpenApiResponse';
45import type { OpenApiResponses } from '../interfaces/OpenApiResponses';
56import { getOperationResponse } from './getOperationResponse';
67import { getOperationResponseCode } from './getOperationResponseCode';
77-import { getRef } from './getRef';
8899export const getOperationResponses = (openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] => {
1010 const operationResponses: OperationResponse[] = [];
-35
src/openApi/v2/parser/getRef.spec.ts
···11-import { describe, expect, it } from 'vitest';
22-33-import { getRef } from './getRef';
44-55-describe('getRef', () => {
66- it('should produce correct result', () => {
77- expect(
88- getRef(
99- {
1010- swagger: '2.0',
1111- info: {
1212- title: 'dummy',
1313- version: '1.0',
1414- },
1515- host: 'localhost:8080',
1616- basePath: '/api',
1717- schemes: ['http', 'https'],
1818- paths: {},
1919- definitions: {
2020- Example: {
2121- description: 'This is an Example model ',
2222- type: 'integer',
2323- },
2424- },
2525- },
2626- {
2727- $ref: '#/definitions/Example',
2828- }
2929- )
3030- ).toEqual({
3131- description: 'This is an Example model ',
3232- type: 'integer',
3333- });
3434- });
3535-});
-33
src/openApi/v2/parser/getRef.ts
···11-import type { OpenApi } from '../interfaces/OpenApi';
22-import type { OpenApiReference } from '../interfaces/OpenApiReference';
33-44-const ESCAPED_REF_SLASH = /~1/g;
55-const ESCAPED_REF_TILDE = /~0/g;
66-77-export const getRef = <T>(openApi: OpenApi, item: T & OpenApiReference): T => {
88- if (item.$ref) {
99- // Fetch the paths to the definitions, this converts:
1010- // "#/definitions/Form" to ["definitions", "Form"]
1111- const paths = item.$ref
1212- .replace(/^#/g, '')
1313- .split('/')
1414- .filter(item => item);
1515-1616- // Try to find the reference by walking down the path,
1717- // if we cannot find it, then we throw an error.
1818- let result = openApi;
1919- paths.forEach(path => {
2020- const decodedPath = decodeURIComponent(
2121- path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~')
2222- );
2323- if (result.hasOwnProperty(decodedPath)) {
2424- // @ts-ignore
2525- result = result[decodedPath];
2626- } else {
2727- throw new Error(`Could not find reference: "${item.$ref}"`);
2828- }
2929- });
3030- return result as T;
3131- }
3232- return item as T;
3333-};
···11import type { Model } from '../../../types/client';
22+import { getRef } from '../../common/parser/getRef';
23import type { OpenApi } from '../interfaces/OpenApi';
34import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
45import type { getModel } from './getModel';
55-import { getRef } from './getRef';
6677// Fix for circular dependency
88export type GetModelFn = typeof getModel;
-11
src/openApi/v2/parser/getServiceVersion.spec.ts
···11-import { describe, expect, it } from 'vitest';
22-33-import { getServiceVersion } from './getServiceVersion';
44-55-describe('getServiceVersion', () => {
66- it('should produce correct result', () => {
77- expect(getServiceVersion('1.0')).toEqual('1.0');
88- expect(getServiceVersion('v1.0')).toEqual('1.0');
99- expect(getServiceVersion('V1.0')).toEqual('1.0');
1010- });
1111-});
-6
src/openApi/v2/parser/getServiceVersion.ts
···11-/**
22- * Convert the service version to 'normal' version.
33- * This basically removes any "v" prefix from the version string.
44- * @param version
55- */
66-export const getServiceVersion = (version = '1.0'): string => String(version).replace(/^v/gi, '');
+1-1
src/openApi/v3/index.ts
···11import type { Client } from '../../types/client';
22import type { Config } from '../../types/config';
33+import { getServiceVersion } from '../common/parser/service';
34import type { OpenApi } from './interfaces/OpenApi';
45import { getModels } from './parser/getModels';
56import { getServer } from './parser/getServer';
67import { getServices } from './parser/getServices';
77-import { getServiceVersion } from './parser/service';
8899/**
1010 * Parse the OpenAPI specification to a Client model that contains
+1-9
src/openApi/v3/parser/__tests__/service.spec.ts
···11import { describe, expect, it } from 'vitest';
2233-import { getServiceName, getServiceVersion } from '../service';
33+import { getServiceName } from '../service';
4455describe('getServiceName', () => {
66 it('should produce correct result', () => {
···1414 expect(getServiceName('non-ascii-æøåÆØÅöôêÊ字符串')).toEqual('NonAsciiÆøåÆøÅöôêÊ字符串');
1515 });
1616});
1717-1818-describe('getServiceVersion', () => {
1919- it('should produce correct result', () => {
2020- expect(getServiceVersion('1.0')).toEqual('1.0');
2121- expect(getServiceVersion('v1.0')).toEqual('1.0');
2222- expect(getServiceVersion('V1.0')).toEqual('1.0');
2323- });
2424-});
+1-1
src/openApi/v3/parser/getOperationParameter.ts
···11import type { OperationParameter } from '../../../types/client';
22import { getPattern } from '../../../utils/getPattern';
33import { getType } from '../../../utils/type';
44+import { getRef } from '../../common/parser/getRef';
45import type { OpenApi } from '../interfaces/OpenApi';
56import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
67import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
78import { getModel } from './getModel';
89import { getModelDefault } from './getModelDefault';
910import { getOperationParameterName } from './getOperationParameterName';
1010-import { getRef } from './getRef';
11111212export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParameter): OperationParameter => {
1313 const operationParameter: OperationParameter = {
+1-1
src/openApi/v3/parser/getOperationParameters.ts
···11import type { OperationParameters } from '../../../types/client';
22+import { getRef } from '../../common/parser/getRef';
23import type { OpenApi } from '../interfaces/OpenApi';
34import type { OpenApiParameter } from '../interfaces/OpenApiParameter';
45import { getOperationParameter } from './getOperationParameter';
55-import { getRef } from './getRef';
6677const allowedIn = ['cookie', 'formData', 'header', 'path', 'query'] as const;
88
+1-1
src/openApi/v3/parser/getOperationResponse.ts
···11import type { OperationResponse } from '../../../types/client';
22import { getPattern } from '../../../utils/getPattern';
33import { getType } from '../../../utils/type';
44+import { getRef } from '../../common/parser/getRef';
45import type { OpenApi } from '../interfaces/OpenApi';
56import type { OpenApiResponse } from '../interfaces/OpenApiResponse';
67import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
78import { getContent } from './getContent';
89import { getModel } from './getModel';
99-import { getRef } from './getRef';
10101111export const getOperationResponse = (
1212 openApi: OpenApi,
+1-1
src/openApi/v3/parser/getOperationResponses.ts
···11import type { OperationResponse } from '../../../types/client';
22+import { getRef } from '../../common/parser/getRef';
23import type { OpenApi } from '../interfaces/OpenApi';
34import type { OpenApiResponse } from '../interfaces/OpenApiResponse';
45import type { OpenApiResponses } from '../interfaces/OpenApiResponses';
56import { getOperationResponse } from './getOperationResponse';
67import { getOperationResponseCode } from './getOperationResponseCode';
77-import { getRef } from './getRef';
8899export const getOperationResponses = (openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] => {
1010 const operationResponses: OperationResponse[] = [];
···11-import type { OpenApi } from '../interfaces/OpenApi';
22-import type { OpenApiReference } from '../interfaces/OpenApiReference';
11+import type { OpenApiReference as OpenApiReferenceV2 } from '../../v2/interfaces/OpenApiReference';
22+import type { OpenApiReference as OpenApiReferenceV3 } from '../../v3/interfaces/OpenApiReference';
33+import { OpenApi } from '../interfaces/OpenApi';
3445const ESCAPED_REF_SLASH = /~1/g;
56const ESCAPED_REF_TILDE = /~0/g;
6777-export const getRef = <T>(openApi: OpenApi, item: T & OpenApiReference): T => {
88+export function getRef<T>(openApi: OpenApi, item: T & (OpenApiReferenceV2 | OpenApiReferenceV3)): T {
89 if (item.$ref) {
910 // Fetch the paths to the definitions, this converts:
1011 // "#/components/schemas/Form" to ["components", "schemas", "Form"]
···3031 return result as T;
3132 }
3233 return item as T;
3333-};
3434+}
···11import type { Model } from '../../../types/client';
22+import { getRef } from '../../common/parser/getRef';
23import type { OpenApi } from '../interfaces/OpenApi';
34import type { OpenApiSchema } from '../interfaces/OpenApiSchema';
45import type { getModel } from './getModel';
55-import { getRef } from './getRef';
6677// Fix for circular dependency
88export type GetModelFn = typeof getModel;
+4-10
src/openApi/v3/parser/operation.ts
···11import type { Operation, OperationParameter, OperationParameters } from '../../../types/client';
22import type { Config } from '../../../types/config';
33import { getOperationName } from '../../../utils/operation';
44+import { getRef } from '../../common/parser/getRef';
55+import { toSortedByRequired } from '../../common/parser/sort';
46import type { OpenApi } from '../interfaces/OpenApi';
57import type { OpenApiOperation } from '../interfaces/OpenApiOperation';
68import type { OpenApiRequestBody } from '../interfaces/OpenApiRequestBody';
···1012import { getOperationResponseHeader } from './getOperationResponseHeader';
1113import { getOperationResponses } from './getOperationResponses';
1214import { getOperationResults } from './getOperationResults';
1313-import { getRef } from './getRef';
1415import { getServiceName } from './service';
15161617// add global path parameters, skip duplicate names
···109110 operation.parametersPath = mergeParameters(operation.parametersPath, pathParams.parametersPath);
110111 operation.parametersQuery = mergeParameters(operation.parametersQuery, pathParams.parametersQuery);
111112112112- // place required parameters first so we don't generate invalid types since
113113- // optional parameters cannot be positioned before required ones
114114- operation.parameters = operation.parameters.sort((a: OperationParameter, b: OperationParameter): number => {
115115- const aNeedsValue = a.isRequired && a.default === undefined;
116116- const bNeedsValue = b.isRequired && b.default === undefined;
117117- if (aNeedsValue && !bNeedsValue) return -1;
118118- if (bNeedsValue && !aNeedsValue) return 1;
119119- return 0;
120120- });
113113+ // Sort by required
114114+ operation.parameters = toSortedByRequired(operation.parameters);
121115122116 return operation;
123117};
-7
src/openApi/v3/parser/service.ts
···1212 const clean = sanitizeServiceName(value).trim();
1313 return camelCase(clean, { pascalCase: true });
1414};
1515-1616-/**
1717- * Convert the service version to 'normal' version.
1818- * This basically removes any "v" prefix from the version string.
1919- * @param version
2020- */
2121-export const getServiceVersion = (version = '1.0'): string => String(version).replace(/^v/gi, '');
+2-3
src/utils/getOpenApiSpec.ts
···3344import $RefParser from '@apidevtools/json-schema-ref-parser';
5566-import type { OpenApi as OpenApiV2 } from '../openApi/v2/interfaces/OpenApi';
77-import type { OpenApi as OpenApiV3 } from '../openApi/v3/interfaces/OpenApi';
66+import type { OpenApi } from '../openApi/common/interfaces/OpenApi';
8798/**
109 * Load and parse te open api spec. If the file extension is ".yml" or ".yaml"
···1413 */
1514export const getOpenApiSpec = async (location: string) => {
1615 const absolutePathOrUrl = existsSync(location) ? path.resolve(location) : location;
1717- const schema = (await $RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {})) as OpenApiV2 | OpenApiV3;
1616+ const schema = (await $RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {})) as OpenApi;
1817 return schema;
1918};