Skip to content

Commit

Permalink
feat: copy all schema examples (#93)
Browse files Browse the repository at this point in the history
* feat: schema examples

* test: schema examples

* chore: cleanup

* fix: do not support x-example in oas3

* chore: rename schema util

* fix: oas3 schema examples are not supported

* fix: do not support oas2 schema x-example property

* test: update and clean

* fix: combine oas2 schema examples
  • Loading branch information
Jakub Jankowski authored May 22, 2020
1 parent eae08d8 commit c4c8115
Show file tree
Hide file tree
Showing 7 changed files with 230 additions and 11 deletions.
73 changes: 73 additions & 0 deletions src/oas2/transformers/__tests__/params.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,79 @@ describe('params.translator', () => {
),
).toMatchSnapshot();
});

describe('schema examples', () => {
describe('given response with schema with x-examples', () => {
it('should translate to body parameter with examples', () => {
const body = translateToBodyParameter(
{
in: 'body',
name: 'name',
schema: {
// @ts-ignore: "x-examples" as schema extension
'x-examples': {
'example-1': {
hello: 'world',
},
'example-2': {
foo: 'bar',
},
},
},
},
consumes,
);

expect(body).toEqual(
expect.objectContaining({
contents: expect.arrayContaining([
expect.objectContaining({
examples: [
{ key: 'example-1', value: { hello: 'world' } },
{ key: 'example-2', value: { foo: 'bar' } },
],
}),
]),
}),
);
});
});

describe('given response with body param and schema examples', () => {
it('root x-examples should take precedence over schema examples', () => {
const body = translateToBodyParameter(
{
in: 'body',
name: 'name',
schema: {
// @ts-ignore: "x-examples" as schema and oas extension
'x-examples': {
'example-1': {
hello: 'world',
},
},
},
'x-examples': {
'example-2': {
foo: 'bar',
},
},
},
consumes,
);

expect(body).toEqual(
expect.objectContaining({
contents: expect.arrayContaining([
expect.objectContaining({
examples: [{ key: 'example-2', value: { foo: 'bar' } }],
}),
]),
}),
);
});
});
});
});

describe('translateToFormDataParameter', () => {
Expand Down
79 changes: 79 additions & 0 deletions src/oas2/transformers/__tests__/responses.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { HttpParamStyles, IHttpHeaderParam } from '@stoplight/types';

import { translateToHeaderParams } from '../params';
import { translateToResponses } from '../responses';
import { Schema } from 'swagger-schema-official';

jest.mock('../params');

Expand Down Expand Up @@ -94,4 +95,82 @@ describe('responses', () => {
expect(responses[0].contents![0].examples).toHaveLength(2);
});
});

describe('schema examples', () => {
describe('given a response with a schema with an example', () => {
it('should translate to response with examples', () => {
const responses = translateToResponses(
{
r1: {
description: 'd1',
headers: {},
schema: {
example: {
name: 'value',
},
},
},
},
produces,
);
expect(responses[0].contents![0]).toHaveProperty('examples', [{ key: 'default', value: { name: 'value' } }]);
});
});

describe('given multiple schema example properties', () => {
it('should translate all examples', () => {
const responses = translateToResponses(
{
r1: {
description: 'd1',
headers: {},
schema: {
example: {
name: 'example value',
},
['x-examples']: {
'application/json': {
name: 'examples value',
},
},
} as Schema,
},
},
produces,
);
expect(responses[0].contents![0]).toHaveProperty('examples', [
{ key: 'application/json', value: { name: 'examples value' } },
{ key: 'default', value: { name: 'example value' } },
]);
});
});

describe('given response with examples in root and schema objects', () => {
it('root examples should take precedence over schema examples', () => {
const responses = translateToResponses(
{
r1: {
description: 'd1',
headers: {},
examples: {
'application/i-have-no-clue': {},
'application/json': {},
},
schema: {
example: {
name: 'value',
},
},
},
},
produces,
);
expect(responses[0].contents![0].examples).toHaveLength(2);
expect(responses[0].contents![0].examples).toEqual([
{ key: 'application/json', value: {} },
{ key: 'application/i-have-no-clue', value: {} },
]);
});
});
});
});
10 changes: 10 additions & 0 deletions src/oas2/transformers/getExamplesFromSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { isObject } from 'lodash';
import { Schema } from 'swagger-schema-official';
import { Dictionary } from '@stoplight/types';

