fork of hey-api/openapi-ts because I need some additional things
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #107 from jordanshatford/feat/unify-v2-v3-parsing

feat: unify v2 v3 parsing

authored by

Lubos and committed by
GitHub
a848dba7 5d8c1439

+258 -255
+2 -100
src/index.spec.ts
··· 1 - import { afterEach, describe, expect, it, vi } from 'vitest'; 1 + import { describe, it } from 'vitest'; 2 2 3 - import { createClient, parseOpenApiSpecification } from './index'; 4 - import * as parseV2 from './openApi/v2'; 5 - import * as parseV3 from './openApi/v3'; 3 + import { createClient } from './index'; 6 4 7 5 describe('index', () => { 8 6 it('parses v2 without issues', async () => { ··· 37 35 }); 38 36 }); 39 37 }); 40 - 41 - describe('parseOpenApiSpecification', () => { 42 - afterEach(() => { 43 - vi.restoreAllMocks(); 44 - }); 45 - 46 - const options: Parameters<typeof parseOpenApiSpecification>[1] = { 47 - client: 'fetch', 48 - enums: true, 49 - exportCore: true, 50 - exportModels: true, 51 - exportSchemas: true, 52 - exportServices: true, 53 - format: true, 54 - input: '', 55 - lint: false, 56 - operationId: true, 57 - output: '', 58 - postfixModels: '', 59 - postfixServices: '', 60 - serviceResponse: 'body', 61 - useDateType: false, 62 - useOptions: true, 63 - write: false, 64 - }; 65 - 66 - it('uses v2 parser', () => { 67 - const spy = vi.spyOn(parseV2, 'parse'); 68 - 69 - const spec: Parameters<typeof parseOpenApiSpecification>[0] = { 70 - info: { 71 - title: 'dummy', 72 - version: '1.0', 73 - }, 74 - paths: {}, 75 - swagger: '2', 76 - }; 77 - parseOpenApiSpecification(spec, options); 78 - expect(spy).toHaveBeenCalledWith(spec, options); 79 - 80 - const spec2: Parameters<typeof parseOpenApiSpecification>[0] = { 81 - info: { 82 - title: 'dummy', 83 - version: '1.0', 84 - }, 85 - paths: {}, 86 - swagger: '2.0', 87 - }; 88 - parseOpenApiSpecification(spec2, options); 89 - expect(spy).toHaveBeenCalledWith(spec2, options); 90 - }); 91 - 92 - it('uses v3 parser', () => { 93 - const spy = vi.spyOn(parseV3, 'parse'); 94 - 95 - const spec: Parameters<typeof parseOpenApiSpecification>[0] = { 96 - info: { 97 - title: 'dummy', 98 - version: '1.0', 99 - }, 100 - openapi: '3', 101 - paths: {}, 102 - }; 103 - parseOpenApiSpecification(spec, options); 104 - expect(spy).toHaveBeenCalledWith(spec, options); 105 - 106 - const spec2: Parameters<typeof parseOpenApiSpecification>[0] = { 107 - info: { 108 - title: 'dummy', 109 - version: '1.0', 110 - }, 111 - openapi: '3.0', 112 - paths: {}, 113 - }; 114 - parseOpenApiSpecification(spec2, options); 115 - expect(spy).toHaveBeenCalledWith(spec2, options); 116 - 117 - const spec3: Parameters<typeof parseOpenApiSpecification>[0] = { 118 - info: { 119 - title: 'dummy', 120 - version: '1.0', 121 - }, 122 - openapi: '3.1.0', 123 - paths: {}, 124 - }; 125 - parseOpenApiSpecification(spec3, options); 126 - expect(spy).toHaveBeenCalledWith(spec3, options); 127 - }); 128 - 129 - it('throws on unknown version', () => { 130 - // @ts-ignore 131 - expect(() => parseOpenApiSpecification({ foo: 'bar' }, options)).toThrow( 132 - `Unsupported Open API specification: ${JSON.stringify({ foo: 'bar' }, null, 2)}` 133 - ); 134 - }); 135 - });
+2 -13
src/index.ts
··· 4 4 5 5 import { sync } from 'cross-spawn'; 6 6 7 - import { parse as parseV2 } from './openApi/v2'; 8 - import { parse as parseV3 } from './openApi/v3'; 7 + import { parse } from './openApi'; 9 8 import type { Client } from './types/client'; 10 9 import type { Config, UserConfig } from './types/config'; 11 10 import { getOpenApiSpec } from './utils/getOpenApiSpec'; ··· 18 17 19 18 // TODO: add support for `openapi-ts.config.ts` 20 19 const configFiles = ['openapi-ts.config.js', 'openapi-ts.config.cjs', 'openapi-ts.config.mjs']; 21 - 22 - export const parseOpenApiSpecification = (openApi: Awaited<ReturnType<typeof getOpenApiSpec>>, config: Config) => { 23 - if ('openapi' in openApi) { 24 - return parseV3(openApi, config); 25 - } 26 - if ('swagger' in openApi) { 27 - return parseV2(openApi, config); 28 - } 29 - throw new Error(`Unsupported Open API specification: ${JSON.stringify(openApi, null, 2)}`); 30 - }; 31 20 32 21 const processOutput = (config: Config, dependencies: Dependencies) => { 33 22 if (config.format) { ··· 174 163 ? await getOpenApiSpec(config.input) 175 164 : (config.input as unknown as Awaited<ReturnType<typeof getOpenApiSpec>>); 176 165 177 - const client = postProcessClient(parseOpenApiSpecification(openApi, config)); 166 + const client = postProcessClient(parse(openApi, config)); 178 167 const templates = registerHandlebarTemplates(config, client); 179 168 180 169 if (config.write) {
+101
src/openApi/__tests__/index.spec.ts
··· 1 + import { afterEach, describe, expect, it, vi } from 'vitest'; 2 + 3 + import { parse } from '..'; 4 + import * as parseV2 from '../v2'; 5 + import * as parseV3 from '../v3'; 6 + 7 + describe('parse', () => { 8 + afterEach(() => { 9 + vi.restoreAllMocks(); 10 + }); 11 + 12 + const options: Parameters<typeof parse>[1] = { 13 + client: 'fetch', 14 + enums: true, 15 + exportCore: true, 16 + exportModels: true, 17 + exportSchemas: true, 18 + exportServices: true, 19 + format: true, 20 + input: '', 21 + lint: false, 22 + operationId: true, 23 + output: '', 24 + postfixModels: '', 25 + postfixServices: '', 26 + serviceResponse: 'body', 27 + useDateType: false, 28 + useOptions: true, 29 + write: false, 30 + }; 31 + 32 + it('uses v2 parser', () => { 33 + const spy = vi.spyOn(parseV2, 'parse'); 34 + 35 + const spec: Parameters<typeof parse>[0] = { 36 + info: { 37 + title: 'dummy', 38 + version: '1.0', 39 + }, 40 + paths: {}, 41 + swagger: '2', 42 + }; 43 + parse(spec, options); 44 + expect(spy).toHaveBeenCalledWith(spec, options); 45 + 46 + const spec2: Parameters<typeof parse>[0] = { 47 + info: { 48 + title: 'dummy', 49 + version: '1.0', 50 + }, 51 + paths: {}, 52 + swagger: '2.0', 53 + }; 54 + parse(spec2, options); 55 + expect(spy).toHaveBeenCalledWith(spec2, options); 56 + }); 57 + 58 + it('uses v3 parser', () => { 59 + const spy = vi.spyOn(parseV3, 'parse'); 60 + 61 + const spec: Parameters<typeof parse>[0] = { 62 + info: { 63 + title: 'dummy', 64 + version: '1.0', 65 + }, 66 + openapi: '3', 67 + paths: {}, 68 + }; 69 + parse(spec, options); 70 + expect(spy).toHaveBeenCalledWith(spec, options); 71 + 72 + const spec2: Parameters<typeof parse>[0] = { 73 + info: { 74 + title: 'dummy', 75 + version: '1.0', 76 + }, 77 + openapi: '3.0', 78 + paths: {}, 79 + }; 80 + parse(spec2, options); 81 + expect(spy).toHaveBeenCalledWith(spec2, options); 82 + 83 + const spec3: Parameters<typeof parse>[0] = { 84 + info: { 85 + title: 'dummy', 86 + version: '1.0', 87 + }, 88 + openapi: '3.1.0', 89 + paths: {}, 90 + }; 91 + parse(spec3, options); 92 + expect(spy).toHaveBeenCalledWith(spec3, options); 93 + }); 94 + 95 + it('throws on unknown version', () => { 96 + // @ts-ignore 97 + expect(() => parse({ foo: 'bar' }, options)).toThrow( 98 + `Unsupported Open API specification: ${JSON.stringify({ foo: 'bar' }, null, 2)}` 99 + ); 100 + }); 101 + });
+4
src/openApi/common/interfaces/OpenApi.ts
··· 1 + import type { OpenApi as OpenApiV2 } from '../../v2/interfaces/OpenApi'; 2 + import type { OpenApi as OpenApiV3 } from '../../v3/interfaces/OpenApi'; 3 + 4 + export type OpenApi = OpenApiV2 | OpenApiV3;
+13
src/openApi/common/parser/__tests__/service.spec.ts
··· 1 + import { describe, expect, it } from 'vitest'; 2 + 3 + import { getServiceVersion } from '../service'; 4 + 5 + describe('getServiceVersion', () => { 6 + it.each([ 7 + { input: '1.0', expected: '1.0' }, 8 + { input: 'v1.2', expected: '1.2' }, 9 + { input: 'V2.4', expected: '2.4' }, 10 + ])('should get $expected when version is $input', ({ input, expected }) => { 11 + expect(getServiceVersion(input)).toEqual(expected); 12 + }); 13 + });
+34
src/openApi/common/parser/__tests__/sort.spec.ts
··· 1 + import { describe, expect, it } from 'vitest'; 2 + 3 + import { toSortedByRequired } from '../sort'; 4 + 5 + describe('sort', () => { 6 + it.each([ 7 + { 8 + input: [ 9 + { id: 'test', isRequired: false }, 10 + { id: 'test2', isRequired: true }, 11 + { id: 'test3', isRequired: true }, 12 + ], 13 + expected: [ 14 + { id: 'test2', isRequired: true }, 15 + { id: 'test3', isRequired: true }, 16 + { id: 'test', isRequired: false }, 17 + ], 18 + }, 19 + { 20 + input: [ 21 + { id: 'test', isRequired: false }, 22 + { id: 'test2', isRequired: false }, 23 + { id: 'test3', isRequired: true, default: 'something' }, 24 + ], 25 + expected: [ 26 + { id: 'test', isRequired: false }, 27 + { id: 'test2', isRequired: false }, 28 + { id: 'test3', isRequired: true, default: 'something' }, 29 + ], 30 + }, 31 + ])('should sort $input by required to produce $expected', ({ input, expected }) => { 32 + expect(toSortedByRequired(input)).toEqual(expected); 33 + }); 34 + });
+8
src/openApi/common/parser/service.ts
··· 1 + /** 2 + * Convert the service version to 'normal' version. 3 + * This basically removes any "v" prefix from the version string. 4 + * @param version 5 + */ 6 + export function getServiceVersion(version = '1.0'): string { 7 + return String(version).replace(/^v/gi, ''); 8 + }
+13
src/openApi/common/parser/sort.ts
··· 1 + /** 2 + * Sort list of values and ensure that required parameters are first so that we do not generate 3 + * invalid types. Optional parameters cannot be positioned after required ones. 4 + */ 5 + export function toSortedByRequired<T extends { isRequired: boolean; default?: string }>(values: T[]): T[] { 6 + return values.sort((a, b) => { 7 + const aNeedsValue = a.isRequired && a.default === undefined; 8 + const bNeedsValue = b.isRequired && b.default === undefined; 9 + if (aNeedsValue && !bNeedsValue) return -1; 10 + if (bNeedsValue && !aNeedsValue) return 1; 11 + return 0; 12 + }); 13 + }
+20
src/openApi/index.ts
··· 1 + import type { Client } from '../types/client'; 2 + import type { Config } from '../types/config'; 3 + import { OpenApi } from './common/interfaces/OpenApi'; 4 + import { parse as parseV2 } from './v2/index'; 5 + import { parse as parseV3 } from './v3/index'; 6 + 7 + /** 8 + * Parse the OpenAPI specification to a Client model that contains 9 + * all the models, services and schema's we should output. 10 + * @param openApi The OpenAPI spec that we have loaded from disk. 11 + * @param options {@link Config} passed to the `createClient()` method 12 + */ 13 + export function parse(openApi: OpenApi, config: Config): Client { 14 + if ('openapi' in openApi) { 15 + return parseV3(openApi, config); 16 + } else if ('swagger' in openApi) { 17 + return parseV2(openApi, config); 18 + } 19 + throw new Error(`Unsupported Open API specification: ${JSON.stringify(openApi, null, 2)}`); 20 + }
+1 -1
src/openApi/v2/index.ts
··· 1 1 import type { Client } from '../../types/client'; 2 2 import type { Config } from '../../types/config'; 3 + import { getServiceVersion } from '../common/parser/service'; 3 4 import type { OpenApi } from './interfaces/OpenApi'; 4 5 import { getModels } from './parser/getModels'; 5 6 import { getServer } from './parser/getServer'; 6 7 import { getServices } from './parser/getServices'; 7 - import { getServiceVersion } from './parser/getServiceVersion'; 8 8 9 9 /** 10 10 * Parse the OpenAPI specification to a Client model that contains
+3 -10
src/openApi/v2/parser/getOperation.ts
··· 1 - import type { Operation, OperationParameter, OperationParameters } from '../../../types/client'; 1 + import type { Operation, OperationParameters } from '../../../types/client'; 2 2 import type { Config } from '../../../types/config'; 3 3 import { getOperationName } from '../../../utils/operation'; 4 + import { toSortedByRequired } from '../../common/parser/sort'; 4 5 import type { OpenApi } from '../interfaces/OpenApi'; 5 6 import type { OpenApiOperation } from '../interfaces/OpenApiOperation'; 6 7 import { getOperationErrors } from './getOperationErrors'; ··· 9 10 import { getOperationResponses } from './getOperationResponses'; 10 11 import { getOperationResults } from './getOperationResults'; 11 12 import { getServiceName } from './getServiceName'; 12 - 13 - const sortByRequired = (a: OperationParameter, b: OperationParameter): number => { 14 - const aNeedsValue = a.isRequired && a.default === undefined; 15 - const bNeedsValue = b.isRequired && b.default === undefined; 16 - if (aNeedsValue && !bNeedsValue) return -1; 17 - if (bNeedsValue && !aNeedsValue) return 1; 18 - return 0; 19 - }; 20 13 21 14 export const getOperation = ( 22 15 openApi: OpenApi, ··· 79 72 }); 80 73 } 81 74 82 - operation.parameters = operation.parameters.sort(sortByRequired); 75 + operation.parameters = toSortedByRequired(operation.parameters); 83 76 84 77 return operation; 85 78 };
+1 -1
src/openApi/v2/parser/getOperationParameter.ts
··· 2 2 import { getEnums } from '../../../utils/getEnums'; 3 3 import { getPattern } from '../../../utils/getPattern'; 4 4 import { getType } from '../../../utils/type'; 5 + import { getRef } from '../../common/parser/getRef'; 5 6 import type { OpenApi } from '../interfaces/OpenApi'; 6 7 import type { OpenApiParameter } from '../interfaces/OpenApiParameter'; 7 8 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 8 9 import { getModel } from './getModel'; 9 10 import { getOperationParameterDefault } from './getOperationParameterDefault'; 10 11 import { getOperationParameterName } from './getOperationParameterName'; 11 - import { getRef } from './getRef'; 12 12 13 13 export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParameter): OperationParameter => { 14 14 const operationParameter: OperationParameter = {
+1 -1
src/openApi/v2/parser/getOperationParameters.ts
··· 1 1 import type { OperationParameters } from '../../../types/client'; 2 + import { getRef } from '../../common/parser/getRef'; 2 3 import type { OpenApi } from '../interfaces/OpenApi'; 3 4 import type { OpenApiParameter } from '../interfaces/OpenApiParameter'; 4 5 import { getOperationParameter } from './getOperationParameter'; 5 - import { getRef } from './getRef'; 6 6 7 7 export const getOperationParameters = (openApi: OpenApi, parameters: OpenApiParameter[]): OperationParameters => { 8 8 const operationParameters: OperationParameters = {
+1 -1
src/openApi/v2/parser/getOperationResponse.ts
··· 1 1 import type { OperationResponse } from '../../../types/client'; 2 2 import { getPattern } from '../../../utils/getPattern'; 3 3 import { getType } from '../../../utils/type'; 4 + import { getRef } from '../../common/parser/getRef'; 4 5 import type { OpenApi } from '../interfaces/OpenApi'; 5 6 import type { OpenApiResponse } from '../interfaces/OpenApiResponse'; 6 7 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 7 8 import { getModel } from './getModel'; 8 - import { getRef } from './getRef'; 9 9 10 10 export const getOperationResponse = ( 11 11 openApi: OpenApi,
+1 -1
src/openApi/v2/parser/getOperationResponses.ts
··· 1 1 import type { OperationResponse } from '../../../types/client'; 2 + import { getRef } from '../../common/parser/getRef'; 2 3 import type { OpenApi } from '../interfaces/OpenApi'; 3 4 import type { OpenApiResponse } from '../interfaces/OpenApiResponse'; 4 5 import type { OpenApiResponses } from '../interfaces/OpenApiResponses'; 5 6 import { getOperationResponse } from './getOperationResponse'; 6 7 import { getOperationResponseCode } from './getOperationResponseCode'; 7 - import { getRef } from './getRef'; 8 8 9 9 export const getOperationResponses = (openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] => { 10 10 const operationResponses: OperationResponse[] = [];
-35
src/openApi/v2/parser/getRef.spec.ts
··· 1 - import { describe, expect, it } from 'vitest'; 2 - 3 - import { getRef } from './getRef'; 4 - 5 - describe('getRef', () => { 6 - it('should produce correct result', () => { 7 - expect( 8 - getRef( 9 - { 10 - swagger: '2.0', 11 - info: { 12 - title: 'dummy', 13 - version: '1.0', 14 - }, 15 - host: 'localhost:8080', 16 - basePath: '/api', 17 - schemes: ['http', 'https'], 18 - paths: {}, 19 - definitions: { 20 - Example: { 21 - description: 'This is an Example model ', 22 - type: 'integer', 23 - }, 24 - }, 25 - }, 26 - { 27 - $ref: '#/definitions/Example', 28 - } 29 - ) 30 - ).toEqual({ 31 - description: 'This is an Example model ', 32 - type: 'integer', 33 - }); 34 - }); 35 - });
-33
src/openApi/v2/parser/getRef.ts
··· 1 - import type { OpenApi } from '../interfaces/OpenApi'; 2 - import type { OpenApiReference } from '../interfaces/OpenApiReference'; 3 - 4 - const ESCAPED_REF_SLASH = /~1/g; 5 - const ESCAPED_REF_TILDE = /~0/g; 6 - 7 - export const getRef = <T>(openApi: OpenApi, item: T & OpenApiReference): T => { 8 - if (item.$ref) { 9 - // Fetch the paths to the definitions, this converts: 10 - // "#/definitions/Form" to ["definitions", "Form"] 11 - const paths = item.$ref 12 - .replace(/^#/g, '') 13 - .split('/') 14 - .filter(item => item); 15 - 16 - // Try to find the reference by walking down the path, 17 - // if we cannot find it, then we throw an error. 18 - let result = openApi; 19 - paths.forEach(path => { 20 - const decodedPath = decodeURIComponent( 21 - path.replace(ESCAPED_REF_SLASH, '/').replace(ESCAPED_REF_TILDE, '~') 22 - ); 23 - if (result.hasOwnProperty(decodedPath)) { 24 - // @ts-ignore 25 - result = result[decodedPath]; 26 - } else { 27 - throw new Error(`Could not find reference: "${item.$ref}"`); 28 - } 29 - }); 30 - return result as T; 31 - } 32 - return item as T; 33 - };
+1 -1
src/openApi/v2/parser/getRequiredPropertiesFromComposition.ts
··· 1 1 import type { Model } from '../../../types/client'; 2 + import { getRef } from '../../common/parser/getRef'; 2 3 import type { OpenApi } from '../interfaces/OpenApi'; 3 4 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 4 5 import type { getModel } from './getModel'; 5 - import { getRef } from './getRef'; 6 6 7 7 // Fix for circular dependency 8 8 export type GetModelFn = typeof getModel;
-11
src/openApi/v2/parser/getServiceVersion.spec.ts
··· 1 - import { describe, expect, it } from 'vitest'; 2 - 3 - import { getServiceVersion } from './getServiceVersion'; 4 - 5 - describe('getServiceVersion', () => { 6 - it('should produce correct result', () => { 7 - expect(getServiceVersion('1.0')).toEqual('1.0'); 8 - expect(getServiceVersion('v1.0')).toEqual('1.0'); 9 - expect(getServiceVersion('V1.0')).toEqual('1.0'); 10 - }); 11 - });
-6
src/openApi/v2/parser/getServiceVersion.ts
··· 1 - /** 2 - * Convert the service version to 'normal' version. 3 - * This basically removes any "v" prefix from the version string. 4 - * @param version 5 - */ 6 - export const getServiceVersion = (version = '1.0'): string => String(version).replace(/^v/gi, '');
+1 -1
src/openApi/v3/index.ts
··· 1 1 import type { Client } from '../../types/client'; 2 2 import type { Config } from '../../types/config'; 3 + import { getServiceVersion } from '../common/parser/service'; 3 4 import type { OpenApi } from './interfaces/OpenApi'; 4 5 import { getModels } from './parser/getModels'; 5 6 import { getServer } from './parser/getServer'; 6 7 import { getServices } from './parser/getServices'; 7 - import { getServiceVersion } from './parser/service'; 8 8 9 9 /** 10 10 * Parse the OpenAPI specification to a Client model that contains
+1 -9
src/openApi/v3/parser/__tests__/service.spec.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import { getServiceName, getServiceVersion } from '../service'; 3 + import { getServiceName } from '../service'; 4 4 5 5 describe('getServiceName', () => { 6 6 it('should produce correct result', () => { ··· 14 14 expect(getServiceName('non-ascii-æøåÆØÅöôêÊ字符串')).toEqual('NonAsciiÆøåÆøÅöôêÊ字符串'); 15 15 }); 16 16 }); 17 - 18 - describe('getServiceVersion', () => { 19 - it('should produce correct result', () => { 20 - expect(getServiceVersion('1.0')).toEqual('1.0'); 21 - expect(getServiceVersion('v1.0')).toEqual('1.0'); 22 - expect(getServiceVersion('V1.0')).toEqual('1.0'); 23 - }); 24 - });
+1 -1
src/openApi/v3/parser/getOperationParameter.ts
··· 1 1 import type { OperationParameter } from '../../../types/client'; 2 2 import { getPattern } from '../../../utils/getPattern'; 3 3 import { getType } from '../../../utils/type'; 4 + import { getRef } from '../../common/parser/getRef'; 4 5 import type { OpenApi } from '../interfaces/OpenApi'; 5 6 import type { OpenApiParameter } from '../interfaces/OpenApiParameter'; 6 7 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 7 8 import { getModel } from './getModel'; 8 9 import { getModelDefault } from './getModelDefault'; 9 10 import { getOperationParameterName } from './getOperationParameterName'; 10 - import { getRef } from './getRef'; 11 11 12 12 export const getOperationParameter = (openApi: OpenApi, parameter: OpenApiParameter): OperationParameter => { 13 13 const operationParameter: OperationParameter = {
+1 -1
src/openApi/v3/parser/getOperationParameters.ts
··· 1 1 import type { OperationParameters } from '../../../types/client'; 2 + import { getRef } from '../../common/parser/getRef'; 2 3 import type { OpenApi } from '../interfaces/OpenApi'; 3 4 import type { OpenApiParameter } from '../interfaces/OpenApiParameter'; 4 5 import { getOperationParameter } from './getOperationParameter'; 5 - import { getRef } from './getRef'; 6 6 7 7 const allowedIn = ['cookie', 'formData', 'header', 'path', 'query'] as const; 8 8
+1 -1
src/openApi/v3/parser/getOperationResponse.ts
··· 1 1 import type { OperationResponse } from '../../../types/client'; 2 2 import { getPattern } from '../../../utils/getPattern'; 3 3 import { getType } from '../../../utils/type'; 4 + import { getRef } from '../../common/parser/getRef'; 4 5 import type { OpenApi } from '../interfaces/OpenApi'; 5 6 import type { OpenApiResponse } from '../interfaces/OpenApiResponse'; 6 7 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 7 8 import { getContent } from './getContent'; 8 9 import { getModel } from './getModel'; 9 - import { getRef } from './getRef'; 10 10 11 11 export const getOperationResponse = ( 12 12 openApi: OpenApi,
+1 -1
src/openApi/v3/parser/getOperationResponses.ts
··· 1 1 import type { OperationResponse } from '../../../types/client'; 2 + import { getRef } from '../../common/parser/getRef'; 2 3 import type { OpenApi } from '../interfaces/OpenApi'; 3 4 import type { OpenApiResponse } from '../interfaces/OpenApiResponse'; 4 5 import type { OpenApiResponses } from '../interfaces/OpenApiResponses'; 5 6 import { getOperationResponse } from './getOperationResponse'; 6 7 import { getOperationResponseCode } from './getOperationResponseCode'; 7 - import { getRef } from './getRef'; 8 8 9 9 export const getOperationResponses = (openApi: OpenApi, responses: OpenApiResponses): OperationResponse[] => { 10 10 const operationResponses: OperationResponse[] = [];
+34 -2
src/openApi/v3/parser/getRef.spec.ts src/openApi/common/parser/__tests__/getRef.spec.ts
··· 1 1 import { describe, expect, it } from 'vitest'; 2 2 3 - import { getRef } from './getRef'; 3 + import { getRef } from '../getRef'; 4 4 5 - describe('getRef', () => { 5 + describe('getRef (v2)', () => { 6 + it('should produce correct result', () => { 7 + expect( 8 + getRef( 9 + { 10 + swagger: '2.0', 11 + info: { 12 + title: 'dummy', 13 + version: '1.0', 14 + }, 15 + host: 'localhost:8080', 16 + basePath: '/api', 17 + schemes: ['http', 'https'], 18 + paths: {}, 19 + definitions: { 20 + Example: { 21 + description: 'This is an Example model ', 22 + type: 'integer', 23 + }, 24 + }, 25 + }, 26 + { 27 + $ref: '#/definitions/Example', 28 + } 29 + ) 30 + ).toEqual({ 31 + description: 'This is an Example model ', 32 + type: 'integer', 33 + }); 34 + }); 35 + }); 36 + 37 + describe('getRef (v3)', () => { 6 38 it('should produce correct result', () => { 7 39 expect( 8 40 getRef(
+5 -4
src/openApi/v3/parser/getRef.ts src/openApi/common/parser/getRef.ts
··· 1 - import type { OpenApi } from '../interfaces/OpenApi'; 2 - import type { OpenApiReference } from '../interfaces/OpenApiReference'; 1 + import type { OpenApiReference as OpenApiReferenceV2 } from '../../v2/interfaces/OpenApiReference'; 2 + import type { OpenApiReference as OpenApiReferenceV3 } from '../../v3/interfaces/OpenApiReference'; 3 + import { OpenApi } from '../interfaces/OpenApi'; 3 4 4 5 const ESCAPED_REF_SLASH = /~1/g; 5 6 const ESCAPED_REF_TILDE = /~0/g; 6 7 7 - export const getRef = <T>(openApi: OpenApi, item: T & OpenApiReference): T => { 8 + export function getRef<T>(openApi: OpenApi, item: T & (OpenApiReferenceV2 | OpenApiReferenceV3)): T { 8 9 if (item.$ref) { 9 10 // Fetch the paths to the definitions, this converts: 10 11 // "#/components/schemas/Form" to ["components", "schemas", "Form"] ··· 30 31 return result as T; 31 32 } 32 33 return item as T; 33 - }; 34 + }
+1 -1
src/openApi/v3/parser/getRequiredPropertiesFromComposition.ts
··· 1 1 import type { Model } from '../../../types/client'; 2 + import { getRef } from '../../common/parser/getRef'; 2 3 import type { OpenApi } from '../interfaces/OpenApi'; 3 4 import type { OpenApiSchema } from '../interfaces/OpenApiSchema'; 4 5 import type { getModel } from './getModel'; 5 - import { getRef } from './getRef'; 6 6 7 7 // Fix for circular dependency 8 8 export type GetModelFn = typeof getModel;
+4 -10
src/openApi/v3/parser/operation.ts
··· 1 1 import type { Operation, OperationParameter, OperationParameters } from '../../../types/client'; 2 2 import type { Config } from '../../../types/config'; 3 3 import { getOperationName } from '../../../utils/operation'; 4 + import { getRef } from '../../common/parser/getRef'; 5 + import { toSortedByRequired } from '../../common/parser/sort'; 4 6 import type { OpenApi } from '../interfaces/OpenApi'; 5 7 import type { OpenApiOperation } from '../interfaces/OpenApiOperation'; 6 8 import type { OpenApiRequestBody } from '../interfaces/OpenApiRequestBody'; ··· 10 12 import { getOperationResponseHeader } from './getOperationResponseHeader'; 11 13 import { getOperationResponses } from './getOperationResponses'; 12 14 import { getOperationResults } from './getOperationResults'; 13 - import { getRef } from './getRef'; 14 15 import { getServiceName } from './service'; 15 16 16 17 // add global path parameters, skip duplicate names ··· 109 110 operation.parametersPath = mergeParameters(operation.parametersPath, pathParams.parametersPath); 110 111 operation.parametersQuery = mergeParameters(operation.parametersQuery, pathParams.parametersQuery); 111 112 112 - // place required parameters first so we don't generate invalid types since 113 - // optional parameters cannot be positioned before required ones 114 - operation.parameters = operation.parameters.sort((a: OperationParameter, b: OperationParameter): number => { 115 - const aNeedsValue = a.isRequired && a.default === undefined; 116 - const bNeedsValue = b.isRequired && b.default === undefined; 117 - if (aNeedsValue && !bNeedsValue) return -1; 118 - if (bNeedsValue && !aNeedsValue) return 1; 119 - return 0; 120 - }); 113 + // Sort by required 114 + operation.parameters = toSortedByRequired(operation.parameters); 121 115 122 116 return operation; 123 117 };
-7
src/openApi/v3/parser/service.ts
··· 12 12 const clean = sanitizeServiceName(value).trim(); 13 13 return camelCase(clean, { pascalCase: true }); 14 14 }; 15 - 16 - /** 17 - * Convert the service version to 'normal' version. 18 - * This basically removes any "v" prefix from the version string. 19 - * @param version 20 - */ 21 - export const getServiceVersion = (version = '1.0'): string => String(version).replace(/^v/gi, '');
+2 -3
src/utils/getOpenApiSpec.ts
··· 3 3 4 4 import $RefParser from '@apidevtools/json-schema-ref-parser'; 5 5 6 - import type { OpenApi as OpenApiV2 } from '../openApi/v2/interfaces/OpenApi'; 7 - import type { OpenApi as OpenApiV3 } from '../openApi/v3/interfaces/OpenApi'; 6 + import type { OpenApi } from '../openApi/common/interfaces/OpenApi'; 8 7 9 8 /** 10 9 * Load and parse te open api spec. If the file extension is ".yml" or ".yaml" ··· 14 13 */ 15 14 export const getOpenApiSpec = async (location: string) => { 16 15 const absolutePathOrUrl = existsSync(location) ? path.resolve(location) : location; 17 - const schema = (await $RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {})) as OpenApiV2 | OpenApiV3; 16 + const schema = (await $RefParser.bundle(absolutePathOrUrl, absolutePathOrUrl, {})) as OpenApi; 18 17 return schema; 19 18 };