From 9201593f25df95effb5ff56f48e30297c81f1e09 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Mon, 12 Feb 2024 15:44:54 +0400 Subject: [PATCH 01/20] feat(oas): introduce support for examples vendor extensions in Swagger converter closes #224 --- packages/oas/src/converter/Sampler.ts | 22 ++- .../oas/src/converter/VendorExtensions.ts | 4 + packages/oas/src/index.ts | 17 +- packages/oas/tests/DefaultConverter.spec.ts | 146 +++++++++++++++--- ...body.api-connect-or-smartbear.swagger.yaml | 66 ++++++++ ...params.x-example.body.redocly.swagger.yaml | 68 ++++++++ ...s.x-example.body.schemathesis.swagger.yaml | 67 ++++++++ ...ody.swagger.include-examples-is-false.json | 48 ++++++ ...body.swagger.include-examples-is-true.json | 48 ++++++ ...ata.swagger.include-examples-is-false.json | 25 +++ ...data.swagger.include-examples-is-true.json | 25 +++ .../params.x-example.form-data.swagger.yaml | 23 +++ ...der.swagger.include-examples-is-false.json | 21 +++ ...ader.swagger.include-examples-is-true.json | 21 +++ .../params.x-example.header.swagger.yaml | 24 +++ .../fixtures/params.x-example.oas.result.json | 68 ++++++++ .../tests/fixtures/params.x-example.oas.yaml | 89 +++++++++++ ...ath.swagger.include-examples-is-false.json | 17 ++ ...path.swagger.include-examples-is-true.json | 17 ++ .../params.x-example.path.swagger.yaml | 24 +++ ...ery.swagger.include-examples-is-false.json | 22 +++ ...uery.swagger.include-examples-is-true.json | 22 +++ .../params.x-example.query.swagger.yaml | 24 +++ .../openapi-sampler/src/VendorExtensions.ts | 4 + packages/openapi-sampler/src/index.ts | 12 +- .../src/traverse/DefaultTraverse.ts | 79 +++++++++- .../openapi-sampler/src/traverse/Traverse.ts | 1 + .../openapi-sampler/tests/example.spec.ts | 63 ++++++++ 28 files changed, 1034 insertions(+), 33 deletions(-) create mode 100644 packages/oas/src/converter/VendorExtensions.ts create mode 100644 packages/oas/tests/fixtures/params.x-example.body.api-connect-or-smartbear.swagger.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.body.redocly.swagger.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.body.schemathesis.swagger.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-false.json create mode 100644 packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-true.json create mode 100644 packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-false.json create mode 100644 packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-true.json create mode 100644 packages/oas/tests/fixtures/params.x-example.form-data.swagger.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-false.json create mode 100644 packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-true.json create mode 100644 packages/oas/tests/fixtures/params.x-example.header.swagger.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.oas.result.json create mode 100644 packages/oas/tests/fixtures/params.x-example.oas.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-false.json create mode 100644 packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-true.json create mode 100644 packages/oas/tests/fixtures/params.x-example.path.swagger.yaml create mode 100644 packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-false.json create mode 100644 packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-true.json create mode 100644 packages/oas/tests/fixtures/params.x-example.query.swagger.yaml create mode 100644 packages/openapi-sampler/src/VendorExtensions.ts diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index 6faa13e7..fbcd789c 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -1,9 +1,11 @@ import { ConvertError } from '../errors'; -import { sample, Schema } from '@har-sdk/openapi-sampler'; +import { VendorExtensions } from './VendorExtensions'; +import { Options, sample, Schema } from '@har-sdk/openapi-sampler'; import pointer from 'json-pointer'; import type { OpenAPI } from '@har-sdk/core'; export class Sampler { + constructor(private readonly options: Options) {} public sampleParam( param: OpenAPI.Parameter, context: { @@ -16,6 +18,18 @@ export class Sampler { 'schema' in param ? { ...param.schema, + ...(param[VendorExtensions.X_EXAMPLE] !== undefined + ? { + [VendorExtensions.X_EXAMPLE]: + param[VendorExtensions.X_EXAMPLE] + } + : {}), + ...(param[VendorExtensions.X_EXAMPLES] !== undefined + ? { + [VendorExtensions.X_EXAMPLES]: + param[VendorExtensions.X_EXAMPLES] + } + : {}), ...(param.example !== undefined ? { example: param.example } : {}) } : param, @@ -43,7 +57,11 @@ export class Sampler { } ): any | undefined { try { - return sample(schema, { skipReadOnly: true, quiet: true }, context?.spec); + return sample( + schema, + { ...this.options, skipReadOnly: true, quiet: true }, + context?.spec + ); } catch (e) { throw new ConvertError(e.message, context?.jsonPointer); } diff --git a/packages/oas/src/converter/VendorExtensions.ts b/packages/oas/src/converter/VendorExtensions.ts new file mode 100644 index 00000000..94aa6de2 --- /dev/null +++ b/packages/oas/src/converter/VendorExtensions.ts @@ -0,0 +1,4 @@ +export enum VendorExtensions { + X_EXAMPLE = 'x-example', + X_EXAMPLES = 'x-examples' +} diff --git a/packages/oas/src/index.ts b/packages/oas/src/index.ts index 7a7a0f4d..03dc8823 100644 --- a/packages/oas/src/index.ts +++ b/packages/oas/src/index.ts @@ -6,16 +6,29 @@ import { SubConverterFactory, SubConverterRegistry } from './converter'; +import { isOASV2 } from './utils'; import type { OpenAPI, Request } from '@har-sdk/core'; +import { Options } from '@har-sdk/openapi-sampler'; export * from './errors'; +export { Options }; -export const oas2har = (collection: OpenAPI.Document): Promise => { +export const oas2har = ( + collection: OpenAPI.Document, + options: Options = {} +): Promise => { if (!collection) { throw new TypeError('Please provide a valid OAS specification.'); } - const sampler = new Sampler(); + const { includeVendorExamples } = options; + + options = { + ...options, + includeVendorExamples: isOASV2(collection) ? includeVendorExamples : false + }; + + const sampler = new Sampler(options); const baseUrlParser = new BaseUrlParser(sampler); const subConverterFactory = new SubConverterFactory(sampler); const subConverterRegistry = new SubConverterRegistry(subConverterFactory); diff --git a/packages/oas/tests/DefaultConverter.spec.ts b/packages/oas/tests/DefaultConverter.spec.ts index 7a77d8c4..c093c1b7 100644 --- a/packages/oas/tests/DefaultConverter.spec.ts +++ b/packages/oas/tests/DefaultConverter.spec.ts @@ -1,11 +1,32 @@ import { ConvertError, oas2har } from '../src'; -import yaml, { load } from 'js-yaml'; +import { load } from 'js-yaml'; import { OpenAPIV2, Request } from '@har-sdk/core'; import { resolve } from 'path'; -import { readFile, readFileSync } from 'fs'; +import { readFile } from 'fs'; import { promisify } from 'util'; describe('DefaultConverter', () => { + const readFileAsync = promisify(readFile); + + const loadFile = async (fileName: string) => { + const filePath = resolve(__dirname, fileName); + + const content = await readFileAsync(filePath, 'utf-8'); + + return content.endsWith('.json') ? JSON.parse(content) : load(content); + }; + + const createFixture = async ({ + inputFile, + expectedFile + }: { + inputFile: string; + expectedFile: string; + }) => ({ + inputDoc: await loadFile(inputFile), + expectedDoc: await loadFile(expectedFile) + }); + describe('convert', () => { [ { @@ -183,24 +204,14 @@ describe('DefaultConverter', () => { } ].forEach(({ input: inputFile, expected: expectedFile, message }) => { it(message, async () => { - const content = readFileSync( - resolve(__dirname, `./fixtures/${inputFile}`), - 'utf-8' - ); - const input = inputFile.endsWith('json') - ? JSON.parse(content) - : load(content); - - const expected = JSON.parse( - readFileSync( - resolve(__dirname, `./fixtures/${expectedFile}`), - 'utf-8' - ) - ); + const { inputDoc, expectedDoc } = await createFixture({ + inputFile: `./fixtures/${inputFile}`, + expectedFile: `./fixtures/${expectedFile}` + }); - const result: Request[] = await oas2har(input as any); + const result: Request[] = await oas2har(inputDoc as any); - expect(result).toStrictEqual(expected); + expect(result).toStrictEqual(expectedDoc); }); }); @@ -271,12 +282,9 @@ describe('DefaultConverter', () => { /^convert-error-on-(.+)\.(.+)\.yaml$/, '$1 ($2)' )}`, async () => { - const content: string = await promisify(readFile)( - resolve(__dirname, `./fixtures/${input}`), - 'utf8' - ); + const inputDoc = await loadFile(`./fixtures/${input}`); - const result = oas2har(yaml.load(content) as OpenAPIV2.Document); + const result = oas2har(inputDoc as OpenAPIV2.Document); await expect(result).rejects.toThrow(ConvertError); await expect(result).rejects.toMatchObject({ @@ -284,5 +292,97 @@ describe('DefaultConverter', () => { }); }) ); + + it('should ignore x-example when includeVendorExamples is true (oas)', async () => { + // arrange + const { inputDoc, expectedDoc } = await createFixture({ + inputFile: `./fixtures/params.x-example.oas.yaml`, + expectedFile: `./fixtures/params.x-example.oas.result.json` + }); + + // act + const result: Request[] = await oas2har(inputDoc as any, { + includeVendorExamples: true + }); + + // assert + expect(result).toStrictEqual(expectedDoc); + }); + + it.each(['path', 'query', 'header', 'form-data'])( + 'should ignore %s parameter x-example when includeVendorExamples is false (swagger)', + async (input) => { + // arrange + const { inputDoc, expectedDoc } = await createFixture({ + inputFile: `./fixtures/params.x-example.${input}.swagger.yaml`, + expectedFile: `./fixtures/params.x-example.${input}.swagger.include-examples-is-false.json` + }); + + // act + const result: Request[] = await oas2har(inputDoc as any, { + includeVendorExamples: false + }); + + // assert + expect(result).toStrictEqual(expectedDoc); + } + ); + + it.each(['path', 'query', 'header', 'form-data'])( + 'should prefer %s parameter x-example over default when includeVendorExamples is true (swagger)', + async (input) => { + // arrange + const { inputDoc, expectedDoc } = await createFixture({ + inputFile: `./fixtures/params.x-example.${input}.swagger.yaml`, + expectedFile: `./fixtures/params.x-example.${input}.swagger.include-examples-is-true.json` + }); + + // act + const result: Request[] = await oas2har(inputDoc as any, { + includeVendorExamples: true + }); + + // assert + expect(result).toStrictEqual(expectedDoc); + } + ); + + it.each(['schemathesis', 'redocly', 'api-connect-or-smartbear'])( + 'should use %s body parameter x-example when includeVendorExamples is true (swagger)', + async (input) => { + // arrange + const { inputDoc, expectedDoc } = await createFixture({ + inputFile: `./fixtures/params.x-example.body.${input}.swagger.yaml`, + expectedFile: `./fixtures/params.x-example.body.swagger.include-examples-is-true.json` + }); + + // act + const result: Request[] = await oas2har(inputDoc as any, { + includeVendorExamples: true + }); + + // assert + expect(result).toStrictEqual(expectedDoc); + } + ); + + it.each(['schemathesis', 'redocly', 'api-connect-or-smartbear'])( + 'should not use %s body parameter x-example when includeVendorExamples is false (swagger)', + async (input) => { + // arrange + const { inputDoc, expectedDoc } = await createFixture({ + inputFile: `./fixtures/params.x-example.body.${input}.swagger.yaml`, + expectedFile: `./fixtures/params.x-example.body.swagger.include-examples-is-false.json` + }); + + // act + const result: Request[] = await oas2har(inputDoc as any, { + includeVendorExamples: false + }); + + // assert + expect(result).toStrictEqual(expectedDoc); + } + ); }); }); diff --git a/packages/oas/tests/fixtures/params.x-example.body.api-connect-or-smartbear.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.body.api-connect-or-smartbear.swagger.yaml new file mode 100644 index 00000000..38160cea --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.body.api-connect-or-smartbear.swagger.yaml @@ -0,0 +1,66 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.body.redocly.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.body.redocly.swagger.yaml new file mode 100644 index 00000000..8436fa67 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.body.redocly.swagger.yaml @@ -0,0 +1,68 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + application/json: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + example-name: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.body.schemathesis.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.body.schemathesis.swagger.yaml new file mode 100644 index 00000000..fc77633f --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.body.schemathesis.swagger.yaml @@ -0,0 +1,67 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-examples: + application/json: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-false.json new file mode 100644 index 00000000..dc2a4f99 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-false.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"default_name\",\"age\":10}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"default_name\",\"age\":10}" + }, + "url": "https://example.com/sample/0" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-true.json new file mode 100644 index 00000000..8ccbaa69 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-true.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"x_example_name\",\"age\":30}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"x_example_name\",\"age\":30}" + }, + "url": "https://example.com/sample/123" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-false.json new file mode 100644 index 00000000..4bcf62a6 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-false.json @@ -0,0 +1,25 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/x-www-form-urlencoded", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/x-www-form-urlencoded", + "text": "name=default_name" + }, + "url": "https://example.com/sample" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-true.json new file mode 100644 index 00000000..34597e0d --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-true.json @@ -0,0 +1,25 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/x-www-form-urlencoded", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/x-www-form-urlencoded", + "text": "name=x_example_name" + }, + "url": "https://example.com/sample" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.form-data.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.form-data.swagger.yaml new file mode 100644 index 00000000..9680cbe1 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.form-data.swagger.yaml @@ -0,0 +1,23 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/x-www-form-urlencoded + produces: + - application/json + parameters: + - name: name + in: formData + type: string + default: default_name + x-example: x_example_name + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-false.json new file mode 100644 index 00000000..dd164a74 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-false.json @@ -0,0 +1,21 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "GET", + "headers": [ + { + "value": "application/json", + "name": "accept" + }, + { + "value": "Bearer default_jwt_token", + "name": "authorization" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "url": "https://example.com/sample" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-true.json new file mode 100644 index 00000000..3b9c0956 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-true.json @@ -0,0 +1,21 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "GET", + "headers": [ + { + "value": "application/json", + "name": "accept" + }, + { + "value": "Bearer x_example_jwt_token", + "name": "authorization" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "url": "https://example.com/sample" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.header.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.header.swagger.yaml new file mode 100644 index 00000000..41d3899b --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.header.swagger.yaml @@ -0,0 +1,24 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + get: + consumes: + - application/json + produces: + - application/json + parameters: + - name: Authorization + in: header + required: true + type: string + default: 'Bearer default_jwt_token' + x-example: 'Bearer x_example_jwt_token' + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.oas.result.json b/packages/oas/tests/fixtures/params.x-example.oas.result.json new file mode 100644 index 00000000..a1c80e23 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.oas.result.json @@ -0,0 +1,68 @@ +[ + { + "queryString": [ + { + "name": "filter", + "value": "default_filter" + } + ], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/x-www-form-urlencoded", + "name": "content-type" + }, + { + "value": "Bearer default_jwt_token", + "name": "authorization" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/x-www-form-urlencoded", + "text": "name=default_name" + }, + "url": "https://example.com/sample/0?filter=default_filter" + }, + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"default_name\",\"age\":42}" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PATCH", + "headers": [ + { + "value": "application/json", + "name": "content-type" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"example_name\",\"age\":20}" + }, + "url": "https://example.com/sample" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.oas.yaml b/packages/oas/tests/fixtures/params.x-example.oas.yaml new file mode 100644 index 00000000..7718ea6e --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.oas.yaml @@ -0,0 +1,89 @@ +openapi: 3.0.1 +info: + title: Sample API + version: 1.0.0 +servers: + - url: https://example.com/ +paths: + /sample/{id}: + put: + parameters: + - name: id + in: path + required: true + schema: + type: integer + default: 0 + x-example: 123 + - name: filter + in: query + schema: + type: string + default: default_filter + x-example: x_example_filter + - name: Authorization + in: header + required: true + schema: + type: string + default: Bearer default_jwt_token + x-example: Bearer x_example_jwt_token + requestBody: + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + name: + type: string + x-example: x_example_name + default: default_name + responses: + '201': + description: '' + content: {} + /sample: + post: + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + default: default_name + age: + type: integer + x-example: + name: x_example_name + age: 30 + required: true + responses: + '201': + description: '' + content: {} + x-codegen-request-body-name: payload + patch: + requestBody: + content: + application/json: + schema: + type: object + properties: + name: + type: string + default: default_name + age: + type: integer + example: + name: example_name + age: 20 + x-example: + name: x_example_name + age: 30 + required: true + responses: + '201': + description: '' + content: {} diff --git a/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-false.json new file mode 100644 index 00000000..04efd4e0 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-false.json @@ -0,0 +1,17 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "GET", + "headers": [ + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "url": "https://example.com/sample/0" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-true.json new file mode 100644 index 00000000..8b0e7754 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-true.json @@ -0,0 +1,17 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "GET", + "headers": [ + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "url": "https://example.com/sample/123" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.path.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.path.swagger.yaml new file mode 100644 index 00000000..df57449e --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.path.swagger.yaml @@ -0,0 +1,24 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample/{id}: + get: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-false.json new file mode 100644 index 00000000..dd23d29d --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-false.json @@ -0,0 +1,22 @@ +[ + { + "queryString": [ + { + "name": "filter", + "value": "default_filter" + } + ], + "cookies": [], + "method": "GET", + "headers": [ + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "url": "https://example.com/sample?filter=default_filter" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-true.json new file mode 100644 index 00000000..1b905fe4 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-true.json @@ -0,0 +1,22 @@ +[ + { + "queryString": [ + { + "name": "filter", + "value": "x_example_filter" + } + ], + "cookies": [], + "method": "GET", + "headers": [ + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "url": "https://example.com/sample?filter=x_example_filter" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.query.swagger.yaml b/packages/oas/tests/fixtures/params.x-example.query.swagger.yaml new file mode 100644 index 00000000..e1a460f5 --- /dev/null +++ b/packages/oas/tests/fixtures/params.x-example.query.swagger.yaml @@ -0,0 +1,24 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + get: + consumes: + - application/json + produces: + - application/json + parameters: + - name: filter + in: query + required: false + type: string + default: 'default_filter' + x-example: 'x_example_filter' + responses: + '201': + description: '' diff --git a/packages/openapi-sampler/src/VendorExtensions.ts b/packages/openapi-sampler/src/VendorExtensions.ts new file mode 100644 index 00000000..94aa6de2 --- /dev/null +++ b/packages/openapi-sampler/src/VendorExtensions.ts @@ -0,0 +1,4 @@ +export enum VendorExtensions { + X_EXAMPLE = 'x-example', + X_EXAMPLES = 'x-examples' +} diff --git a/packages/openapi-sampler/src/index.ts b/packages/openapi-sampler/src/index.ts index 4c5bba9c..a856a1af 100644 --- a/packages/openapi-sampler/src/index.ts +++ b/packages/openapi-sampler/src/index.ts @@ -16,11 +16,13 @@ export const sample = ( options?: Options, spec?: Specification ): any | undefined => { - const opts = Object.assign( - {}, - { skipReadOnly: false, maxSampleDepth: 1 }, - options - ); + const { skipReadOnly, maxSampleDepth } = options ?? {}; + + const opts = { + ...options, + maxSampleDepth: maxSampleDepth ?? 1, + skipReadOnly: skipReadOnly ?? false + }; const traverse = new DefaultTraverse(); diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 0b5ff674..dbaccbdc 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -4,6 +4,7 @@ import { getReplacementForCircular, mergeDeep } from '../utils'; +import { VendorExtensions } from '../VendorExtensions'; import { OpenAPISchema, Sampler } from '../samplers'; import JsonPointer from 'json-pointer'; import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; @@ -127,8 +128,13 @@ export class DefaultTraverse implements Traverse { let example: any; let type: string; + const vendorExample = options.includeVendorExamples + ? this.findVendorExample(schema) + : undefined; - if (this.isDefaultExists(schema)) { + if (vendorExample) { + example = vendorExample; + } else if (this.isDefaultExists(schema)) { example = schema.default; } else if ((schema as any).const !== undefined) { example = (schema as any).const; @@ -334,4 +340,75 @@ export class DefaultTraverse implements Traverse { undefined ); } + + private findVendorExample(schema: Schema) { + const example = + schema[VendorExtensions.X_EXAMPLE] ?? schema[VendorExtensions.X_EXAMPLES]; + + const schemaKeys = this.findSchemaKeys(schema); + + if ( + !example || + typeof example !== 'object' || + 0 === schemaKeys.keys.length + ) { + return example; + } + + return this.matchVendorExampleKeys(example, schemaKeys); + } + + private findSchemaKeys( + schema: Schema, + depth: number = 0 + ): { depth: number; keys: string[] } { + if ('items' in schema) { + return this.findSchemaKeys(schema.items, 1 + depth); + } + + return { + depth, + keys: 'properties' in schema ? Object.keys(schema.properties) : [] + }; + } + + private matchVendorExampleKeys( + example: unknown, + schemaKeys: { depth: number; keys: string[] }, + possibleExamples: unknown[] = [] + ): unknown { + if (!example || typeof example !== 'object') { + return undefined; + } + + if (schemaKeys.depth > 0 && Array.isArray(example)) { + return this.matchVendorExampleKeys( + [...example, undefined].shift(), + { + ...schemaKeys, + depth: schemaKeys.depth - 1 + }, + [...possibleExamples, example] + ); + } + + const objectKeys = Object.keys(example); + + if (objectKeys.every((key) => schemaKeys.keys.includes(key))) { + if (possibleExamples.length > 0) { + return possibleExamples.shift(); + } + + return example; + } + + for (const key of objectKeys) { + const value = this.matchVendorExampleKeys(example[key], schemaKeys); + if (value) { + return value; + } + } + + return undefined; + } } diff --git a/packages/openapi-sampler/src/traverse/Traverse.ts b/packages/openapi-sampler/src/traverse/Traverse.ts index e7a806c1..414ec584 100644 --- a/packages/openapi-sampler/src/traverse/Traverse.ts +++ b/packages/openapi-sampler/src/traverse/Traverse.ts @@ -4,6 +4,7 @@ export interface Options { skipReadOnly?: boolean; skipWriteOnly?: boolean; skipNonRequired?: boolean; + includeVendorExamples?: boolean; quiet?: boolean; maxSampleDepth?: number; } diff --git a/packages/openapi-sampler/tests/example.spec.ts b/packages/openapi-sampler/tests/example.spec.ts index 9ed43698..e300bda6 100644 --- a/packages/openapi-sampler/tests/example.spec.ts +++ b/packages/openapi-sampler/tests/example.spec.ts @@ -1,7 +1,9 @@ +import { VendorExtensions } from '../src/VendorExtensions'; import { sample } from '../src'; describe('Example', () => { it('should use example', () => { + // arrange const obj = { test: 'test', properties: { @@ -14,26 +16,87 @@ describe('Example', () => { type: 'object', example: obj }; + + // act const result = sample(schema); + + // assert expect(result).toEqual(obj); }); it('should use falsy example', () => { + // arrange const schema = { type: 'string', example: false }; + + // act const result = sample(schema); + + // assert expect(result).toEqual(false); }); it('should use enum', () => { + // arrange const enumList = ['test1', 'test2']; const schema = { type: 'string', enum: enumList }; + + // act const result = sample(schema); + + // assert expect(result).toEqual(enumList[0]); }); + + it.each([false, undefined])( + 'should not use vendor example when includeVendorExamples is %s', + (input) => { + // arrange + const schema = { + type: 'string', + [VendorExtensions.X_EXAMPLE]: 'foo' + }; + + // act + const result = sample(schema, { includeVendorExamples: input }); + + // assert + expect(result).toEqual('lorem'); + } + ); + + it('should prefer schema example over vendor example when includeVendorExamples is true', () => { + // arrange + const schema = { + type: 'string', + [VendorExtensions.X_EXAMPLE]: 'foo', + example: 'bar' + }; + + // act + const result = sample(schema, { includeVendorExamples: true }); + + // assert + expect(result).toEqual('bar'); + }); + + it('should prefer vendor example over default when includeVendorExamples is true', () => { + // arrange + const schema = { + type: 'string', + [VendorExtensions.X_EXAMPLE]: 'foo', + default: 'bar' + }; + + // act + const result = sample(schema, { includeVendorExamples: true }); + + // assert + expect(result).toEqual('foo'); + }); }); From 903dafd09dbf2ef35bc090607ffe18cc659d2563 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Tue, 13 Feb 2024 09:51:32 +0400 Subject: [PATCH 02/20] feat(oas): reduce complexity, increase coverage closes #224 --- packages/oas/src/converter/Sampler.ts | 58 ++-- .../src/traverse/DefaultTraverse.ts | 80 ++++-- .../openapi-sampler/tests/example.spec.ts | 249 ++++++++++++++++++ 3 files changed, 331 insertions(+), 56 deletions(-) diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index fbcd789c..f703651a 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -14,35 +14,15 @@ export class Sampler { tokens: string[]; } ): any { - return this.sample( - 'schema' in param - ? { - ...param.schema, - ...(param[VendorExtensions.X_EXAMPLE] !== undefined - ? { - [VendorExtensions.X_EXAMPLE]: - param[VendorExtensions.X_EXAMPLE] - } - : {}), - ...(param[VendorExtensions.X_EXAMPLES] !== undefined - ? { - [VendorExtensions.X_EXAMPLES]: - param[VendorExtensions.X_EXAMPLES] - } - : {}), - ...(param.example !== undefined ? { example: param.example } : {}) - } - : param, - { - spec: context.spec, - jsonPointer: pointer.compile([ - ...context.tokens, - 'parameters', - context.idx.toString(), - ...('schema' in param ? ['schema'] : []) - ]) - } - ); + return this.sample(this.createSchema(param), { + spec: context.spec, + jsonPointer: pointer.compile([ + ...context.tokens, + 'parameters', + context.idx.toString(), + ...('schema' in param ? ['schema'] : []) + ]) + }); } /** @@ -66,4 +46,24 @@ export class Sampler { throw new ConvertError(e.message, context?.jsonPointer); } } + + private createSchema(param: OpenAPI.Parameter): Schema { + return 'schema' in param + ? { + ...param.schema, + ...(param[VendorExtensions.X_EXAMPLE] !== undefined + ? { + [VendorExtensions.X_EXAMPLE]: param[VendorExtensions.X_EXAMPLE] + } + : {}), + ...(param[VendorExtensions.X_EXAMPLES] !== undefined + ? { + [VendorExtensions.X_EXAMPLES]: + param[VendorExtensions.X_EXAMPLES] + } + : {}), + ...(param.example !== undefined ? { example: param.example } : {}) + } + : param; + } } diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index dbaccbdc..73421460 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -345,70 +345,96 @@ export class DefaultTraverse implements Traverse { const example = schema[VendorExtensions.X_EXAMPLE] ?? schema[VendorExtensions.X_EXAMPLES]; - const schemaKeys = this.findSchemaKeys(schema); + const matchingSchema = this.getMatchingSchema(schema); + const isPrimitiveType = 0 === matchingSchema.keys.length; - if ( - !example || - typeof example !== 'object' || - 0 === schemaKeys.keys.length - ) { + if (!example || typeof example !== 'object' || isPrimitiveType) { return example; } - return this.matchVendorExampleKeys(example, schemaKeys); + return this.matchVendorExample(example, matchingSchema); } - private findSchemaKeys( + private getMatchingSchema( schema: Schema, depth: number = 0 ): { depth: number; keys: string[] } { if ('items' in schema) { - return this.findSchemaKeys(schema.items, 1 + depth); + return this.getMatchingSchema(schema.items, 1 + depth); } return { depth, - keys: 'properties' in schema ? Object.keys(schema.properties) : [] + keys: + 'properties' in schema && schema.properties + ? Object.keys(schema.properties) + : [] }; } - private matchVendorExampleKeys( + private matchVendorExample( example: unknown, - schemaKeys: { depth: number; keys: string[] }, - possibleExamples: unknown[] = [] + matchingSchema: { depth: number; keys: string[] }, + possibleExample: unknown[] = [] ): unknown { if (!example || typeof example !== 'object') { return undefined; } - if (schemaKeys.depth > 0 && Array.isArray(example)) { - return this.matchVendorExampleKeys( + if (matchingSchema.depth > 0 && Array.isArray(example)) { + return this.matchArrayVendorExample( + example, + matchingSchema, + possibleExample + ); + } + + return this.matchObjectVendorExample( + example, + matchingSchema, + possibleExample + ); + } + + private matchArrayVendorExample( + example: unknown, + matchingSchema: { depth: number; keys: string[] }, + possibleExample: unknown[] + ): unknown { + if (matchingSchema.depth > 0 && Array.isArray(example)) { + possibleExample.push(example); + + return this.matchArrayVendorExample( [...example, undefined].shift(), { - ...schemaKeys, - depth: schemaKeys.depth - 1 + ...matchingSchema, + depth: matchingSchema.depth - 1 }, - [...possibleExamples, example] + possibleExample ); } - const objectKeys = Object.keys(example); + return !!example && Array.isArray(example) + ? undefined + : this.matchObjectVendorExample(example, matchingSchema, possibleExample); + } - if (objectKeys.every((key) => schemaKeys.keys.includes(key))) { - if (possibleExamples.length > 0) { - return possibleExamples.shift(); - } + private matchObjectVendorExample( + example: unknown, + matchingSchema: { depth: number; keys: string[] }, + possibleExample: unknown[] + ): unknown { + const objectKeys = Object.keys(example ?? {}); - return example; + if (objectKeys.every((key) => matchingSchema.keys.includes(key))) { + return possibleExample.length > 0 ? possibleExample.shift() : example; } for (const key of objectKeys) { - const value = this.matchVendorExampleKeys(example[key], schemaKeys); + const value = this.matchVendorExample(example[key], matchingSchema); if (value) { return value; } } - - return undefined; } } diff --git a/packages/openapi-sampler/tests/example.spec.ts b/packages/openapi-sampler/tests/example.spec.ts index e300bda6..06e6f89b 100644 --- a/packages/openapi-sampler/tests/example.spec.ts +++ b/packages/openapi-sampler/tests/example.spec.ts @@ -99,4 +99,253 @@ describe('Example', () => { // assert expect(result).toEqual('foo'); }); + + it.each([ + { + [VendorExtensions.X_EXAMPLE]: { + name: 'name', + age: 30 + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'application/json': { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + some: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + some: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + } + ])( + 'should match %j object vendor example when includeVendorExamples is true', + (input) => { + // arrange + const expected = { + name: 'name', + age: 30 + }; + + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + ...input + }; + + // act + const result = sample(schema, { includeVendorExamples: true }); + + // assert + expect(result).toMatchObject(expected); + } + ); + + it.each(['some-string', { name: 'some-name', points: 100 }])( + 'should ignore %j object vendor example when includeVendorExamples is true', + (input) => { + // arrange + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + [VendorExtensions.X_EXAMPLES]: { + 'some-example': input + } + }; + + // act + const result = sample(schema, { includeVendorExamples: true }); + + // assert + expect(result).toMatchObject({ age: 42, name: 'lorem' }); + } + ); + + it.each([ + [ + { + [VendorExtensions.X_EXAMPLE]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + some: { + name: 'name', + age: 30 + } + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + } + ] + ])( + 'should match %j array vendor example when includeVendorExamples is true', + (input) => { + // arrange + const expected = [ + { + name: 'name', + age: 30 + } + ]; + + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + } + }, + ...input + }; + + // act + const result = sample(schema, { includeVendorExamples: true }); + + // assert + expect(result).toMatchObject(expected); + } + ); + + it.each([ + [[{ name: 'some-name', age: 30 }]], + 2, + 'some-string', + { name: 'some-name', points: 100 } + ])( + 'should ignore %j array vendor example when includeVendorExamples is true', + (input) => { + // arrange + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + } + }, + [VendorExtensions.X_EXAMPLES]: { + 'some-example': input + } + }; + + // act + const result = sample(schema, { includeVendorExamples: true }); + + // assert + expect(result).toMatchObject([{ age: 42, name: 'lorem' }]); + } + ); }); From c84c1821be5898b40fae794f77033774d1746ac5 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Tue, 13 Feb 2024 11:39:40 +0400 Subject: [PATCH 03/20] refactor(oas): extract `VendorExamples` closes #224 --- packages/oas/src/converter/Sampler.ts | 58 ++-- .../src/traverse/DefaultTraverse.ts | 105 +------- .../src/traverse/VendorExamples.ts | 94 +++++++ .../openapi-sampler/src/traverse/index.ts | 1 + .../tests/VendorExamples.spec.ts | 252 ++++++++++++++++++ .../openapi-sampler/tests/example.spec.ts | 249 ----------------- 6 files changed, 383 insertions(+), 376 deletions(-) create mode 100644 packages/openapi-sampler/src/traverse/VendorExamples.ts create mode 100644 packages/openapi-sampler/tests/VendorExamples.spec.ts diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index f703651a..a7dc3648 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -2,7 +2,7 @@ import { ConvertError } from '../errors'; import { VendorExtensions } from './VendorExtensions'; import { Options, sample, Schema } from '@har-sdk/openapi-sampler'; import pointer from 'json-pointer'; -import type { OpenAPI } from '@har-sdk/core'; +import type { OpenAPI, OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; export class Sampler { constructor(private readonly options: Options) {} @@ -14,15 +14,18 @@ export class Sampler { tokens: string[]; } ): any { - return this.sample(this.createSchema(param), { - spec: context.spec, - jsonPointer: pointer.compile([ - ...context.tokens, - 'parameters', - context.idx.toString(), - ...('schema' in param ? ['schema'] : []) - ]) - }); + return this.sample( + 'schema' in param ? this.createSchema(param) : (param as Schema), + { + spec: context.spec, + jsonPointer: pointer.compile([ + ...context.tokens, + 'parameters', + context.idx.toString(), + ...('schema' in param ? ['schema'] : []) + ]) + } + ); } /** @@ -47,23 +50,22 @@ export class Sampler { } } - private createSchema(param: OpenAPI.Parameter): Schema { - return 'schema' in param - ? { - ...param.schema, - ...(param[VendorExtensions.X_EXAMPLE] !== undefined - ? { - [VendorExtensions.X_EXAMPLE]: param[VendorExtensions.X_EXAMPLE] - } - : {}), - ...(param[VendorExtensions.X_EXAMPLES] !== undefined - ? { - [VendorExtensions.X_EXAMPLES]: - param[VendorExtensions.X_EXAMPLES] - } - : {}), - ...(param.example !== undefined ? { example: param.example } : {}) - } - : param; + private createSchema( + param: OpenAPIV2.InBodyParameterObject | OpenAPIV3.ParameterObject + ): Schema { + return { + ...param.schema, + ...(param[VendorExtensions.X_EXAMPLE] !== undefined + ? { + [VendorExtensions.X_EXAMPLE]: param[VendorExtensions.X_EXAMPLE] + } + : {}), + ...(param[VendorExtensions.X_EXAMPLES] !== undefined + ? { + [VendorExtensions.X_EXAMPLES]: param[VendorExtensions.X_EXAMPLES] + } + : {}), + ...(param.example !== undefined ? { example: param.example } : {}) + }; } } diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 73421460..d1a68ce7 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -1,10 +1,10 @@ import { Options, Sample, Schema, Specification, Traverse } from './Traverse'; +import { VendorExamples } from './VendorExamples'; import { firstArrayElement, getReplacementForCircular, mergeDeep } from '../utils'; -import { VendorExtensions } from '../VendorExtensions'; import { OpenAPISchema, Sampler } from '../samplers'; import JsonPointer from 'json-pointer'; import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; @@ -49,6 +49,10 @@ export class DefaultTraverse implements Traverse { this._samplers = samplers; } + constructor( + private readonly vendorExamples: VendorExamples = new VendorExamples() + ) {} + public clearCache(): void { this.refCache = {}; this.schemasStack = []; @@ -129,7 +133,7 @@ export class DefaultTraverse implements Traverse { let example: any; let type: string; const vendorExample = options.includeVendorExamples - ? this.findVendorExample(schema) + ? this.vendorExamples.find(schema) : undefined; if (vendorExample) { @@ -340,101 +344,4 @@ export class DefaultTraverse implements Traverse { undefined ); } - - private findVendorExample(schema: Schema) { - const example = - schema[VendorExtensions.X_EXAMPLE] ?? schema[VendorExtensions.X_EXAMPLES]; - - const matchingSchema = this.getMatchingSchema(schema); - const isPrimitiveType = 0 === matchingSchema.keys.length; - - if (!example || typeof example !== 'object' || isPrimitiveType) { - return example; - } - - return this.matchVendorExample(example, matchingSchema); - } - - private getMatchingSchema( - schema: Schema, - depth: number = 0 - ): { depth: number; keys: string[] } { - if ('items' in schema) { - return this.getMatchingSchema(schema.items, 1 + depth); - } - - return { - depth, - keys: - 'properties' in schema && schema.properties - ? Object.keys(schema.properties) - : [] - }; - } - - private matchVendorExample( - example: unknown, - matchingSchema: { depth: number; keys: string[] }, - possibleExample: unknown[] = [] - ): unknown { - if (!example || typeof example !== 'object') { - return undefined; - } - - if (matchingSchema.depth > 0 && Array.isArray(example)) { - return this.matchArrayVendorExample( - example, - matchingSchema, - possibleExample - ); - } - - return this.matchObjectVendorExample( - example, - matchingSchema, - possibleExample - ); - } - - private matchArrayVendorExample( - example: unknown, - matchingSchema: { depth: number; keys: string[] }, - possibleExample: unknown[] - ): unknown { - if (matchingSchema.depth > 0 && Array.isArray(example)) { - possibleExample.push(example); - - return this.matchArrayVendorExample( - [...example, undefined].shift(), - { - ...matchingSchema, - depth: matchingSchema.depth - 1 - }, - possibleExample - ); - } - - return !!example && Array.isArray(example) - ? undefined - : this.matchObjectVendorExample(example, matchingSchema, possibleExample); - } - - private matchObjectVendorExample( - example: unknown, - matchingSchema: { depth: number; keys: string[] }, - possibleExample: unknown[] - ): unknown { - const objectKeys = Object.keys(example ?? {}); - - if (objectKeys.every((key) => matchingSchema.keys.includes(key))) { - return possibleExample.length > 0 ? possibleExample.shift() : example; - } - - for (const key of objectKeys) { - const value = this.matchVendorExample(example[key], matchingSchema); - if (value) { - return value; - } - } - } } diff --git a/packages/openapi-sampler/src/traverse/VendorExamples.ts b/packages/openapi-sampler/src/traverse/VendorExamples.ts new file mode 100644 index 00000000..301bbf60 --- /dev/null +++ b/packages/openapi-sampler/src/traverse/VendorExamples.ts @@ -0,0 +1,94 @@ +import { Schema } from './Traverse'; +import { VendorExtensions } from '../VendorExtensions'; + +export class VendorExamples { + public find(schema: Schema) { + const example = + schema[VendorExtensions.X_EXAMPLE] ?? schema[VendorExtensions.X_EXAMPLES]; + + const matchingSchema = this.getMatchingSchema(schema); + const isPrimitiveType = 0 === matchingSchema.keys.length; + + if (!example || typeof example !== 'object' || isPrimitiveType) { + return example; + } + + return this.traverse(example, matchingSchema); + } + + private getMatchingSchema( + schema: Schema, + depth: number = 0 + ): { depth: number; keys: string[] } { + if ('items' in schema) { + return this.getMatchingSchema(schema.items, 1 + depth); + } + + return { + depth, + keys: + 'properties' in schema && schema.properties + ? Object.keys(schema.properties) + : [] + }; + } + + private traverse( + example: unknown, + matchingSchema: { depth: number; keys: string[] }, + possibleExample: unknown[] = [] + ): unknown { + if (!example || typeof example !== 'object') { + return undefined; + } + + if (matchingSchema.depth > 0 && Array.isArray(example)) { + return this.traverseArray(example, matchingSchema, possibleExample); + } + + return this.traverseObject(example, matchingSchema, possibleExample); + } + + private traverseArray( + example: unknown, + matchingSchema: { depth: number; keys: string[] }, + possibleExample: unknown[] + ): unknown { + if (matchingSchema.depth > 0 && Array.isArray(example)) { + possibleExample.push(example); + + return this.traverseArray( + [...example, undefined].shift(), + { + ...matchingSchema, + depth: matchingSchema.depth - 1 + }, + possibleExample + ); + } + + return !!example && Array.isArray(example) + ? undefined + : this.traverseObject(example, matchingSchema, possibleExample); + } + + // eslint-disable-next-line complexity + private traverseObject( + example: unknown, + matchingSchema: { depth: number; keys: string[] }, + possibleExample: unknown[] + ): unknown { + const objectKeys = Object.keys(example ?? {}); + + if (objectKeys.every((key) => matchingSchema.keys.includes(key))) { + return possibleExample.length > 0 ? possibleExample.shift() : example; + } + + for (const key of objectKeys) { + const value = this.traverse(example[key], matchingSchema); + if (value) { + return value; + } + } + } +} diff --git a/packages/openapi-sampler/src/traverse/index.ts b/packages/openapi-sampler/src/traverse/index.ts index 41463884..05209ec5 100644 --- a/packages/openapi-sampler/src/traverse/index.ts +++ b/packages/openapi-sampler/src/traverse/index.ts @@ -1,2 +1,3 @@ export * from './Traverse'; export * from './DefaultTraverse'; +export * from './VendorExamples'; diff --git a/packages/openapi-sampler/tests/VendorExamples.spec.ts b/packages/openapi-sampler/tests/VendorExamples.spec.ts new file mode 100644 index 00000000..c61612fb --- /dev/null +++ b/packages/openapi-sampler/tests/VendorExamples.spec.ts @@ -0,0 +1,252 @@ +import { VendorExtensions } from '../src/VendorExtensions'; +import { VendorExamples } from '../src/traverse'; + +describe('VendorExamples', () => { + describe('find', () => { + let sut!: VendorExamples; + + beforeEach(() => { + sut = new VendorExamples(); + }); + + it.each([ + { + [VendorExtensions.X_EXAMPLE]: { + name: 'name', + age: 30 + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'application/json': { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + some: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + some: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + } + ])('should match %j object vendor example', (input) => { + // arrange + const expected = { + name: 'name', + age: 30 + }; + + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + ...input + }; + + // act + const result = sut.find(schema); + + // assert + expect(result).toMatchObject(expected); + }); + + it.each(['some-string', { name: 'some-name', points: 100 }])( + 'should ignore %j object vendor example', + (input) => { + // arrange + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + [VendorExtensions.X_EXAMPLES]: { + 'some-example': input + } + }; + + // act + const result = sut.find(schema); + + // assert + expect(result).toBe(undefined); + } + ); + + it.each([ + [ + { + [VendorExtensions.X_EXAMPLE]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + some: { + name: 'name', + age: 30 + } + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': [ + { + name: 'name', + age: 30 + } + ] + } + } + ] + ])('should match %j array vendor example', (input) => { + // arrange + const expected = [ + { + name: 'name', + age: 30 + } + ]; + + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + } + }, + ...input + }; + + // act + const result = sut.find(schema); + + // assert + expect(result).toMatchObject(expected); + }); + + it.each([ + [[{ name: 'some-name', age: 30 }]], + 2, + 'some-string', + { name: 'some-name', points: 100 } + ])('should ignore %j array vendor example', (input) => { + // arrange + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + } + }, + [VendorExtensions.X_EXAMPLES]: { + 'some-example': input + } + }; + + // act + const result = sut.find(schema); + + // assert + expect(result).toBe(undefined); + }); + }); +}); diff --git a/packages/openapi-sampler/tests/example.spec.ts b/packages/openapi-sampler/tests/example.spec.ts index 06e6f89b..e300bda6 100644 --- a/packages/openapi-sampler/tests/example.spec.ts +++ b/packages/openapi-sampler/tests/example.spec.ts @@ -99,253 +99,4 @@ describe('Example', () => { // assert expect(result).toEqual('foo'); }); - - it.each([ - { - [VendorExtensions.X_EXAMPLE]: { - name: 'name', - age: 30 - } - }, - { - [VendorExtensions.X_EXAMPLE]: { - 'application/json': { - name: 'name', - age: 30 - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': { - name: 'name', - age: 30 - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': { - some: { - name: 'name', - age: 30 - } - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': { - some: { - name: 'name', - age: 30 - } - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: [ - { - name: 'name', - age: 30 - } - ] - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - } - ])( - 'should match %j object vendor example when includeVendorExamples is true', - (input) => { - // arrange - const expected = { - name: 'name', - age: 30 - }; - - const schema = { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - }, - ...input - }; - - // act - const result = sample(schema, { includeVendorExamples: true }); - - // assert - expect(result).toMatchObject(expected); - } - ); - - it.each(['some-string', { name: 'some-name', points: 100 }])( - 'should ignore %j object vendor example when includeVendorExamples is true', - (input) => { - // arrange - const schema = { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - }, - [VendorExtensions.X_EXAMPLES]: { - 'some-example': input - } - }; - - // act - const result = sample(schema, { includeVendorExamples: true }); - - // assert - expect(result).toMatchObject({ age: 42, name: 'lorem' }); - } - ); - - it.each([ - [ - { - [VendorExtensions.X_EXAMPLE]: [ - { - name: 'name', - age: 30 - } - ] - }, - { - [VendorExtensions.X_EXAMPLE]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - some: { - name: 'name', - age: 30 - } - } - ] - } - }, - { - [VendorExtensions.X_EXAMPLES]: [ - { - name: 'name', - age: 30 - } - ] - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - } - ] - ])( - 'should match %j array vendor example when includeVendorExamples is true', - (input) => { - // arrange - const expected = [ - { - name: 'name', - age: 30 - } - ]; - - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - } - }, - ...input - }; - - // act - const result = sample(schema, { includeVendorExamples: true }); - - // assert - expect(result).toMatchObject(expected); - } - ); - - it.each([ - [[{ name: 'some-name', age: 30 }]], - 2, - 'some-string', - { name: 'some-name', points: 100 } - ])( - 'should ignore %j array vendor example when includeVendorExamples is true', - (input) => { - // arrange - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - } - }, - [VendorExtensions.X_EXAMPLES]: { - 'some-example': input - } - }; - - // act - const result = sample(schema, { includeVendorExamples: true }); - - // assert - expect(result).toMatchObject([{ age: 42, name: 'lorem' }]); - } - ); }); From 200e54af6172671fd27059aa20661110d816f0f5 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Tue, 13 Feb 2024 11:52:23 +0400 Subject: [PATCH 04/20] refactor(oas): reduce complexity closes #224 --- .../openapi-sampler/src/traverse/VendorExamples.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/openapi-sampler/src/traverse/VendorExamples.ts b/packages/openapi-sampler/src/traverse/VendorExamples.ts index 301bbf60..294d3c0a 100644 --- a/packages/openapi-sampler/src/traverse/VendorExamples.ts +++ b/packages/openapi-sampler/src/traverse/VendorExamples.ts @@ -72,7 +72,6 @@ export class VendorExamples { : this.traverseObject(example, matchingSchema, possibleExample); } - // eslint-disable-next-line complexity private traverseObject( example: unknown, matchingSchema: { depth: number; keys: string[] }, @@ -84,11 +83,9 @@ export class VendorExamples { return possibleExample.length > 0 ? possibleExample.shift() : example; } - for (const key of objectKeys) { - const value = this.traverse(example[key], matchingSchema); - if (value) { - return value; - } - } + return objectKeys + .map((key) => this.traverse(example[key], matchingSchema)) + .filter((obj) => !!obj) + .shift(); } } From 29014218ee67bdf78ff70ca4ad6d38d9443149c8 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Tue, 13 Feb 2024 12:06:36 +0400 Subject: [PATCH 05/20] refactor(oas): remove redundant condition closes #224 --- packages/openapi-sampler/src/traverse/VendorExamples.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/openapi-sampler/src/traverse/VendorExamples.ts b/packages/openapi-sampler/src/traverse/VendorExamples.ts index 294d3c0a..8aa4760d 100644 --- a/packages/openapi-sampler/src/traverse/VendorExamples.ts +++ b/packages/openapi-sampler/src/traverse/VendorExamples.ts @@ -9,7 +9,7 @@ export class VendorExamples { const matchingSchema = this.getMatchingSchema(schema); const isPrimitiveType = 0 === matchingSchema.keys.length; - if (!example || typeof example !== 'object' || isPrimitiveType) { + if (isPrimitiveType) { return example; } From 255ec3d8a1e673db86d3a391deaa2cff2149c881 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Thu, 15 Feb 2024 19:06:51 +0400 Subject: [PATCH 06/20] refactor(oas): adjust according to code review closes #224 --- packages/oas/README.md | 12 + packages/oas/src/converter/Sampler.ts | 80 ++-- packages/oas/src/index.ts | 12 +- packages/openapi-sampler/README.md | 13 + .../openapi-sampler/src/VendorExtensions.ts | 4 - packages/openapi-sampler/src/index.ts | 2 +- .../src/traverse/DefaultTraverse.ts | 28 +- .../src/traverse/VendorExampleExtractor.ts | 106 +++++ .../src/traverse/VendorExamples.ts | 91 ---- .../src/traverse}/VendorExtensions.ts | 0 .../openapi-sampler/src/traverse/index.ts | 2 +- .../tests/VendorExampleExtractor.spec.ts | 422 ++++++++++++++++++ .../tests/VendorExamples.spec.ts | 252 ----------- .../openapi-sampler/tests/example.spec.ts | 2 +- 14 files changed, 620 insertions(+), 406 deletions(-) delete mode 100644 packages/openapi-sampler/src/VendorExtensions.ts create mode 100644 packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts delete mode 100644 packages/openapi-sampler/src/traverse/VendorExamples.ts rename packages/{oas/src/converter => openapi-sampler/src/traverse}/VendorExtensions.ts (100%) create mode 100644 packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts delete mode 100644 packages/openapi-sampler/tests/VendorExamples.spec.ts diff --git a/packages/oas/README.md b/packages/oas/README.md index fd2dc45f..fc7dda1d 100644 --- a/packages/oas/README.md +++ b/packages/oas/README.md @@ -57,6 +57,18 @@ const requests = await oas2har(response.data); console.log(requests); ``` +Some specifications may incorporate example values for parameters provided in vendor extension fields, to include such examples in output use the `oas2har` function as follows: + +```js +import schema from './swagger.json' assert { type: 'json' }; +import { oas2har } from '@har-sdk/oas'; + +const requests = await oas2har(schema, { includeVendorExamples: true }); +console.log(requests); +``` + +Notice the `includeVendorExamples` option affects Swagger specifications only. + ## License Copyright © 2023 [Bright Security](https://brightsec.com/). diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index a7dc3648..82672715 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -1,11 +1,12 @@ import { ConvertError } from '../errors'; -import { VendorExtensions } from './VendorExtensions'; -import { Options, sample, Schema } from '@har-sdk/openapi-sampler'; +import { isOASV2 } from '../utils'; +import { Options, sample, Schema, VendorExtensions } from '@har-sdk/openapi-sampler'; import pointer from 'json-pointer'; -import type { OpenAPI, OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; +import type { OpenAPI } from '@har-sdk/core'; export class Sampler { constructor(private readonly options: Options) {} + public sampleParam( param: OpenAPI.Parameter, context: { @@ -14,18 +15,15 @@ export class Sampler { tokens: string[]; } ): any { - return this.sample( - 'schema' in param ? this.createSchema(param) : (param as Schema), - { - spec: context.spec, - jsonPointer: pointer.compile([ - ...context.tokens, - 'parameters', - context.idx.toString(), - ...('schema' in param ? ['schema'] : []) - ]) - } - ); + return this.sample(this.createParamSchema(param), { + spec: context.spec, + jsonPointer: pointer.compile([ + ...context.tokens, + 'parameters', + context.idx.toString(), + ...('schema' in param ? ['schema'] : []) + ]) + }); } /** @@ -40,32 +38,40 @@ export class Sampler { } ): any | undefined { try { - return sample( - schema, - { ...this.options, skipReadOnly: true, quiet: true }, - context?.spec - ); + const { includeVendorExamples } = this.options; + const options = { + ...this.options, + skipReadOnly: true, + quiet: true, + includeVendorExamples: + context?.spec && isOASV2(context?.spec) + ? includeVendorExamples + : false + }; + + return sample(schema, options, context?.spec); } catch (e) { throw new ConvertError(e.message, context?.jsonPointer); } } - private createSchema( - param: OpenAPIV2.InBodyParameterObject | OpenAPIV3.ParameterObject - ): Schema { - return { - ...param.schema, - ...(param[VendorExtensions.X_EXAMPLE] !== undefined - ? { - [VendorExtensions.X_EXAMPLE]: param[VendorExtensions.X_EXAMPLE] - } - : {}), - ...(param[VendorExtensions.X_EXAMPLES] !== undefined - ? { - [VendorExtensions.X_EXAMPLES]: param[VendorExtensions.X_EXAMPLES] - } - : {}), - ...(param.example !== undefined ? { example: param.example } : {}) - }; + private createParamSchema(param: OpenAPI.Parameter): Schema { + return 'schema' in param + ? { + ...param.schema, + ...(param[VendorExtensions.X_EXAMPLE] !== undefined + ? { + [VendorExtensions.X_EXAMPLE]: param[VendorExtensions.X_EXAMPLE] + } + : {}), + ...(param[VendorExtensions.X_EXAMPLES] !== undefined + ? { + [VendorExtensions.X_EXAMPLES]: + param[VendorExtensions.X_EXAMPLES] + } + : {}), + ...(param.example !== undefined ? { example: param.example } : {}) + } + : (param as Schema); } } diff --git a/packages/oas/src/index.ts b/packages/oas/src/index.ts index 03dc8823..5907bedc 100644 --- a/packages/oas/src/index.ts +++ b/packages/oas/src/index.ts @@ -6,28 +6,18 @@ import { SubConverterFactory, SubConverterRegistry } from './converter'; -import { isOASV2 } from './utils'; import type { OpenAPI, Request } from '@har-sdk/core'; -import { Options } from '@har-sdk/openapi-sampler'; export * from './errors'; -export { Options }; export const oas2har = ( collection: OpenAPI.Document, - options: Options = {} + options: { includeVendorExamples?: boolean } = {} ): Promise => { if (!collection) { throw new TypeError('Please provide a valid OAS specification.'); } - const { includeVendorExamples } = options; - - options = { - ...options, - includeVendorExamples: isOASV2(collection) ? includeVendorExamples : false - }; - const sampler = new Sampler(options); const baseUrlParser = new BaseUrlParser(sampler); const subConverterFactory = new SubConverterFactory(sampler); diff --git a/packages/openapi-sampler/README.md b/packages/openapi-sampler/README.md index a56c3fb6..dfbb94e1 100644 --- a/packages/openapi-sampler/README.md +++ b/packages/openapi-sampler/README.md @@ -138,6 +138,19 @@ Also, the library logs console warning messages when it encounters an unsupporte sample(schema, { quiet: true }); ``` +When the schema comes from the specification which does not allow the `example` node to exist e.g. OAS 2.0 parameter definition, some vendors may provide such schema example value in OAS vendor extension nodes namely `x-example` or `x-examples`. If you want to include such kind of example values into the output, you can use the `includeVendorExamples` as shown below: + +```js +sample( + { + 'type': 'string', + 'x-example': 'some_value' + }, + { includeVendorExamples: true } +); +// some_value +``` + ## License Copyright © 2023 [Bright Security](https://brightsec.com/). diff --git a/packages/openapi-sampler/src/VendorExtensions.ts b/packages/openapi-sampler/src/VendorExtensions.ts deleted file mode 100644 index 94aa6de2..00000000 --- a/packages/openapi-sampler/src/VendorExtensions.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum VendorExtensions { - X_EXAMPLE = 'x-example', - X_EXAMPLES = 'x-examples' -} diff --git a/packages/openapi-sampler/src/index.ts b/packages/openapi-sampler/src/index.ts index a856a1af..e42042b6 100644 --- a/packages/openapi-sampler/src/index.ts +++ b/packages/openapi-sampler/src/index.ts @@ -40,4 +40,4 @@ export const sample = ( return traverse.traverse(schema, opts, spec)?.value; }; -export { Schema, Options, Specification } from './traverse'; +export { Schema, Options, Specification, VendorExtensions } from './traverse'; diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index d1a68ce7..9c9c356d 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -1,5 +1,5 @@ import { Options, Sample, Schema, Specification, Traverse } from './Traverse'; -import { VendorExamples } from './VendorExamples'; +import { VendorExampleExtractor } from './VendorExampleExtractor'; import { firstArrayElement, getReplacementForCircular, @@ -50,7 +50,7 @@ export class DefaultTraverse implements Traverse { } constructor( - private readonly vendorExamples: VendorExamples = new VendorExamples() + private readonly vendorExampleExtractor: VendorExampleExtractor = new VendorExampleExtractor() ) {} public clearCache(): void { @@ -91,6 +91,23 @@ export class DefaultTraverse implements Traverse { }; } + const vendorExample = options.includeVendorExamples + ? this.vendorExampleExtractor.extract(schema) + : undefined; + + if (vendorExample) { + this.popSchemaStack(); + + const { readOnly, writeOnly } = schema as OpenAPIV2.SchemaObject; + + return { + readOnly, + writeOnly, + type: schema.type, + value: vendorExample + }; + } + if (schema.allOf) { this.popSchemaStack(); @@ -132,13 +149,8 @@ export class DefaultTraverse implements Traverse { let example: any; let type: string; - const vendorExample = options.includeVendorExamples - ? this.vendorExamples.find(schema) - : undefined; - if (vendorExample) { - example = vendorExample; - } else if (this.isDefaultExists(schema)) { + if (this.isDefaultExists(schema)) { example = schema.default; } else if ((schema as any).const !== undefined) { example = (schema as any).const; diff --git a/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts b/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts new file mode 100644 index 00000000..d2cff238 --- /dev/null +++ b/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts @@ -0,0 +1,106 @@ +import { Schema } from './Traverse'; +import { VendorExtensions } from './VendorExtensions'; + +interface ExampleShape { + arrayDepth: number; + objectKeys: string[]; +} + +export class VendorExampleExtractor { + public extract(schema: Schema) { + return [ + schema[VendorExtensions.X_EXAMPLE], + schema[VendorExtensions.X_EXAMPLES] + ] + .map((example) => this.findExampleByShape(example, schema)) + .filter((example) => !!example) + .shift(); + } + + private findExampleByShape(example: unknown, schema: Schema) { + const exampleShape = this.getExampleShape(schema); + const isPrimitiveType = + 0 === exampleShape.objectKeys.length && exampleShape.arrayDepth === 0; + + if (isPrimitiveType) { + return example; + } + + return this.traverse(example, exampleShape); + } + + private getExampleShape(schema: Schema, depth: number = 0): ExampleShape { + if ('items' in schema) { + return this.getExampleShape(schema.items, 1 + depth); + } + + return { + arrayDepth: depth, + objectKeys: + 'properties' in schema && schema.properties + ? Object.keys(schema.properties) + : [] + }; + } + + private traverse( + example: unknown, + exampleShape: ExampleShape, + possibleExample: unknown[] = [] + ): unknown { + if (!example || typeof example !== 'object') { + return undefined; + } + + if (exampleShape.arrayDepth > 0 && Array.isArray(example)) { + return this.traverseArray(example, exampleShape, possibleExample); + } + + return Array.isArray(example) + ? undefined + : this.traverseObject(example, exampleShape, possibleExample); + } + + private traverseArray( + example: unknown, + exampleShape: ExampleShape, + possibleExample: unknown[] + ): unknown { + if (exampleShape.arrayDepth > 0 && Array.isArray(example)) { + possibleExample.push(example); + + return this.traverseArray( + [...example, undefined].shift(), + { + ...exampleShape, + arrayDepth: exampleShape.arrayDepth - 1 + }, + possibleExample + ); + } + + return !!example && (Array.isArray(example) || exampleShape.arrayDepth > 0) + ? undefined + : this.traverseObject(example, exampleShape, possibleExample); + } + + private traverseObject( + example: unknown, + exampleShape: ExampleShape, + possibleExample: unknown[] + ): unknown { + const objectKeys = Object.keys(example ?? {}); + + if ( + exampleShape.arrayDepth === 0 && + objectKeys.every((key) => exampleShape.objectKeys.includes(key)) + ) { + return possibleExample.length > 0 ? possibleExample.shift() : example; + } + + return objectKeys + .map((key) => this.traverse(example[key], exampleShape)) + .filter((obj) => !!obj) + .shift(); + } +} diff --git a/packages/openapi-sampler/src/traverse/VendorExamples.ts b/packages/openapi-sampler/src/traverse/VendorExamples.ts deleted file mode 100644 index 8aa4760d..00000000 --- a/packages/openapi-sampler/src/traverse/VendorExamples.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { Schema } from './Traverse'; -import { VendorExtensions } from '../VendorExtensions'; - -export class VendorExamples { - public find(schema: Schema) { - const example = - schema[VendorExtensions.X_EXAMPLE] ?? schema[VendorExtensions.X_EXAMPLES]; - - const matchingSchema = this.getMatchingSchema(schema); - const isPrimitiveType = 0 === matchingSchema.keys.length; - - if (isPrimitiveType) { - return example; - } - - return this.traverse(example, matchingSchema); - } - - private getMatchingSchema( - schema: Schema, - depth: number = 0 - ): { depth: number; keys: string[] } { - if ('items' in schema) { - return this.getMatchingSchema(schema.items, 1 + depth); - } - - return { - depth, - keys: - 'properties' in schema && schema.properties - ? Object.keys(schema.properties) - : [] - }; - } - - private traverse( - example: unknown, - matchingSchema: { depth: number; keys: string[] }, - possibleExample: unknown[] = [] - ): unknown { - if (!example || typeof example !== 'object') { - return undefined; - } - - if (matchingSchema.depth > 0 && Array.isArray(example)) { - return this.traverseArray(example, matchingSchema, possibleExample); - } - - return this.traverseObject(example, matchingSchema, possibleExample); - } - - private traverseArray( - example: unknown, - matchingSchema: { depth: number; keys: string[] }, - possibleExample: unknown[] - ): unknown { - if (matchingSchema.depth > 0 && Array.isArray(example)) { - possibleExample.push(example); - - return this.traverseArray( - [...example, undefined].shift(), - { - ...matchingSchema, - depth: matchingSchema.depth - 1 - }, - possibleExample - ); - } - - return !!example && Array.isArray(example) - ? undefined - : this.traverseObject(example, matchingSchema, possibleExample); - } - - private traverseObject( - example: unknown, - matchingSchema: { depth: number; keys: string[] }, - possibleExample: unknown[] - ): unknown { - const objectKeys = Object.keys(example ?? {}); - - if (objectKeys.every((key) => matchingSchema.keys.includes(key))) { - return possibleExample.length > 0 ? possibleExample.shift() : example; - } - - return objectKeys - .map((key) => this.traverse(example[key], matchingSchema)) - .filter((obj) => !!obj) - .shift(); - } -} diff --git a/packages/oas/src/converter/VendorExtensions.ts b/packages/openapi-sampler/src/traverse/VendorExtensions.ts similarity index 100% rename from packages/oas/src/converter/VendorExtensions.ts rename to packages/openapi-sampler/src/traverse/VendorExtensions.ts diff --git a/packages/openapi-sampler/src/traverse/index.ts b/packages/openapi-sampler/src/traverse/index.ts index 05209ec5..ad87627f 100644 --- a/packages/openapi-sampler/src/traverse/index.ts +++ b/packages/openapi-sampler/src/traverse/index.ts @@ -1,3 +1,3 @@ export * from './Traverse'; export * from './DefaultTraverse'; -export * from './VendorExamples'; +export * from './VendorExtensions'; diff --git a/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts b/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts new file mode 100644 index 00000000..3cec1156 --- /dev/null +++ b/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts @@ -0,0 +1,422 @@ +import { VendorExtensions } from '../src/traverse/VendorExtensions'; +import { VendorExampleExtractor } from '../src/traverse/VendorExampleExtractor'; + +describe('VendorExampleExtractor', () => { + describe('find', () => { + let sut!: VendorExampleExtractor; + + beforeEach(() => { + sut = new VendorExampleExtractor(); + }); + + it.each([ + { + [VendorExtensions.X_EXAMPLE]: { + name: 'name', + age: 30 + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + key1: { + name: 'nameOfKey1', + age: 30, + points: 100 + }, + key2: { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'text/plain': { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'application/json': { + key1: { + name: 'nameOfKey1', + age: 30, + points: 100 + }, + key2: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + key1: { + name: 'nameOfKey1', + age: 30, + points: 100 + }, + key2: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'example-name': { + name: 'name', + age: 30 + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'example-name': { + key1: { + name: 'nameOfKey1', + age: 30, + points: 100 + }, + key2: { + name: 'name', + age: 30 + } + } + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'example-name': [ + { + name: 'nameOfExampleName', + age: 30 + } + ] + }, + [VendorExtensions.X_EXAMPLES]: { + 'example-name': { + key1: { + name: 'nameOfKey1', + age: 30, + points: 100 + }, + key2: { + name: 'name', + age: 30 + } + } + } + } + ])('should match %j object vendor example', (input) => { + // arrange + const expected = { + name: 'name', + age: 30 + }; + + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + ...input + }; + + // act + const result = sut.extract(schema); + + // assert + expect(result).toEqual(expected); + }); + + it('debug', () => { + const input = { + [VendorExtensions.X_EXAMPLE]: { + 'example-name': [ + { + name: 'nameOfExampleName', + age: 30 + } + ] + }, + [VendorExtensions.X_EXAMPLES]: { + 'example-name': { + key1: { + name: 'nameOfKey1', + age: 30, + points: 100 + }, + key2: { + name: 'name', + age: 30 + } + } + } + }; + // arrange + const expected = { + name: 'name', + age: 30 + }; + + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + ...input + }; + + // act + const result = sut.extract(schema); + + // assert + expect(result).toEqual(expected); + }); + + it.each(['some-string', { name: 'some-name', points: 100 }])( + 'should ignore %j when example shape does not satisfy the object schema', + (input) => { + // arrange + const schema = { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + }, + [VendorExtensions.X_EXAMPLES]: { + 'some-example': input + } + }; + + // act + const result = sut.extract(schema); + + // assert + expect(result).toBe(undefined); + } + ); + + it.each([ + { + [VendorExtensions.X_EXAMPLE]: [ + { + name: 'name', + age: 30 + } + ] + }, + { + [VendorExtensions.X_EXAMPLE]: { + key1: [ + { + name: 'nameOfKey1', + age: 30, + points: 100 + } + ], + key2: [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'text/plain': [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'application/json': { + key1: [ + { + name: 'nameOfKey1', + age: 30, + points: 100 + } + ], + key2: [ + { + name: 'name', + age: 30 + } + ] + } + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'application/json': { + key1: [ + { + name: 'nameOfKey1', + age: 30, + points: 100 + } + ], + key2: [ + { + name: 'name', + age: 30 + } + ] + } + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'example-name': [ + { + name: 'name', + age: 30 + } + ] + } + }, + { + [VendorExtensions.X_EXAMPLES]: { + 'example-name': { + key1: [ + { + name: 'nameOfKey1', + age: 30, + points: 100 + } + ], + key2: [ + { + name: 'name', + age: 30 + } + ] + } + } + }, + { + [VendorExtensions.X_EXAMPLE]: { + 'example-name': [ + { + name: 'nameOfExampleName', + age: 30, + points: 100 + } + ] + }, + [VendorExtensions.X_EXAMPLES]: { + 'example-name': { + key0: { + name: 'nameOfKey0', + age: 30 + }, + key1: [ + { + name: 'nameOfKey1', + age: 30, + points: 100 + } + ], + key2: [ + { + name: 'name', + age: 30 + } + ] + } + } + } + ])('should match %j array vendor example', (input) => { + // arrange + const expected = [ + { + name: 'name', + age: 30 + } + ]; + + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + } + }, + ...input + }; + + // act + const result = sut.extract(schema); + + // assert + expect(result).toEqual(expected); + }); + + it.each([ + [[{ name: 'some-name', age: 30 }]], + 2, + 'some-string', + { name: 'some-name', points: 100 } + ])( + 'should ignore %j when example shape does not satisfy the array schema', + (input) => { + // arrange + const schema = { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string' + }, + age: { + type: 'integer' + } + } + }, + [VendorExtensions.X_EXAMPLES]: { + 'some-example': input + } + }; + + // act + const result = sut.extract(schema); + + // assert + expect(result).toBe(undefined); + } + ); + }); +}); diff --git a/packages/openapi-sampler/tests/VendorExamples.spec.ts b/packages/openapi-sampler/tests/VendorExamples.spec.ts deleted file mode 100644 index c61612fb..00000000 --- a/packages/openapi-sampler/tests/VendorExamples.spec.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { VendorExtensions } from '../src/VendorExtensions'; -import { VendorExamples } from '../src/traverse'; - -describe('VendorExamples', () => { - describe('find', () => { - let sut!: VendorExamples; - - beforeEach(() => { - sut = new VendorExamples(); - }); - - it.each([ - { - [VendorExtensions.X_EXAMPLE]: { - name: 'name', - age: 30 - } - }, - { - [VendorExtensions.X_EXAMPLE]: { - 'application/json': { - name: 'name', - age: 30 - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': { - name: 'name', - age: 30 - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': { - some: { - name: 'name', - age: 30 - } - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': { - some: { - name: 'name', - age: 30 - } - } - } - }, - { - [VendorExtensions.X_EXAMPLES]: [ - { - name: 'name', - age: 30 - } - ] - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - } - ])('should match %j object vendor example', (input) => { - // arrange - const expected = { - name: 'name', - age: 30 - }; - - const schema = { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - }, - ...input - }; - - // act - const result = sut.find(schema); - - // assert - expect(result).toMatchObject(expected); - }); - - it.each(['some-string', { name: 'some-name', points: 100 }])( - 'should ignore %j object vendor example', - (input) => { - // arrange - const schema = { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - }, - [VendorExtensions.X_EXAMPLES]: { - 'some-example': input - } - }; - - // act - const result = sut.find(schema); - - // assert - expect(result).toBe(undefined); - } - ); - - it.each([ - [ - { - [VendorExtensions.X_EXAMPLE]: [ - { - name: 'name', - age: 30 - } - ] - }, - { - [VendorExtensions.X_EXAMPLE]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - some: { - name: 'name', - age: 30 - } - } - ] - } - }, - { - [VendorExtensions.X_EXAMPLES]: [ - { - name: 'name', - age: 30 - } - ] - }, - { - [VendorExtensions.X_EXAMPLES]: { - 'application/json': [ - { - name: 'name', - age: 30 - } - ] - } - } - ] - ])('should match %j array vendor example', (input) => { - // arrange - const expected = [ - { - name: 'name', - age: 30 - } - ]; - - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - } - }, - ...input - }; - - // act - const result = sut.find(schema); - - // assert - expect(result).toMatchObject(expected); - }); - - it.each([ - [[{ name: 'some-name', age: 30 }]], - 2, - 'some-string', - { name: 'some-name', points: 100 } - ])('should ignore %j array vendor example', (input) => { - // arrange - const schema = { - type: 'array', - items: { - type: 'object', - properties: { - name: { - type: 'string' - }, - age: { - type: 'integer' - } - } - }, - [VendorExtensions.X_EXAMPLES]: { - 'some-example': input - } - }; - - // act - const result = sut.find(schema); - - // assert - expect(result).toBe(undefined); - }); - }); -}); diff --git a/packages/openapi-sampler/tests/example.spec.ts b/packages/openapi-sampler/tests/example.spec.ts index e300bda6..c2118724 100644 --- a/packages/openapi-sampler/tests/example.spec.ts +++ b/packages/openapi-sampler/tests/example.spec.ts @@ -1,4 +1,4 @@ -import { VendorExtensions } from '../src/VendorExtensions'; +import { VendorExtensions } from '../src/traverse/VendorExtensions'; import { sample } from '../src'; describe('Example', () => { From 15183c3cf845978ffa9601dfc8854ae2d585c3b6 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Thu, 15 Feb 2024 19:11:07 +0400 Subject: [PATCH 07/20] refactor(oas): fix formatting closes #224 --- packages/oas/src/converter/Sampler.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index 82672715..e0327534 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -1,6 +1,11 @@ import { ConvertError } from '../errors'; import { isOASV2 } from '../utils'; -import { Options, sample, Schema, VendorExtensions } from '@har-sdk/openapi-sampler'; +import { + Options, + sample, + Schema, + VendorExtensions +} from '@har-sdk/openapi-sampler'; import pointer from 'json-pointer'; import type { OpenAPI } from '@har-sdk/core'; From 905258c52ab488c0dedd4686571df471eb70d524 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 09:31:10 +0400 Subject: [PATCH 08/20] refactor(oas): adjust according to code review closes #224 --- packages/oas/tests/DefaultConverter.spec.ts | 36 +++++----- ....disabled.api-connect.swagger.result.json} | 0 ...le.body.disabled.api-connect.swagger.yaml} | 0 ....body.disabled.redocly.swagger.result.json | 48 +++++++++++++ ...xample.body.disabled.redocly.swagger.yaml} | 0 ....disabled.schemathesis.swagger.result.json | 48 +++++++++++++ ...e.body.disabled.schemathesis.swagger.yaml} | 0 ...ody.disabled.smartbear.swagger.result.json | 48 +++++++++++++ ...ample.body.disabled.smartbear.swagger.yaml | 66 ++++++++++++++++++ ...ody.disabled.smatrbear.swagger.result.json | 48 +++++++++++++ ...y.enabled.api-connect.swagger.result.json} | 0 ...mple.body.enabled.api-connect.swagger.yaml | 66 ++++++++++++++++++ ...e.body.enabled.redocly.swagger.result.json | 48 +++++++++++++ ...-example.body.enabled.redocly.swagger.yaml | 68 +++++++++++++++++++ ...y.enabled.schemathesis.swagger.result.json | 48 +++++++++++++ ...ple.body.enabled.schemathesis.swagger.yaml | 67 ++++++++++++++++++ ...body.enabled.smartbear.swagger.result.json | 48 +++++++++++++ ...xample.body.enabled.smartbear.swagger.yaml | 66 ++++++++++++++++++ ...le.form-data.disabled.swagger.result.json} | 0 ...x-example.form-data.disabled.swagger.yaml} | 0 ...ple.form-data.enabled.swagger.result.json} | 0 .../x-example.form-data.enabled.swagger.yaml | 23 +++++++ ...ample.header.disabled.swagger.result.json} | 0 ...=> x-example.header.disabled.swagger.yaml} | 0 ...xample.header.enabled.swagger.result.json} | 0 .../x-example.header.enabled.swagger.yaml | 24 +++++++ ....result.json => x-example.oas.result.json} | 0 ....x-example.oas.yaml => x-example.oas.yaml} | 0 ...example.path.disabled.swagger.result.json} | 0 ...l => x-example.path.disabled.swagger.yaml} | 0 ...-example.path.enabled.swagger.result.json} | 0 .../x-example.path.enabled.swagger.yaml | 24 +++++++ ...xample.query.disabled.swagger.result.json} | 0 ... => x-example.query.disabled.swagger.yaml} | 0 ...example.query.enabled.swagger.result.json} | 0 .../x-example.query.enabled.swagger.yaml | 24 +++++++ 36 files changed, 782 insertions(+), 18 deletions(-) rename packages/oas/tests/fixtures/{params.x-example.body.swagger.include-examples-is-false.json => x-example.body.disabled.api-connect.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{params.x-example.body.api-connect-or-smartbear.swagger.yaml => x-example.body.disabled.api-connect.swagger.yaml} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json rename packages/oas/tests/fixtures/{params.x-example.body.redocly.swagger.yaml => x-example.body.disabled.redocly.swagger.yaml} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json rename packages/oas/tests/fixtures/{params.x-example.body.schemathesis.swagger.yaml => x-example.body.disabled.schemathesis.swagger.yaml} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json create mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.yaml create mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json rename packages/oas/tests/fixtures/{params.x-example.body.swagger.include-examples-is-true.json => x-example.body.enabled.api-connect.swagger.result.json} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.result.json create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.result.json create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.result.json create mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml rename packages/oas/tests/fixtures/{params.x-example.form-data.swagger.include-examples-is-false.json => x-example.form-data.disabled.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{params.x-example.form-data.swagger.yaml => x-example.form-data.disabled.swagger.yaml} (100%) rename packages/oas/tests/fixtures/{params.x-example.form-data.swagger.include-examples-is-true.json => x-example.form-data.enabled.swagger.result.json} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml rename packages/oas/tests/fixtures/{params.x-example.header.swagger.include-examples-is-false.json => x-example.header.disabled.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{params.x-example.header.swagger.yaml => x-example.header.disabled.swagger.yaml} (100%) rename packages/oas/tests/fixtures/{params.x-example.header.swagger.include-examples-is-true.json => x-example.header.enabled.swagger.result.json} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml rename packages/oas/tests/fixtures/{params.x-example.oas.result.json => x-example.oas.result.json} (100%) rename packages/oas/tests/fixtures/{params.x-example.oas.yaml => x-example.oas.yaml} (100%) rename packages/oas/tests/fixtures/{params.x-example.path.swagger.include-examples-is-false.json => x-example.path.disabled.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{params.x-example.path.swagger.yaml => x-example.path.disabled.swagger.yaml} (100%) rename packages/oas/tests/fixtures/{params.x-example.path.swagger.include-examples-is-true.json => x-example.path.enabled.swagger.result.json} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml rename packages/oas/tests/fixtures/{params.x-example.query.swagger.include-examples-is-false.json => x-example.query.disabled.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{params.x-example.query.swagger.yaml => x-example.query.disabled.swagger.yaml} (100%) rename packages/oas/tests/fixtures/{params.x-example.query.swagger.include-examples-is-true.json => x-example.query.enabled.swagger.result.json} (100%) create mode 100644 packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml diff --git a/packages/oas/tests/DefaultConverter.spec.ts b/packages/oas/tests/DefaultConverter.spec.ts index c093c1b7..1535e07f 100644 --- a/packages/oas/tests/DefaultConverter.spec.ts +++ b/packages/oas/tests/DefaultConverter.spec.ts @@ -296,8 +296,8 @@ describe('DefaultConverter', () => { it('should ignore x-example when includeVendorExamples is true (oas)', async () => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/params.x-example.oas.yaml`, - expectedFile: `./fixtures/params.x-example.oas.result.json` + inputFile: `./fixtures/x-example.oas.yaml`, + expectedFile: `./fixtures/x-example.oas.result.json` }); // act @@ -310,12 +310,12 @@ describe('DefaultConverter', () => { }); it.each(['path', 'query', 'header', 'form-data'])( - 'should ignore %s parameter x-example when includeVendorExamples is false (swagger)', + 'should ignore %s parameter vendor example when vendor examples inclusion disabled (swagger)', async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/params.x-example.${input}.swagger.yaml`, - expectedFile: `./fixtures/params.x-example.${input}.swagger.include-examples-is-false.json` + inputFile: `./fixtures/x-example.${input}.disabled.swagger.yaml`, + expectedFile: `./fixtures/x-example.${input}.disabled.swagger.result.json` }); // act @@ -329,12 +329,12 @@ describe('DefaultConverter', () => { ); it.each(['path', 'query', 'header', 'form-data'])( - 'should prefer %s parameter x-example over default when includeVendorExamples is true (swagger)', + 'should use %s parameter vendor example when vendor examples inclusion enabled (swagger)', async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/params.x-example.${input}.swagger.yaml`, - expectedFile: `./fixtures/params.x-example.${input}.swagger.include-examples-is-true.json` + inputFile: `./fixtures/x-example.${input}.enabled.swagger.yaml`, + expectedFile: `./fixtures/x-example.${input}.enabled.swagger.result.json` }); // act @@ -347,18 +347,18 @@ describe('DefaultConverter', () => { } ); - it.each(['schemathesis', 'redocly', 'api-connect-or-smartbear'])( - 'should use %s body parameter x-example when includeVendorExamples is true (swagger)', + it.each(['schemathesis', 'redocly', 'api-connect', 'smartbear'])( + 'should ignore body parameter vendor example when vendor examples inclusion disabled (swagger, %s)', async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/params.x-example.body.${input}.swagger.yaml`, - expectedFile: `./fixtures/params.x-example.body.swagger.include-examples-is-true.json` + inputFile: `./fixtures/x-example.body.disabled.${input}.swagger.yaml`, + expectedFile: `./fixtures/x-example.body.disabled.${input}.swagger.result.json` }); // act const result: Request[] = await oas2har(inputDoc as any, { - includeVendorExamples: true + includeVendorExamples: false }); // assert @@ -366,18 +366,18 @@ describe('DefaultConverter', () => { } ); - it.each(['schemathesis', 'redocly', 'api-connect-or-smartbear'])( - 'should not use %s body parameter x-example when includeVendorExamples is false (swagger)', + it.each(['schemathesis', 'redocly', 'api-connect', 'smartbear'])( + 'should use body parameter vendor example when vendor examples inclusion enabled (swagger, %s)', async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/params.x-example.body.${input}.swagger.yaml`, - expectedFile: `./fixtures/params.x-example.body.swagger.include-examples-is-false.json` + inputFile: `./fixtures/x-example.body.enabled.${input}.swagger.yaml`, + expectedFile: `./fixtures/x-example.body.enabled.${input}.swagger.result.json` }); // act const result: Request[] = await oas2har(inputDoc as any, { - includeVendorExamples: false + includeVendorExamples: true }); // assert diff --git a/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-false.json rename to packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.result.json diff --git a/packages/oas/tests/fixtures/params.x-example.body.api-connect-or-smartbear.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.body.api-connect-or-smartbear.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json new file mode 100644 index 00000000..dc2a4f99 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"default_name\",\"age\":10}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"default_name\",\"age\":10}" + }, + "url": "https://example.com/sample/0" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.body.redocly.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.body.redocly.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json new file mode 100644 index 00000000..dc2a4f99 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"default_name\",\"age\":10}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"default_name\",\"age\":10}" + }, + "url": "https://example.com/sample/0" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.body.schemathesis.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.body.schemathesis.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json new file mode 100644 index 00000000..dc2a4f99 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"default_name\",\"age\":10}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"default_name\",\"age\":10}" + }, + "url": "https://example.com/sample/0" + } +] diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.yaml new file mode 100644 index 00000000..38160cea --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.yaml @@ -0,0 +1,66 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json new file mode 100644 index 00000000..dc2a4f99 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"default_name\",\"age\":10}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"default_name\",\"age\":10}" + }, + "url": "https://example.com/sample/0" + } +] diff --git a/packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.body.swagger.include-examples-is-true.json rename to packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml new file mode 100644 index 00000000..38160cea --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml @@ -0,0 +1,66 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.result.json new file mode 100644 index 00000000..8ccbaa69 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"x_example_name\",\"age\":30}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"x_example_name\",\"age\":30}" + }, + "url": "https://example.com/sample/123" + } +] diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml new file mode 100644 index 00000000..8436fa67 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml @@ -0,0 +1,68 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + application/json: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + example-name: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.result.json new file mode 100644 index 00000000..8ccbaa69 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"x_example_name\",\"age\":30}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"x_example_name\",\"age\":30}" + }, + "url": "https://example.com/sample/123" + } +] diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml new file mode 100644 index 00000000..fc77633f --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml @@ -0,0 +1,67 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-examples: + application/json: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.result.json new file mode 100644 index 00000000..8ccbaa69 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.result.json @@ -0,0 +1,48 @@ +[ + { + "queryString": [], + "cookies": [], + "method": "POST", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "[{\"name\":\"x_example_name\",\"age\":30}]" + }, + "url": "https://example.com/sample" + }, + { + "queryString": [], + "cookies": [], + "method": "PUT", + "headers": [ + { + "value": "application/json", + "name": "content-type" + }, + { + "value": "application/json", + "name": "accept" + } + ], + "httpVersion": "HTTP/1.1", + "headersSize": 0, + "bodySize": 0, + "postData": { + "mimeType": "application/json", + "text": "{\"name\":\"x_example_name\",\"age\":30}" + }, + "url": "https://example.com/sample/123" + } +] diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml new file mode 100644 index 00000000..38160cea --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml @@ -0,0 +1,66 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/json + produces: + - application/json + parameters: + - name: payload + in: body + required: true + schema: + type: array + items: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + - name: x_example_name + age: 30 + responses: + '201': + description: '' + /sample/{id}: + put: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + - name: payload + in: body + required: true + schema: + type: object + properties: + name: + type: string + default: 'default_name' + age: + type: integer + default: 10 + x-example: + name: x_example_name + age: 30 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/x-example.form-data.disabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-false.json rename to packages/oas/tests/fixtures/x-example.form-data.disabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/params.x-example.form-data.swagger.yaml b/packages/oas/tests/fixtures/x-example.form-data.disabled.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.form-data.swagger.yaml rename to packages/oas/tests/fixtures/x-example.form-data.disabled.swagger.yaml diff --git a/packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.form-data.swagger.include-examples-is-true.json rename to packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml new file mode 100644 index 00000000..9680cbe1 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml @@ -0,0 +1,23 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + post: + consumes: + - application/x-www-form-urlencoded + produces: + - application/json + parameters: + - name: name + in: formData + type: string + default: default_name + x-example: x_example_name + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/x-example.header.disabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-false.json rename to packages/oas/tests/fixtures/x-example.header.disabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/params.x-example.header.swagger.yaml b/packages/oas/tests/fixtures/x-example.header.disabled.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.header.swagger.yaml rename to packages/oas/tests/fixtures/x-example.header.disabled.swagger.yaml diff --git a/packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/x-example.header.enabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.header.swagger.include-examples-is-true.json rename to packages/oas/tests/fixtures/x-example.header.enabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml new file mode 100644 index 00000000..41d3899b --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml @@ -0,0 +1,24 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + get: + consumes: + - application/json + produces: + - application/json + parameters: + - name: Authorization + in: header + required: true + type: string + default: 'Bearer default_jwt_token' + x-example: 'Bearer x_example_jwt_token' + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.oas.result.json b/packages/oas/tests/fixtures/x-example.oas.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.oas.result.json rename to packages/oas/tests/fixtures/x-example.oas.result.json diff --git a/packages/oas/tests/fixtures/params.x-example.oas.yaml b/packages/oas/tests/fixtures/x-example.oas.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.oas.yaml rename to packages/oas/tests/fixtures/x-example.oas.yaml diff --git a/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/x-example.path.disabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-false.json rename to packages/oas/tests/fixtures/x-example.path.disabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/params.x-example.path.swagger.yaml b/packages/oas/tests/fixtures/x-example.path.disabled.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.path.swagger.yaml rename to packages/oas/tests/fixtures/x-example.path.disabled.swagger.yaml diff --git a/packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/x-example.path.enabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.path.swagger.include-examples-is-true.json rename to packages/oas/tests/fixtures/x-example.path.enabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml new file mode 100644 index 00000000..df57449e --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml @@ -0,0 +1,24 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample/{id}: + get: + consumes: + - application/json + produces: + - application/json + parameters: + - name: id + in: path + required: true + type: integer + default: 0 + x-example: 123 + responses: + '201': + description: '' diff --git a/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-false.json b/packages/oas/tests/fixtures/x-example.query.disabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-false.json rename to packages/oas/tests/fixtures/x-example.query.disabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/params.x-example.query.swagger.yaml b/packages/oas/tests/fixtures/x-example.query.disabled.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.query.swagger.yaml rename to packages/oas/tests/fixtures/x-example.query.disabled.swagger.yaml diff --git a/packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-true.json b/packages/oas/tests/fixtures/x-example.query.enabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/params.x-example.query.swagger.include-examples-is-true.json rename to packages/oas/tests/fixtures/x-example.query.enabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml new file mode 100644 index 00000000..e1a460f5 --- /dev/null +++ b/packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml @@ -0,0 +1,24 @@ +swagger: '2.0' +info: + title: Sample API + version: 1.0.0 +host: example.com +schemes: + - https +paths: + /sample: + get: + consumes: + - application/json + produces: + - application/json + parameters: + - name: filter + in: query + required: false + type: string + default: 'default_filter' + x-example: 'x_example_filter' + responses: + '201': + description: '' From 8a1b527480cd88321be483b9f60ba3ff11dfad7e Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 10:00:07 +0400 Subject: [PATCH 09/20] refactor(oas): address complexity issues closes #224 --- .../src/traverse/DefaultTraverse.ts | 85 ++++++++++--------- 1 file changed, 47 insertions(+), 38 deletions(-) diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 9c9c356d..9b531b7e 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -7,7 +7,7 @@ import { } from '../utils'; import { OpenAPISchema, Sampler } from '../samplers'; import JsonPointer from 'json-pointer'; -import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; +import { IJsonSchema, OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; const schemaKeywordTypes = { multipleOf: 'number', @@ -80,49 +80,20 @@ export class DefaultTraverse implements Traverse { return this.inferRef(spec, schema, options); } - if (this.isExampleExists(schema)) { - this.popSchemaStack(); - - return { - value: schema.example, - readOnly: schema.readOnly, - writeOnly: schema.writeOnly, - type: schema.type - }; - } + let example = this.getExample(schema, options); - const vendorExample = options.includeVendorExamples - ? this.vendorExampleExtractor.extract(schema) - : undefined; - - if (vendorExample) { + if (example) { this.popSchemaStack(); - const { readOnly, writeOnly } = schema as OpenAPIV2.SchemaObject; - - return { - readOnly, - writeOnly, - type: schema.type, - value: vendorExample - }; + return example; } if (schema.allOf) { this.popSchemaStack(); return this.allOfSample( - { ...schema, allOf: undefined } as - | OpenAPIV3.ReferenceObject - | OpenAPIV2.ReferenceObject - | OpenAPIV3.SchemaObject - | OpenAPIV2.SchemaObject, - schema.allOf as ( - | OpenAPIV3.ReferenceObject - | OpenAPIV2.ReferenceObject - | OpenAPIV3.SchemaObject - | OpenAPIV2.SchemaObject - )[], + { ...schema, allOf: undefined } as Exclude, + schema.allOf as Exclude[], options, spec ); @@ -138,16 +109,23 @@ export class DefaultTraverse implements Traverse { this.popSchemaStack(); - return this.traverse(firstArrayElement(schema.oneOf), options, spec); + return this.traverse( + firstArrayElement(schema.oneOf as Exclude), + options, + spec + ); } if (schema.anyOf && schema.anyOf.length) { this.popSchemaStack(); - return this.traverse(firstArrayElement(schema.anyOf), options, spec); + return this.traverse( + firstArrayElement(schema.anyOf as Exclude), + options, + spec + ); } - let example: any; let type: string; if (this.isDefaultExists(schema)) { @@ -356,4 +334,35 @@ export class DefaultTraverse implements Traverse { undefined ); } + + private getExample( + schema: Schema, + { includeVendorExamples }: Options + ): Sample | undefined { + if (this.isExampleExists(schema)) { + const { type, readOnly, writeOnly, example } = schema; + + return { + type, + readOnly, + writeOnly, + value: example + }; + } + + const vendorExample = includeVendorExamples + ? this.vendorExampleExtractor.extract(schema) + : undefined; + + if (vendorExample) { + const { type, readOnly, writeOnly } = schema as OpenAPIV2.SchemaObject; + + return { + type, + readOnly, + writeOnly, + value: vendorExample + }; + } + } } From 4fead7bfc65d3713aee44c0ee40619a2012e5774 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 12:46:12 +0400 Subject: [PATCH 10/20] refactor(oas): address complexity issues closes #224 --- packages/oas/src/converter/Sampler.ts | 34 ++--- .../src/traverse/DefaultTraverse.ts | 132 +++++++++--------- 2 files changed, 86 insertions(+), 80 deletions(-) diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index e0327534..65c47d0d 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -61,22 +61,22 @@ export class Sampler { } private createParamSchema(param: OpenAPI.Parameter): Schema { - return 'schema' in param - ? { - ...param.schema, - ...(param[VendorExtensions.X_EXAMPLE] !== undefined - ? { - [VendorExtensions.X_EXAMPLE]: param[VendorExtensions.X_EXAMPLE] - } - : {}), - ...(param[VendorExtensions.X_EXAMPLES] !== undefined - ? { - [VendorExtensions.X_EXAMPLES]: - param[VendorExtensions.X_EXAMPLES] - } - : {}), - ...(param.example !== undefined ? { example: param.example } : {}) - } - : (param as Schema); + if ('schema' in param) { + const { schema, example, ...rest } = param; + + return { + ...schema, + ...(example !== undefined ? { example } : {}), + ...[VendorExtensions.X_EXAMPLE, VendorExtensions.X_EXAMPLES].reduce( + (acc, prop) => ({ + ...acc, + ...(rest[prop] !== undefined ? { [prop]: rest[prop] } : {}) + }), + {} + ) + }; + } + + return param as Schema; } } diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 9b531b7e..cb579e9c 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -80,14 +80,57 @@ export class DefaultTraverse implements Traverse { return this.inferRef(spec, schema, options); } - let example = this.getExample(schema, options); + return ( + this.findSchemaExample(schema, options, spec) ?? + this.tryTraverseSubSchema(schema, options, spec) ?? + this.createSchemaExample(schema, options, spec) + ); + } - if (example) { - this.popSchemaStack(); + private createSchemaExample( + schema: Schema, + options?: Options, + spec?: Specification + ): Sample | undefined { + let example: Sample | undefined; + let type: string; + + if (this.isDefaultExists(schema)) { + example = schema.default; + } else if ((schema as any).const !== undefined) { + example = (schema as any).const; + } else if ((schema as any).enum && (schema as any).enum.length) { + example = firstArrayElement((schema as any).enum); + } else { + type = (schema as any).type as string; - return example; + if (!type) { + type = this.inferType(schema as OpenAPISchema); + } + + const sampler = this.samplers.get(type || 'null'); + + if (sampler) { + example = sampler.sample(schema as OpenAPISchema, spec, options); + } } + this.popSchemaStack(); + + const { readOnly, writeOnly } = schema as OpenAPISchema; + + return { + type, + readOnly, + writeOnly, + value: example + }; + } + private tryTraverseSubSchema( + schema: IJsonSchema, + options?: Options, + spec?: Specification + ): Sample | undefined { if (schema.allOf) { this.popSchemaStack(); @@ -125,43 +168,37 @@ export class DefaultTraverse implements Traverse { spec ); } + } - let type: string; + private findSchemaExample( + schema: Schema, + { includeVendorExamples }: Options, + _: Specification + ): Sample | undefined { + let example; - if (this.isDefaultExists(schema)) { - example = schema.default; - } else if ((schema as any).const !== undefined) { - example = (schema as any).const; - } else if (schema.enum && schema.enum.length) { - example = firstArrayElement(schema.enum); + if (this.isExampleExists(schema)) { + example = schema.example; } else if ((schema as any).examples && (schema as any).examples.length) { example = firstArrayElement((schema as any).examples); } else { - type = schema.type as string; + example = includeVendorExamples + ? this.vendorExampleExtractor.extract(schema) + : undefined; + } - if (!type) { - type = this.inferType( - schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject - ); - } + if (example !== undefined) { + const { type, readOnly, writeOnly } = schema as OpenAPISchema; - const sampler = this.samplers.get(type || 'null'); + this.popSchemaStack(); - if (sampler) { - example = sampler.sample(schema as OpenAPISchema, spec, options); - } + return { + type, + readOnly, + writeOnly, + value: example + }; } - - this.popSchemaStack(); - - return { - type, - value: example, - readOnly: (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject) - .readOnly, - writeOnly: (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject) - .writeOnly - }; } private pushSchemaStack(schema: Schema) { @@ -334,35 +371,4 @@ export class DefaultTraverse implements Traverse { undefined ); } - - private getExample( - schema: Schema, - { includeVendorExamples }: Options - ): Sample | undefined { - if (this.isExampleExists(schema)) { - const { type, readOnly, writeOnly, example } = schema; - - return { - type, - readOnly, - writeOnly, - value: example - }; - } - - const vendorExample = includeVendorExamples - ? this.vendorExampleExtractor.extract(schema) - : undefined; - - if (vendorExample) { - const { type, readOnly, writeOnly } = schema as OpenAPIV2.SchemaObject; - - return { - type, - readOnly, - writeOnly, - value: vendorExample - }; - } - } } From 84344aa0f4ccd4a8ac6eb897b287a2f630d08b60 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 13:15:04 +0400 Subject: [PATCH 11/20] refactor(oas): address complexity issues closes #224 --- packages/oas/src/converter/Sampler.ts | 18 +++++++++++------- .../src/traverse/DefaultTraverse.ts | 9 ++++++++- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/oas/src/converter/Sampler.ts b/packages/oas/src/converter/Sampler.ts index 65c47d0d..f50e251d 100644 --- a/packages/oas/src/converter/Sampler.ts +++ b/packages/oas/src/converter/Sampler.ts @@ -67,16 +67,20 @@ export class Sampler { return { ...schema, ...(example !== undefined ? { example } : {}), - ...[VendorExtensions.X_EXAMPLE, VendorExtensions.X_EXAMPLES].reduce( - (acc, prop) => ({ - ...acc, - ...(rest[prop] !== undefined ? { [prop]: rest[prop] } : {}) - }), - {} - ) + ...this.extractVendorExamples(rest) }; } return param as Schema; } + + private extractVendorExamples(param: OpenAPI.Parameter) { + return [VendorExtensions.X_EXAMPLE, VendorExtensions.X_EXAMPLES].reduce( + (acc, prop) => ({ + ...acc, + ...(param[prop] !== undefined ? { [prop]: param[prop] } : {}) + }), + {} + ); + } } diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index cb579e9c..59c677b4 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -179,7 +179,7 @@ export class DefaultTraverse implements Traverse { if (this.isExampleExists(schema)) { example = schema.example; - } else if ((schema as any).examples && (schema as any).examples.length) { + } else if (this.isExamplesExists(schema)) { example = firstArrayElement((schema as any).examples); } else { example = includeVendorExamples @@ -363,6 +363,13 @@ export class DefaultTraverse implements Traverse { ); } + private isExamplesExists(schema: Schema): schema is { examples: unknown[] } { + return ( + (schema as any).examples !== undefined && + (schema as any).examples.length > 0 + ); + } + private isDefaultExists( schema: Schema ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { From 928930e292ee3748f59b6d9d2a04a683a6d23ece Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 14:43:14 +0400 Subject: [PATCH 12/20] refactor(oas): address complexity issues closes #224 --- .../src/traverse/DefaultTraverse.ts | 133 ++++++++++++------ 1 file changed, 92 insertions(+), 41 deletions(-) diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 59c677b4..7c6a7fe6 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -81,7 +81,7 @@ export class DefaultTraverse implements Traverse { } return ( - this.findSchemaExample(schema, options, spec) ?? + this.findSchemaExample(schema, options) ?? this.tryTraverseSubSchema(schema, options, spec) ?? this.createSchemaExample(schema, options, spec) ); @@ -92,28 +92,14 @@ export class DefaultTraverse implements Traverse { options?: Options, spec?: Specification ): Sample | undefined { - let example: Sample | undefined; - let type: string; + const type = (schema as any).type as string; - if (this.isDefaultExists(schema)) { - example = schema.default; - } else if ((schema as any).const !== undefined) { - example = (schema as any).const; - } else if ((schema as any).enum && (schema as any).enum.length) { - example = firstArrayElement((schema as any).enum); - } else { - type = (schema as any).type as string; + let value = this.extractSampleValueFromSchema(schema); - if (!type) { - type = this.inferType(schema as OpenAPISchema); - } - - const sampler = this.samplers.get(type || 'null'); - - if (sampler) { - example = sampler.sample(schema as OpenAPISchema, spec, options); - } - } + value = + value === undefined + ? this.createSampleValueFromInferredType(schema, options, spec) + : value; this.popSchemaStack(); @@ -123,13 +109,59 @@ export class DefaultTraverse implements Traverse { type, readOnly, writeOnly, - value: example + value }; } + + private extractSampleValueFromSchema(schema: Schema): unknown { + let value; + if (this.isDefaultExists(schema)) { + value = schema.default; + } else if ((schema as any).const !== undefined) { + value = (schema as any).const; + } else if ((schema as any).enum && (schema as any).enum.length) { + value = firstArrayElement((schema as any).enum); + } + + return value; + } + + private createSampleValueFromInferredType( + schema: Schema, + options?: Options, + spec?: Specification + ): unknown { + let type = (schema as any).type as string; + + if (!type) { + type = this.inferType(schema as OpenAPISchema); + } + + const sampler = this.samplers.get(type || 'null'); + + let value; + if (sampler) { + value = sampler.sample(schema as OpenAPISchema, spec, options); + } + + return value; + } + private tryTraverseSubSchema( schema: IJsonSchema, options?: Options, spec?: Specification + ): Sample | undefined { + return ( + this.tryTraverseAllOf(schema, options, spec) ?? + this.tryTraverseOneOf(schema, options, spec) ?? + this.tryTraverseAnyOf(schema, options, spec) + ); + } + private tryTraverseAllOf( + schema: IJsonSchema, + options?: Options, + spec?: Specification ): Sample | undefined { if (schema.allOf) { this.popSchemaStack(); @@ -141,7 +173,13 @@ export class DefaultTraverse implements Traverse { spec ); } + } + private tryTraverseOneOf( + schema: IJsonSchema, + options?: Options, + spec?: Specification + ): Sample | undefined { if (schema.oneOf && schema.oneOf.length) { if (schema.anyOf && !options.quiet) { // eslint-disable-next-line no-console @@ -158,7 +196,13 @@ export class DefaultTraverse implements Traverse { spec ); } + } + private tryTraverseAnyOf( + schema: IJsonSchema, + options?: Options, + spec?: Specification + ): Sample | undefined { if (schema.anyOf && schema.anyOf.length) { this.popSchemaStack(); @@ -172,20 +216,14 @@ export class DefaultTraverse implements Traverse { private findSchemaExample( schema: Schema, - { includeVendorExamples }: Options, - _: Specification + options: Options ): Sample | undefined { - let example; + let example = this.extractSampleValueFromExamples(schema); - if (this.isExampleExists(schema)) { - example = schema.example; - } else if (this.isExamplesExists(schema)) { - example = firstArrayElement((schema as any).examples); - } else { - example = includeVendorExamples - ? this.vendorExampleExtractor.extract(schema) - : undefined; - } + example = + example === undefined + ? this.extractSampleValueFromVendorExamples(schema, options) + : example; if (example !== undefined) { const { type, readOnly, writeOnly } = schema as OpenAPISchema; @@ -201,6 +239,26 @@ export class DefaultTraverse implements Traverse { } } + private extractSampleValueFromExamples(schema: Schema): unknown { + if (this.isExampleExists(schema)) { + return schema.example; + } else if ( + (schema as any).examples !== undefined && + (schema as any).examples.length > 0 + ) { + return firstArrayElement((schema as any).examples); + } + } + + private extractSampleValueFromVendorExamples( + schema: Schema, + { includeVendorExamples }: Options + ): unknown { + return includeVendorExamples + ? this.vendorExampleExtractor.extract(schema) + : undefined; + } + private pushSchemaStack(schema: Schema) { this.schemasStack.push(schema); } @@ -363,13 +421,6 @@ export class DefaultTraverse implements Traverse { ); } - private isExamplesExists(schema: Schema): schema is { examples: unknown[] } { - return ( - (schema as any).examples !== undefined && - (schema as any).examples.length > 0 - ); - } - private isDefaultExists( schema: Schema ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { From f3a45275f6d8f523d67aaf20455550b3ec8a28de Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 15:08:39 +0400 Subject: [PATCH 13/20] refactor(oas): address complexity issues closes #224 --- .../src/traverse/DefaultTraverse.ts | 71 +++---------------- .../src/traverse/SchemaExampleExtractor.ts | 71 +++++++++++++++++++ 2 files changed, 80 insertions(+), 62 deletions(-) create mode 100644 packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 7c6a7fe6..24829374 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -1,5 +1,5 @@ import { Options, Sample, Schema, Specification, Traverse } from './Traverse'; -import { VendorExampleExtractor } from './VendorExampleExtractor'; +import { SchemaExampleExtractor } from './SchemaExampleExtractor'; import { firstArrayElement, getReplacementForCircular, @@ -50,7 +50,7 @@ export class DefaultTraverse implements Traverse { } constructor( - private readonly vendorExampleExtractor: VendorExampleExtractor = new VendorExampleExtractor() + private readonly sampleValueExtractor: SchemaExampleExtractor = new SchemaExampleExtractor() ) {} public clearCache(): void { @@ -94,7 +94,7 @@ export class DefaultTraverse implements Traverse { ): Sample | undefined { const type = (schema as any).type as string; - let value = this.extractSampleValueFromSchema(schema); + let value = this.sampleValueExtractor.extractFromProperties(schema); value = value === undefined @@ -113,19 +113,6 @@ export class DefaultTraverse implements Traverse { }; } - private extractSampleValueFromSchema(schema: Schema): unknown { - let value; - if (this.isDefaultExists(schema)) { - value = schema.default; - } else if ((schema as any).const !== undefined) { - value = (schema as any).const; - } else if ((schema as any).enum && (schema as any).enum.length) { - value = firstArrayElement((schema as any).enum); - } - - return value; - } - private createSampleValueFromInferredType( schema: Schema, options?: Options, @@ -218,14 +205,12 @@ export class DefaultTraverse implements Traverse { schema: Schema, options: Options ): Sample | undefined { - let example = this.extractSampleValueFromExamples(schema); - - example = - example === undefined - ? this.extractSampleValueFromVendorExamples(schema, options) - : example; + const value = this.sampleValueExtractor.extractFromExamples( + schema, + options + ); - if (example !== undefined) { + if (value !== undefined) { const { type, readOnly, writeOnly } = schema as OpenAPISchema; this.popSchemaStack(); @@ -234,31 +219,11 @@ export class DefaultTraverse implements Traverse { type, readOnly, writeOnly, - value: example + value }; } } - private extractSampleValueFromExamples(schema: Schema): unknown { - if (this.isExampleExists(schema)) { - return schema.example; - } else if ( - (schema as any).examples !== undefined && - (schema as any).examples.length > 0 - ) { - return firstArrayElement((schema as any).examples); - } - } - - private extractSampleValueFromVendorExamples( - schema: Schema, - { includeVendorExamples }: Options - ): unknown { - return includeVendorExamples - ? this.vendorExampleExtractor.extract(schema) - : undefined; - } - private pushSchemaStack(schema: Schema) { this.schemasStack.push(schema); } @@ -411,22 +376,4 @@ export class DefaultTraverse implements Traverse { undefined ); } - - private isExampleExists( - schema: Schema - ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { - return ( - (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).example !== - undefined - ); - } - - private isDefaultExists( - schema: Schema - ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { - return ( - (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).default !== - undefined - ); - } } diff --git a/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts b/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts new file mode 100644 index 00000000..6ec0298a --- /dev/null +++ b/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts @@ -0,0 +1,71 @@ +import { VendorExampleExtractor } from './VendorExampleExtractor'; +import { Options, Schema } from './Traverse'; +import { firstArrayElement } from '../utils'; +import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; + +export class SchemaExampleExtractor { + constructor( + protected readonly vendorExampleExtractor: VendorExampleExtractor = new VendorExampleExtractor() + ) {} + + public extractFromExamples(schema: Schema, options: Options): unknown { + let value = this.extractFromSchemaExamples(schema); + + value = + value === undefined + ? this.extractFromVendorExamples(schema, options) + : value; + + return value; + } + public extractFromProperties(schema: Schema): unknown { + let value; + if (this.isDefaultExists(schema)) { + value = schema.default; + } else if ((schema as any).const !== undefined) { + value = (schema as any).const; + } else if ((schema as any).enum && (schema as any).enum.length) { + value = firstArrayElement((schema as any).enum); + } + + return value; + } + + private extractFromSchemaExamples(schema: Schema): unknown { + if (this.isExampleExists(schema)) { + return schema.example; + } else if ( + (schema as any).examples !== undefined && + (schema as any).examples.length > 0 + ) { + return firstArrayElement((schema as any).examples); + } + } + + private extractFromVendorExamples( + schema: Schema, + { includeVendorExamples }: Options + ): unknown { + return includeVendorExamples + ? this.vendorExampleExtractor.extract(schema) + : undefined; + } + + private isExampleExists( + schema: Schema + ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { + return ( + (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).example !== + undefined + ); + } + + private isDefaultExists( + schema: Schema + ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { + return ( + (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).default !== + undefined + ); + } +} From d3029a76bdaae3a32e9a17d2a290031fb09fb76e Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 15:23:23 +0400 Subject: [PATCH 14/20] refactor(oas): address duplication issues closes #224 --- .../src/samplers/ArraySampler.ts | 19 ++++---------- .../src/traverse/SchemaExampleExtractor.ts | 25 +++---------------- packages/openapi-sampler/src/utils.ts | 21 ++++++++++++++++ 3 files changed, 29 insertions(+), 36 deletions(-) diff --git a/packages/openapi-sampler/src/samplers/ArraySampler.ts b/packages/openapi-sampler/src/samplers/ArraySampler.ts index 2eaa0d6c..dda01301 100644 --- a/packages/openapi-sampler/src/samplers/ArraySampler.ts +++ b/packages/openapi-sampler/src/samplers/ArraySampler.ts @@ -1,6 +1,6 @@ import { Options, Sample, Specification, Traverse } from '../traverse'; import { Sampler, OpenAPISchema } from './Sampler'; -import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; +import { isItemsExists } from '../utils'; export class ArraySampler implements Sampler { constructor(private readonly traverse: Traverse) {} @@ -12,21 +12,21 @@ export class ArraySampler implements Sampler { ): any[] { let arrayLength = schema.minItems || 1; - if (this.isItemsExists(schema) && Array.isArray(schema.items)) { + if (isItemsExists(schema) && Array.isArray(schema.items)) { arrayLength = Math.max(arrayLength, schema.items.length); } const itemSchemaGetter = (itemNumber: number) => { - if (this.isItemsExists(schema) && Array.isArray(schema.items)) { + if (isItemsExists(schema) && Array.isArray(schema.items)) { return schema.items[itemNumber] || {}; } - return this.isItemsExists(schema) ? schema.items : {}; + return isItemsExists(schema) ? schema.items : {}; }; const res: Sample[] = []; - if (!this.isItemsExists(schema)) { + if (!isItemsExists(schema)) { return res; } @@ -37,13 +37,4 @@ export class ArraySampler implements Sampler { return res; } - - private isItemsExists( - schema: OpenAPISchema - ): schema is OpenAPIV2.SchemaObject | OpenAPIV3.ArraySchemaObject { - return ( - (schema as OpenAPIV2.SchemaObject | OpenAPIV3.ArraySchemaObject).items !== - undefined - ); - } } diff --git a/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts b/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts index 6ec0298a..acffef3d 100644 --- a/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts +++ b/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts @@ -1,7 +1,6 @@ import { VendorExampleExtractor } from './VendorExampleExtractor'; import { Options, Schema } from './Traverse'; -import { firstArrayElement } from '../utils'; -import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; +import { firstArrayElement, isDefaultExists, isExampleExists } from '../utils'; export class SchemaExampleExtractor { constructor( @@ -20,7 +19,7 @@ export class SchemaExampleExtractor { } public extractFromProperties(schema: Schema): unknown { let value; - if (this.isDefaultExists(schema)) { + if (isDefaultExists(schema)) { value = schema.default; } else if ((schema as any).const !== undefined) { value = (schema as any).const; @@ -32,7 +31,7 @@ export class SchemaExampleExtractor { } private extractFromSchemaExamples(schema: Schema): unknown { - if (this.isExampleExists(schema)) { + if (isExampleExists(schema)) { return schema.example; } else if ( (schema as any).examples !== undefined && @@ -50,22 +49,4 @@ export class SchemaExampleExtractor { ? this.vendorExampleExtractor.extract(schema) : undefined; } - - private isExampleExists( - schema: Schema - ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { - return ( - (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).example !== - undefined - ); - } - - private isDefaultExists( - schema: Schema - ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject { - return ( - (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).default !== - undefined - ); - } } diff --git a/packages/openapi-sampler/src/utils.ts b/packages/openapi-sampler/src/utils.ts index 3637d7c1..60170489 100644 --- a/packages/openapi-sampler/src/utils.ts +++ b/packages/openapi-sampler/src/utils.ts @@ -1,3 +1,6 @@ +import { Schema } from './traverse'; +import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; + const isObject = >(obj: T): obj is T => obj && typeof obj === 'object'; @@ -27,3 +30,21 @@ export const firstArrayElement = (x: T[]): T | undefined => x[0]; export const getReplacementForCircular = (type: string) => ({ value: type === 'object' ? {} : type === 'array' ? [] : undefined }); + +export const isExampleExists = ( + schema: Schema +): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => + (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).example !== + undefined; + +export const isDefaultExists = ( + schema: Schema +): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => + (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).default !== + undefined; + +export const isItemsExists = ( + schema: Schema +): schema is OpenAPIV3.ArraySchemaObject | OpenAPIV2.SchemaObject => + (schema as OpenAPIV3.ArraySchemaObject | OpenAPIV2.SchemaObject).items !== + undefined; From 1d5e320533daf0507c67484a9958f2d44eae514d Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 15:31:39 +0400 Subject: [PATCH 15/20] refactor(oas): address duplication issues closes #224 --- .../src/traverse/DefaultTraverse.ts | 12 ++---------- packages/openapi-sampler/src/utils.ts | 14 ++++++++------ 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 24829374..186cee00 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -3,6 +3,7 @@ import { SchemaExampleExtractor } from './SchemaExampleExtractor'; import { firstArrayElement, getReplacementForCircular, + isRefExists, mergeDeep } from '../utils'; import { OpenAPISchema, Sampler } from '../samplers'; @@ -76,7 +77,7 @@ export class DefaultTraverse implements Traverse { this.pushSchemaStack(schema); - if (this.isRefExists(schema)) { + if (isRefExists(schema)) { return this.inferRef(spec, schema, options); } @@ -367,13 +368,4 @@ export class DefaultTraverse implements Traverse { return res; } - - private isRefExists( - schema: Schema - ): schema is OpenAPIV3.ReferenceObject | OpenAPIV2.ReferenceObject { - return ( - (schema as OpenAPIV3.ReferenceObject | OpenAPIV2.ReferenceObject).$ref !== - undefined - ); - } } diff --git a/packages/openapi-sampler/src/utils.ts b/packages/openapi-sampler/src/utils.ts index 60170489..804b70f7 100644 --- a/packages/openapi-sampler/src/utils.ts +++ b/packages/openapi-sampler/src/utils.ts @@ -34,17 +34,19 @@ export const getReplacementForCircular = (type: string) => ({ export const isExampleExists = ( schema: Schema ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => - (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).example !== - undefined; + (schema as any).example !== undefined; export const isDefaultExists = ( schema: Schema ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => - (schema as OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject).default !== - undefined; + (schema as any).default !== undefined; export const isItemsExists = ( schema: Schema ): schema is OpenAPIV3.ArraySchemaObject | OpenAPIV2.SchemaObject => - (schema as OpenAPIV3.ArraySchemaObject | OpenAPIV2.SchemaObject).items !== - undefined; + (schema as any).items !== undefined; + +export const isRefExists = ( + schema: Schema +): schema is OpenAPIV3.ReferenceObject | OpenAPIV2.ReferenceObject => + (schema as any).$ref !== undefined; From 8da9097d25fdadafdb74a6d2a517912079c2e9c9 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 15:39:45 +0400 Subject: [PATCH 16/20] refactor(oas): address duplication issues closes #224 --- packages/openapi-sampler/src/samplers/ArraySampler.ts | 10 +++++----- .../openapi-sampler/src/traverse/DefaultTraverse.ts | 4 ++-- .../src/traverse/SchemaExampleExtractor.ts | 6 +++--- packages/openapi-sampler/src/utils.ts | 8 ++++---- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/openapi-sampler/src/samplers/ArraySampler.ts b/packages/openapi-sampler/src/samplers/ArraySampler.ts index dda01301..8d252d8a 100644 --- a/packages/openapi-sampler/src/samplers/ArraySampler.ts +++ b/packages/openapi-sampler/src/samplers/ArraySampler.ts @@ -1,6 +1,6 @@ import { Options, Sample, Specification, Traverse } from '../traverse'; import { Sampler, OpenAPISchema } from './Sampler'; -import { isItemsExists } from '../utils'; +import { isArraySchema } from '../utils'; export class ArraySampler implements Sampler { constructor(private readonly traverse: Traverse) {} @@ -12,21 +12,21 @@ export class ArraySampler implements Sampler { ): any[] { let arrayLength = schema.minItems || 1; - if (isItemsExists(schema) && Array.isArray(schema.items)) { + if (isArraySchema(schema) && Array.isArray(schema.items)) { arrayLength = Math.max(arrayLength, schema.items.length); } const itemSchemaGetter = (itemNumber: number) => { - if (isItemsExists(schema) && Array.isArray(schema.items)) { + if (isArraySchema(schema) && Array.isArray(schema.items)) { return schema.items[itemNumber] || {}; } - return isItemsExists(schema) ? schema.items : {}; + return isArraySchema(schema) ? schema.items : {}; }; const res: Sample[] = []; - if (!isItemsExists(schema)) { + if (!isArraySchema(schema)) { return res; } diff --git a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts index 186cee00..ad5218e4 100644 --- a/packages/openapi-sampler/src/traverse/DefaultTraverse.ts +++ b/packages/openapi-sampler/src/traverse/DefaultTraverse.ts @@ -3,7 +3,7 @@ import { SchemaExampleExtractor } from './SchemaExampleExtractor'; import { firstArrayElement, getReplacementForCircular, - isRefExists, + isReference, mergeDeep } from '../utils'; import { OpenAPISchema, Sampler } from '../samplers'; @@ -77,7 +77,7 @@ export class DefaultTraverse implements Traverse { this.pushSchemaStack(schema); - if (isRefExists(schema)) { + if (isReference(schema)) { return this.inferRef(spec, schema, options); } diff --git a/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts b/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts index acffef3d..4f508dff 100644 --- a/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts +++ b/packages/openapi-sampler/src/traverse/SchemaExampleExtractor.ts @@ -1,6 +1,6 @@ import { VendorExampleExtractor } from './VendorExampleExtractor'; import { Options, Schema } from './Traverse'; -import { firstArrayElement, isDefaultExists, isExampleExists } from '../utils'; +import { firstArrayElement, hasDefault, hasExample } from '../utils'; export class SchemaExampleExtractor { constructor( @@ -19,7 +19,7 @@ export class SchemaExampleExtractor { } public extractFromProperties(schema: Schema): unknown { let value; - if (isDefaultExists(schema)) { + if (hasDefault(schema)) { value = schema.default; } else if ((schema as any).const !== undefined) { value = (schema as any).const; @@ -31,7 +31,7 @@ export class SchemaExampleExtractor { } private extractFromSchemaExamples(schema: Schema): unknown { - if (isExampleExists(schema)) { + if (hasExample(schema)) { return schema.example; } else if ( (schema as any).examples !== undefined && diff --git a/packages/openapi-sampler/src/utils.ts b/packages/openapi-sampler/src/utils.ts index 804b70f7..402bbbb1 100644 --- a/packages/openapi-sampler/src/utils.ts +++ b/packages/openapi-sampler/src/utils.ts @@ -31,22 +31,22 @@ export const getReplacementForCircular = (type: string) => ({ value: type === 'object' ? {} : type === 'array' ? [] : undefined }); -export const isExampleExists = ( +export const hasExample = ( schema: Schema ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => (schema as any).example !== undefined; -export const isDefaultExists = ( +export const hasDefault = ( schema: Schema ): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => (schema as any).default !== undefined; -export const isItemsExists = ( +export const isArraySchema = ( schema: Schema ): schema is OpenAPIV3.ArraySchemaObject | OpenAPIV2.SchemaObject => (schema as any).items !== undefined; -export const isRefExists = ( +export const isReference = ( schema: Schema ): schema is OpenAPIV3.ReferenceObject | OpenAPIV2.ReferenceObject => (schema as any).$ref !== undefined; From 54ca3430ea61374c2006e5e4cae098d355341a74 Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 15:44:31 +0400 Subject: [PATCH 17/20] refactor(oas): address duplication issues closes #224 --- packages/openapi-sampler/src/utils.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/openapi-sampler/src/utils.ts b/packages/openapi-sampler/src/utils.ts index 402bbbb1..38166129 100644 --- a/packages/openapi-sampler/src/utils.ts +++ b/packages/openapi-sampler/src/utils.ts @@ -1,4 +1,5 @@ import { Schema } from './traverse'; +import { OpenAPISchema } from './samplers'; import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; const isObject = >(obj: T): obj is T => @@ -31,14 +32,10 @@ export const getReplacementForCircular = (type: string) => ({ value: type === 'object' ? {} : type === 'array' ? [] : undefined }); -export const hasExample = ( - schema: Schema -): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => +export const hasExample = (schema: Schema): schema is OpenAPISchema => (schema as any).example !== undefined; -export const hasDefault = ( - schema: Schema -): schema is OpenAPIV3.SchemaObject | OpenAPIV2.SchemaObject => +export const hasDefault = (schema: Schema): schema is OpenAPISchema => (schema as any).default !== undefined; export const isArraySchema = ( From b01a3c2fe2bc0da92db4f7090d7b2e83cf0018cf Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Fri, 16 Feb 2024 15:50:36 +0400 Subject: [PATCH 18/20] refactor(oas): address duplication issues closes #224 --- .../openapi-sampler/src/samplers/ArraySampler.ts | 10 +++++----- packages/openapi-sampler/src/utils.ts | 16 ++++++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/openapi-sampler/src/samplers/ArraySampler.ts b/packages/openapi-sampler/src/samplers/ArraySampler.ts index 8d252d8a..ceb36623 100644 --- a/packages/openapi-sampler/src/samplers/ArraySampler.ts +++ b/packages/openapi-sampler/src/samplers/ArraySampler.ts @@ -1,6 +1,6 @@ import { Options, Sample, Specification, Traverse } from '../traverse'; import { Sampler, OpenAPISchema } from './Sampler'; -import { isArraySchema } from '../utils'; +import { hasItems } from '../utils'; export class ArraySampler implements Sampler { constructor(private readonly traverse: Traverse) {} @@ -12,21 +12,21 @@ export class ArraySampler implements Sampler { ): any[] { let arrayLength = schema.minItems || 1; - if (isArraySchema(schema) && Array.isArray(schema.items)) { + if (hasItems(schema) && Array.isArray(schema.items)) { arrayLength = Math.max(arrayLength, schema.items.length); } const itemSchemaGetter = (itemNumber: number) => { - if (isArraySchema(schema) && Array.isArray(schema.items)) { + if (hasItems(schema) && Array.isArray(schema.items)) { return schema.items[itemNumber] || {}; } - return isArraySchema(schema) ? schema.items : {}; + return hasItems(schema) ? schema.items : {}; }; const res: Sample[] = []; - if (!isArraySchema(schema)) { + if (!hasItems(schema)) { return res; } diff --git a/packages/openapi-sampler/src/utils.ts b/packages/openapi-sampler/src/utils.ts index 38166129..7ca7c8c3 100644 --- a/packages/openapi-sampler/src/utils.ts +++ b/packages/openapi-sampler/src/utils.ts @@ -2,6 +2,14 @@ import { Schema } from './traverse'; import { OpenAPISchema } from './samplers'; import { OpenAPIV2, OpenAPIV3 } from '@har-sdk/core'; +export type OpenAPIArraySchemaObject = + | OpenAPIV3.ArraySchemaObject + | OpenAPIV2.SchemaObject; + +export type OpenAPIReferenceObject = + | OpenAPIV3.ReferenceObject + | OpenAPIV2.ReferenceObject; + const isObject = >(obj: T): obj is T => obj && typeof obj === 'object'; @@ -38,12 +46,8 @@ export const hasExample = (schema: Schema): schema is OpenAPISchema => export const hasDefault = (schema: Schema): schema is OpenAPISchema => (schema as any).default !== undefined; -export const isArraySchema = ( - schema: Schema -): schema is OpenAPIV3.ArraySchemaObject | OpenAPIV2.SchemaObject => +export const hasItems = (schema: Schema): schema is OpenAPIArraySchemaObject => (schema as any).items !== undefined; -export const isReference = ( - schema: Schema -): schema is OpenAPIV3.ReferenceObject | OpenAPIV2.ReferenceObject => +export const isReference = (schema: Schema): schema is OpenAPIReferenceObject => (schema as any).$ref !== undefined; From ea525ab391042dd2bd14f36cc84f621990c532e0 Mon Sep 17 00:00:00 2001 From: Viachaslau Tyshkavets Date: Mon, 26 Feb 2024 18:21:12 +0400 Subject: [PATCH 19/20] refactor: shorten import code style minors --- packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts | 2 +- packages/openapi-sampler/tests/example.spec.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts b/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts index 3cec1156..bb45bffa 100644 --- a/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts +++ b/packages/openapi-sampler/tests/VendorExampleExtractor.spec.ts @@ -1,4 +1,4 @@ -import { VendorExtensions } from '../src/traverse/VendorExtensions'; +import { VendorExtensions } from '../src'; import { VendorExampleExtractor } from '../src/traverse/VendorExampleExtractor'; describe('VendorExampleExtractor', () => { diff --git a/packages/openapi-sampler/tests/example.spec.ts b/packages/openapi-sampler/tests/example.spec.ts index c2118724..65c782c5 100644 --- a/packages/openapi-sampler/tests/example.spec.ts +++ b/packages/openapi-sampler/tests/example.spec.ts @@ -1,5 +1,4 @@ -import { VendorExtensions } from '../src/traverse/VendorExtensions'; -import { sample } from '../src'; +import { VendorExtensions, sample } from '../src'; describe('Example', () => { it('should use example', () => { From 11921351a6776a298f2072e5c5038558ab91d6cb Mon Sep 17 00:00:00 2001 From: Dmitry Ostrikov Date: Tue, 27 Feb 2024 10:18:15 +0400 Subject: [PATCH 20/20] feat(oas): adjust according to code review closes #224 --- packages/oas/tests/DefaultConverter.spec.ts | 14 ++-- ...mple.body.api-connect.swagger.result.json} | 0 ...> x-example.body.api-connect.swagger.yaml} | 0 ....body.disabled.redocly.swagger.result.json | 48 ------------- ....disabled.schemathesis.swagger.result.json | 48 ------------- ...ody.disabled.smartbear.swagger.result.json | 48 ------------- ...ody.disabled.smatrbear.swagger.result.json | 48 ------------- ...example.body.disabled.swagger.result.json} | 0 ...mple.body.enabled.api-connect.swagger.yaml | 66 ------------------ ...-example.body.enabled.redocly.swagger.yaml | 68 ------------------- ...ple.body.enabled.schemathesis.swagger.yaml | 67 ------------------ ...xample.body.enabled.smartbear.swagger.yaml | 66 ------------------ ...-example.body.redocly.swagger.result.json} | 0 ...ml => x-example.body.redocly.swagger.yaml} | 0 ...ple.body.schemathesis.swagger.result.json} | 0 ... x-example.body.schemathesis.swagger.yaml} | 0 ...xample.body.smartbear.swagger.result.json} | 0 ... => x-example.body.smartbear.swagger.yaml} | 0 .../x-example.form-data.enabled.swagger.yaml | 23 ------- ...> x-example.form-data.swagger.result.json} | 0 ....yaml => x-example.form-data.swagger.yaml} | 0 .../x-example.header.enabled.swagger.yaml | 24 ------- ...n => x-example.header.swagger.result.json} | 0 ...ger.yaml => x-example.header.swagger.yaml} | 0 .../x-example.path.enabled.swagger.yaml | 24 ------- ...son => x-example.path.swagger.result.json} | 0 ...agger.yaml => x-example.path.swagger.yaml} | 0 .../x-example.query.enabled.swagger.yaml | 24 ------- ...on => x-example.query.swagger.result.json} | 0 ...gger.yaml => x-example.query.swagger.yaml} | 0 .../src/traverse/VendorExampleExtractor.ts | 8 +-- 31 files changed, 11 insertions(+), 565 deletions(-) rename packages/oas/tests/fixtures/{x-example.body.enabled.api-connect.swagger.result.json => x-example.body.api-connect.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.body.disabled.api-connect.swagger.yaml => x-example.body.api-connect.swagger.yaml} (100%) delete mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json delete mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json delete mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json delete mode 100644 packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json rename packages/oas/tests/fixtures/{x-example.body.disabled.api-connect.swagger.result.json => x-example.body.disabled.swagger.result.json} (100%) delete mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml delete mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml delete mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml delete mode 100644 packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml rename packages/oas/tests/fixtures/{x-example.body.enabled.redocly.swagger.result.json => x-example.body.redocly.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.body.disabled.redocly.swagger.yaml => x-example.body.redocly.swagger.yaml} (100%) rename packages/oas/tests/fixtures/{x-example.body.enabled.schemathesis.swagger.result.json => x-example.body.schemathesis.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.body.disabled.schemathesis.swagger.yaml => x-example.body.schemathesis.swagger.yaml} (100%) rename packages/oas/tests/fixtures/{x-example.body.enabled.smartbear.swagger.result.json => x-example.body.smartbear.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.body.disabled.smartbear.swagger.yaml => x-example.body.smartbear.swagger.yaml} (100%) delete mode 100644 packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml rename packages/oas/tests/fixtures/{x-example.form-data.enabled.swagger.result.json => x-example.form-data.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.form-data.disabled.swagger.yaml => x-example.form-data.swagger.yaml} (100%) delete mode 100644 packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml rename packages/oas/tests/fixtures/{x-example.header.enabled.swagger.result.json => x-example.header.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.header.disabled.swagger.yaml => x-example.header.swagger.yaml} (100%) delete mode 100644 packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml rename packages/oas/tests/fixtures/{x-example.path.enabled.swagger.result.json => x-example.path.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.path.disabled.swagger.yaml => x-example.path.swagger.yaml} (100%) delete mode 100644 packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml rename packages/oas/tests/fixtures/{x-example.query.enabled.swagger.result.json => x-example.query.swagger.result.json} (100%) rename packages/oas/tests/fixtures/{x-example.query.disabled.swagger.yaml => x-example.query.swagger.yaml} (100%) diff --git a/packages/oas/tests/DefaultConverter.spec.ts b/packages/oas/tests/DefaultConverter.spec.ts index 1535e07f..b0fed690 100644 --- a/packages/oas/tests/DefaultConverter.spec.ts +++ b/packages/oas/tests/DefaultConverter.spec.ts @@ -314,7 +314,7 @@ describe('DefaultConverter', () => { async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/x-example.${input}.disabled.swagger.yaml`, + inputFile: `./fixtures/x-example.${input}.swagger.yaml`, expectedFile: `./fixtures/x-example.${input}.disabled.swagger.result.json` }); @@ -333,8 +333,8 @@ describe('DefaultConverter', () => { async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/x-example.${input}.enabled.swagger.yaml`, - expectedFile: `./fixtures/x-example.${input}.enabled.swagger.result.json` + inputFile: `./fixtures/x-example.${input}.swagger.yaml`, + expectedFile: `./fixtures/x-example.${input}.swagger.result.json` }); // act @@ -352,8 +352,8 @@ describe('DefaultConverter', () => { async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/x-example.body.disabled.${input}.swagger.yaml`, - expectedFile: `./fixtures/x-example.body.disabled.${input}.swagger.result.json` + inputFile: `./fixtures/x-example.body.${input}.swagger.yaml`, + expectedFile: `./fixtures/x-example.body.disabled.swagger.result.json` }); // act @@ -371,8 +371,8 @@ describe('DefaultConverter', () => { async (input) => { // arrange const { inputDoc, expectedDoc } = await createFixture({ - inputFile: `./fixtures/x-example.body.enabled.${input}.swagger.yaml`, - expectedFile: `./fixtures/x-example.body.enabled.${input}.swagger.result.json` + inputFile: `./fixtures/x-example.body.${input}.swagger.yaml`, + expectedFile: `./fixtures/x-example.body.${input}.swagger.result.json` }); // act diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.api-connect.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.result.json rename to packages/oas/tests/fixtures/x-example.body.api-connect.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.api-connect.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.api-connect.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json deleted file mode 100644 index dc2a4f99..00000000 --- a/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.result.json +++ /dev/null @@ -1,48 +0,0 @@ -[ - { - "queryString": [], - "cookies": [], - "method": "POST", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "[{\"name\":\"default_name\",\"age\":10}]" - }, - "url": "https://example.com/sample" - }, - { - "queryString": [], - "cookies": [], - "method": "PUT", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"default_name\",\"age\":10}" - }, - "url": "https://example.com/sample/0" - } -] diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json deleted file mode 100644 index dc2a4f99..00000000 --- a/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.result.json +++ /dev/null @@ -1,48 +0,0 @@ -[ - { - "queryString": [], - "cookies": [], - "method": "POST", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "[{\"name\":\"default_name\",\"age\":10}]" - }, - "url": "https://example.com/sample" - }, - { - "queryString": [], - "cookies": [], - "method": "PUT", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"default_name\",\"age\":10}" - }, - "url": "https://example.com/sample/0" - } -] diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json deleted file mode 100644 index dc2a4f99..00000000 --- a/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.result.json +++ /dev/null @@ -1,48 +0,0 @@ -[ - { - "queryString": [], - "cookies": [], - "method": "POST", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "[{\"name\":\"default_name\",\"age\":10}]" - }, - "url": "https://example.com/sample" - }, - { - "queryString": [], - "cookies": [], - "method": "PUT", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"default_name\",\"age\":10}" - }, - "url": "https://example.com/sample/0" - } -] diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json deleted file mode 100644 index dc2a4f99..00000000 --- a/packages/oas/tests/fixtures/x-example.body.disabled.smatrbear.swagger.result.json +++ /dev/null @@ -1,48 +0,0 @@ -[ - { - "queryString": [], - "cookies": [], - "method": "POST", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "[{\"name\":\"default_name\",\"age\":10}]" - }, - "url": "https://example.com/sample" - }, - { - "queryString": [], - "cookies": [], - "method": "PUT", - "headers": [ - { - "value": "application/json", - "name": "content-type" - }, - { - "value": "application/json", - "name": "accept" - } - ], - "httpVersion": "HTTP/1.1", - "headersSize": 0, - "bodySize": 0, - "postData": { - "mimeType": "application/json", - "text": "{\"name\":\"default_name\",\"age\":10}" - }, - "url": "https://example.com/sample/0" - } -] diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.disabled.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.disabled.api-connect.swagger.result.json rename to packages/oas/tests/fixtures/x-example.body.disabled.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml deleted file mode 100644 index 38160cea..00000000 --- a/packages/oas/tests/fixtures/x-example.body.enabled.api-connect.swagger.yaml +++ /dev/null @@ -1,66 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - post: - consumes: - - application/json - produces: - - application/json - parameters: - - name: payload - in: body - required: true - schema: - type: array - items: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - - name: x_example_name - age: 30 - responses: - '201': - description: '' - /sample/{id}: - put: - consumes: - - application/json - produces: - - application/json - parameters: - - name: id - in: path - required: true - type: integer - default: 0 - x-example: 123 - - name: payload - in: body - required: true - schema: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - name: x_example_name - age: 30 - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml deleted file mode 100644 index 8436fa67..00000000 --- a/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.yaml +++ /dev/null @@ -1,68 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - post: - consumes: - - application/json - produces: - - application/json - parameters: - - name: payload - in: body - required: true - schema: - type: array - items: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - application/json: - - name: x_example_name - age: 30 - responses: - '201': - description: '' - /sample/{id}: - put: - consumes: - - application/json - produces: - - application/json - parameters: - - name: id - in: path - required: true - type: integer - default: 0 - x-example: 123 - - name: payload - in: body - required: true - schema: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - example-name: - name: x_example_name - age: 30 - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml deleted file mode 100644 index fc77633f..00000000 --- a/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.yaml +++ /dev/null @@ -1,67 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - post: - consumes: - - application/json - produces: - - application/json - parameters: - - name: payload - in: body - required: true - schema: - type: array - items: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - - name: x_example_name - age: 30 - responses: - '201': - description: '' - /sample/{id}: - put: - consumes: - - application/json - produces: - - application/json - parameters: - - name: id - in: path - required: true - type: integer - default: 0 - x-example: 123 - - name: payload - in: body - required: true - schema: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-examples: - application/json: - name: x_example_name - age: 30 - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml deleted file mode 100644 index 38160cea..00000000 --- a/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.yaml +++ /dev/null @@ -1,66 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - post: - consumes: - - application/json - produces: - - application/json - parameters: - - name: payload - in: body - required: true - schema: - type: array - items: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - - name: x_example_name - age: 30 - responses: - '201': - description: '' - /sample/{id}: - put: - consumes: - - application/json - produces: - - application/json - parameters: - - name: id - in: path - required: true - type: integer - default: 0 - x-example: 123 - - name: payload - in: body - required: true - schema: - type: object - properties: - name: - type: string - default: 'default_name' - age: - type: integer - default: 10 - x-example: - name: x_example_name - age: 30 - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.redocly.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.enabled.redocly.swagger.result.json rename to packages/oas/tests/fixtures/x-example.body.redocly.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.redocly.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.disabled.redocly.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.redocly.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.schemathesis.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.enabled.schemathesis.swagger.result.json rename to packages/oas/tests/fixtures/x-example.body.schemathesis.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.schemathesis.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.disabled.schemathesis.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.schemathesis.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.result.json b/packages/oas/tests/fixtures/x-example.body.smartbear.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.enabled.smartbear.swagger.result.json rename to packages/oas/tests/fixtures/x-example.body.smartbear.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.yaml b/packages/oas/tests/fixtures/x-example.body.smartbear.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.body.disabled.smartbear.swagger.yaml rename to packages/oas/tests/fixtures/x-example.body.smartbear.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml deleted file mode 100644 index 9680cbe1..00000000 --- a/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.yaml +++ /dev/null @@ -1,23 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - post: - consumes: - - application/x-www-form-urlencoded - produces: - - application/json - parameters: - - name: name - in: formData - type: string - default: default_name - x-example: x_example_name - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.result.json b/packages/oas/tests/fixtures/x-example.form-data.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.form-data.enabled.swagger.result.json rename to packages/oas/tests/fixtures/x-example.form-data.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.form-data.disabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.form-data.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.form-data.disabled.swagger.yaml rename to packages/oas/tests/fixtures/x-example.form-data.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml deleted file mode 100644 index 41d3899b..00000000 --- a/packages/oas/tests/fixtures/x-example.header.enabled.swagger.yaml +++ /dev/null @@ -1,24 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - get: - consumes: - - application/json - produces: - - application/json - parameters: - - name: Authorization - in: header - required: true - type: string - default: 'Bearer default_jwt_token' - x-example: 'Bearer x_example_jwt_token' - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.header.enabled.swagger.result.json b/packages/oas/tests/fixtures/x-example.header.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.header.enabled.swagger.result.json rename to packages/oas/tests/fixtures/x-example.header.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.header.disabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.header.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.header.disabled.swagger.yaml rename to packages/oas/tests/fixtures/x-example.header.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml deleted file mode 100644 index df57449e..00000000 --- a/packages/oas/tests/fixtures/x-example.path.enabled.swagger.yaml +++ /dev/null @@ -1,24 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample/{id}: - get: - consumes: - - application/json - produces: - - application/json - parameters: - - name: id - in: path - required: true - type: integer - default: 0 - x-example: 123 - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.path.enabled.swagger.result.json b/packages/oas/tests/fixtures/x-example.path.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.path.enabled.swagger.result.json rename to packages/oas/tests/fixtures/x-example.path.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.path.disabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.path.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.path.disabled.swagger.yaml rename to packages/oas/tests/fixtures/x-example.path.swagger.yaml diff --git a/packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml deleted file mode 100644 index e1a460f5..00000000 --- a/packages/oas/tests/fixtures/x-example.query.enabled.swagger.yaml +++ /dev/null @@ -1,24 +0,0 @@ -swagger: '2.0' -info: - title: Sample API - version: 1.0.0 -host: example.com -schemes: - - https -paths: - /sample: - get: - consumes: - - application/json - produces: - - application/json - parameters: - - name: filter - in: query - required: false - type: string - default: 'default_filter' - x-example: 'x_example_filter' - responses: - '201': - description: '' diff --git a/packages/oas/tests/fixtures/x-example.query.enabled.swagger.result.json b/packages/oas/tests/fixtures/x-example.query.swagger.result.json similarity index 100% rename from packages/oas/tests/fixtures/x-example.query.enabled.swagger.result.json rename to packages/oas/tests/fixtures/x-example.query.swagger.result.json diff --git a/packages/oas/tests/fixtures/x-example.query.disabled.swagger.yaml b/packages/oas/tests/fixtures/x-example.query.swagger.yaml similarity index 100% rename from packages/oas/tests/fixtures/x-example.query.disabled.swagger.yaml rename to packages/oas/tests/fixtures/x-example.query.swagger.yaml diff --git a/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts b/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts index d2cff238..3ac7fb8f 100644 --- a/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts +++ b/packages/openapi-sampler/src/traverse/VendorExampleExtractor.ts @@ -11,10 +11,10 @@ export class VendorExampleExtractor { return [ schema[VendorExtensions.X_EXAMPLE], schema[VendorExtensions.X_EXAMPLES] - ] - .map((example) => this.findExampleByShape(example, schema)) - .filter((example) => !!example) - .shift(); + ].reduce( + (acc, example) => (acc ? acc : this.findExampleByShape(example, schema)), + undefined + ); } private findExampleByShape(example: unknown, schema: Schema) {