Skip to content

Commit

Permalink
feat(oas): introduce support for examples vendor extensions in Swagge…
Browse files Browse the repository at this point in the history
…r converter

closes #224
  • Loading branch information
ostridm committed Feb 12, 2024
1 parent 30eb0e4 commit 9201593
Show file tree
Hide file tree
Showing 28 changed files with 1,034 additions and 33 deletions.
22 changes: 20 additions & 2 deletions packages/oas/src/converter/Sampler.ts
Original file line number Diff line number Diff line change
@@ -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: {
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down
4 changes: 4 additions & 0 deletions packages/oas/src/converter/VendorExtensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum VendorExtensions {
X_EXAMPLE = 'x-example',
X_EXAMPLES = 'x-examples'
}
17 changes: 15 additions & 2 deletions packages/oas/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Request[]> => {
export const oas2har = (
collection: OpenAPI.Document,
options: Options = {}
): Promise<Request[]> => {
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);
Expand Down
146 changes: 123 additions & 23 deletions packages/oas/tests/DefaultConverter.spec.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
[
{
Expand Down Expand Up @@ -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);
});
});

Expand Down Expand Up @@ -271,18 +282,107 @@ 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({
jsonPointer: expected
});
})
);

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);
}
);
});
});
Original file line number Diff line number Diff line change
@@ -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: ''
Original file line number Diff line number Diff line change
@@ -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: ''
Loading

0 comments on commit 9201593

Please sign in to comment.