export function getExamplesFromSchema(data: Schema & { 'x-examples'?: Dictionary<unknown> }): Dictionary<unknown> {
return {
...('x-examples' in data && isObject(data['x-examples']) && { ...data['x-examples'] }),
...('example' in data && { default: data.example }),
};
}
6 changes: 5 additions & 1 deletion src/oas2/transformers/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
QueryParameter,
Schema,
} from 'swagger-schema-official';
import { getExamplesFromSchema } from './getExamplesFromSchema';

function chooseQueryParameterStyle(
parameter: QueryParameter,
Expand Down Expand Up @@ -74,7 +75,10 @@ export function translateToHeaderParams(headers: { [headerName: string]: Header
}

export function translateToBodyParameter(body: BodyParameter, consumes: string[]): IHttpOperationRequestBody {
const examples = map(get(body, 'x-examples'), (value, key) => ({ key, value }));
const examples = map(
get(body, 'x-examples') || (body.schema ? getExamplesFromSchema(body.schema) : void 0),
(value, key) => ({ key, value }),
);

return pickBy({
description: body.description,
Expand Down
5 changes: 4 additions & 1 deletion src/oas2/transformers/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import { chain, map, partial } from 'lodash';
import { Response } from 'swagger-schema-official';

import { translateToHeaderParams } from './params';
import { getExamplesFromSchema } from './getExamplesFromSchema';

function translateToResponse(produces: string[], response: Response, statusCode: string): IHttpOperationResponse {
const headers = translateToHeaderParams(response.headers || {});
const objectifiedExamples = chain(response.examples)
const objectifiedExamples = chain(
response.examples || (response.schema ? getExamplesFromSchema(response.schema) : void 0),
)
.mapValues((value, key) => ({ key, value }))
.values()
.value();
Expand Down
45 changes: 45 additions & 0 deletions src/oas3/transformers/__tests__/content.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,51 @@ describe('translateMediaTypeObject', () => {
});
});
});

describe('schema examples', () => {
const defaultExample = {
name: 'default value',
};

describe('given response with schema with examples', () => {
test('should translate to IHttpContent', () => {
expect(
translateMediaTypeObject(
{
schema: {
example: defaultExample,
},
encoding: {},
},
'mediaType',
),
).toHaveProperty('examples', [{ key: 'default', value: defaultExample }]);
});
});

describe('given response with examples in media and schema objects', () => {
test('root example should take precedence over schema example', () => {
expect(
translateMediaTypeObject(
{
schema: {
example: defaultExample,
},
examples: { example: { value: { name: 'root example value' } } },
example: {
name: 'root default value',
},
encoding: {},
},
'mediaType',
),
).toHaveProperty('examples', [
{ key: 'default', value: { name: 'root default value' } },
{ key: 'example', value: { name: 'root example value' } },
]);
});
});
});
});

describe('schema invalid', () => {
Expand Down
23 changes: 14 additions & 9 deletions src/oas3/transformers/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Optional,
} from '@stoplight/types';
import { JSONSchema4 } from 'json-schema';
import { compact, get, isObject, keys, map, omit, pickBy, union, values } from 'lodash';
import { compact, get, isObject, keys, map, mapValues, omit, pickBy, union, values } from 'lodash';

// @ts-ignore
import * as toJsonSchema from '@openapi-contrib/openapi-schema-to-json-schema';
Expand Down Expand Up @@ -108,17 +108,22 @@ export function translateHeaderObject(headerObject: unknown, name: string): Opti
export function translateMediaTypeObject(mediaObject: unknown, mediaType: string): Optional<IMediaTypeContent> {
if (!isDictionary(mediaObject)) return;

const { schema, example, examples, encoding } = mediaObject;
const { schema, encoding } = mediaObject;

const jsonSchema = schema
? (toJsonSchema(schema, {
cloneSchema: true,
strictMode: false,
keepNotSupported: ['example', 'deprecated', 'readOnly', 'writeOnly', 'xml', 'externalDocs'],
}) as JSONSchema4)
: undefined;

const example = mediaObject.example || jsonSchema?.example;
const examples = mediaObject.examples;

return {
mediaType,
schema: schema
? (toJsonSchema(schema, {
cloneSchema: true,
strictMode: false,
keepNotSupported: ['example', 'deprecated', 'readOnly', 'writeOnly', 'xml', 'externalDocs'],
}) as JSONSchema4)
: undefined,
schema: jsonSchema,
// Note that I'm assuming all references are resolved
examples: compact(
union<INodeExample>(
Expand Down

0 comments on commit c4c8115

Please sign in to comment.