Skip to content

Commit

Permalink
feat(msw): support using OpenAPI example/examples fields as MSW values (
Browse files Browse the repository at this point in the history
  • Loading branch information
GMierzwa authored Oct 30, 2023
1 parent 219d97b commit c19e35c
Show file tree
Hide file tree
Showing 10 changed files with 129 additions and 4 deletions.
20 changes: 20 additions & 0 deletions docs/src/pages/reference/configuration/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -807,6 +807,26 @@ module.exports = {
};
```

##### useExamples

Type: `Boolean`.

Give you the possibility to use the `example`/`examples` fields from your OpenAPI specification as mock values.

```js
module.exports = {
petstore: {
output: {
override: {
mock: {
useExamples: true,
},
},
},
},
};
```

##### baseUrl

Type: `String`.
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/getters/array.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { SchemaObject } from 'openapi3-ts';
import { ContextSpecs, ScalarValue } from '../types';
import { resolveObject } from '../resolvers/object';
import { resolveExampleRefs } from '../resolvers';

/**
* Return the output type from an array
Expand Down Expand Up @@ -32,6 +33,8 @@ export const getArray = ({
type: 'array',
isRef: false,
hasReadonlyProps: resolvedObject.hasReadonlyProps,
example: schema.example,
examples: resolveExampleRefs(schema.examples, context),
};
} else {
throw new Error('All arrays must have an `items` key define');
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/getters/combine.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import omit from 'lodash.omit';
import { SchemaObject } from 'openapi3-ts';
import { resolveObject } from '../resolvers';
import { resolveExampleRefs, resolveObject } from '../resolvers';
import {
ContextSpecs,
GeneratorImport,
Expand Down Expand Up @@ -106,6 +106,8 @@ export const combineSchemas = ({
types: [],
originalSchema: [],
hasReadonlyProps: false,
example: schema.example,
examples: resolveExampleRefs(schema.examples, context),
} as CombinedData,
);

Expand Down Expand Up @@ -137,6 +139,8 @@ export const combineSchemas = ({
type: 'object' as SchemaType,
isRef: false,
hasReadonlyProps: resolvedData.hasReadonlyProps,
example: schema.example,
examples: resolveExampleRefs(schema.examples, context),
};
}

Expand All @@ -155,6 +159,8 @@ export const combineSchemas = ({
resolvedData?.hasReadonlyProps ||
resolvedValue?.hasReadonlyProps ||
false,
example: schema.example,
examples: resolveExampleRefs(schema.examples, context),
};
};

Expand Down
6 changes: 5 additions & 1 deletion packages/core/src/getters/object.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ReferenceObject, SchemaObject } from 'openapi3-ts';
import { resolveObject, resolveRef, resolveValue } from '../resolvers';
import { resolveExampleRefs, resolveObject, resolveValue } from '../resolvers';
import { ContextSpecs, ScalarValue, SchemaType } from '../types';
import { isBoolean, isReference, jsDoc, pascal } from '../utils';
import { combineSchemas } from './combine';
Expand Down Expand Up @@ -32,6 +32,8 @@ export const getObject = ({
type: 'object',
isRef: true,
hasReadonlyProps: item.readOnly || false,
example: item.example,
examples: resolveExampleRefs(item.examples, context),
};
}

Expand Down Expand Up @@ -144,6 +146,8 @@ export const getObject = ({
isRef: false,
schema: {},
hasReadonlyProps: false,
example: item.example,
examples: resolveExampleRefs(item.examples, context),
} as ScalarValue,
);
}
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/getters/res-req-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
SchemaObject,
} from 'openapi3-ts';
import { resolveObject } from '../resolvers/object';
import { resolveRef } from '../resolvers/ref';
import { resolveExampleRefs, resolveRef } from '../resolvers/ref';
import { ContextSpecs, ResReqTypesValue } from '../types';
import { camel } from '../utils';
import { isReference } from '../utils/assertion';
Expand Down Expand Up @@ -76,6 +76,8 @@ export const getResReqTypes = (
isRef: true,
hasReadonlyProps: false,
originalSchema: mediaType?.schema,
example: mediaType.example,
examples: resolveExampleRefs(mediaType.examples, context),
key,
contentType,
},
Expand Down Expand Up @@ -119,6 +121,8 @@ export const getResReqTypes = (
formUrlEncoded,
isRef: true,
originalSchema: mediaType?.schema,
example: mediaType.example,
examples: resolveExampleRefs(mediaType.examples, context),
key,
contentType,
},
Expand Down Expand Up @@ -153,6 +157,8 @@ export const getResReqTypes = (
...resolvedValue,
imports: resolvedValue.imports,
contentType,
example: mediaType.example,
examples: resolveExampleRefs(mediaType.examples, context),
};
}

Expand All @@ -179,6 +185,8 @@ export const getResReqTypes = (
formData,
formUrlEncoded,
contentType,
example: mediaType.example,
examples: resolveExampleRefs(mediaType.examples, context),
};
},
);
Expand Down
9 changes: 9 additions & 0 deletions packages/core/src/getters/scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ContextSpecs, ScalarValue } from '../types';
import { escape, isString } from '../utils';
import { getArray } from './array';
import { getObject } from './object';
import { resolveExampleRefs } from '../resolvers';

/**
* Return the typescript equivalent of open-api data type
Expand Down Expand Up @@ -48,6 +49,8 @@ export const getScalar = ({
imports: [],
isRef: false,
hasReadonlyProps: item.readOnly || false,
example: item.example,
examples: resolveExampleRefs(item.examples, context),
};
}

Expand All @@ -60,6 +63,8 @@ export const getScalar = ({
imports: [],
isRef: false,
hasReadonlyProps: item.readOnly || false,
example: item.example,
examples: resolveExampleRefs(item.examples, context),
};

case 'array': {
Expand Down Expand Up @@ -107,6 +112,8 @@ export const getScalar = ({
schemas: [],
isRef: false,
hasReadonlyProps: item.readOnly || false,
example: item.example,
examples: resolveExampleRefs(item.examples, context),
};
}

Expand Down Expand Up @@ -139,6 +146,8 @@ export const getScalar = ({
schemas: [],
isRef: false,
hasReadonlyProps: item.readOnly || false,
example: item.example,
examples: resolveExampleRefs(item.examples, context),
};
}

Expand Down
44 changes: 44 additions & 0 deletions packages/core/src/resolvers/ref.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import get from 'lodash.get';
import {
ExampleObject,
ParameterObject,
ReferenceObject,
RequestBodyObject,
Expand Down Expand Up @@ -31,6 +32,15 @@ export const resolveRef = <Schema extends ComponentObject = ComponentObject>(
context,
imports,
);
if ('examples' in schema) {
schema.examples = resolveExampleRefs(schema.examples, context);
}
if ('examples' in resolvedRef.schema) {
resolvedRef.schema.examples = resolveExampleRefs(
resolvedRef.schema.examples,
context,
);
}
return {
schema: {
...schema,
Expand All @@ -41,6 +51,9 @@ export const resolveRef = <Schema extends ComponentObject = ComponentObject>(
}

if (!isReference(schema)) {
if ('examples' in schema) {
schema.examples = resolveExampleRefs(schema.examples, context);
}
return { schema: schema as Schema, imports };
}

Expand Down Expand Up @@ -91,3 +104,34 @@ function getSchema<Schema extends ComponentObject = ComponentObject>(
refInfo,
};
}

type Example = ExampleObject | ReferenceObject;
type Examples = Example[] | Record<string, Example> | undefined;
export const resolveExampleRefs = (
examples: Examples,
context: ContextSpecs,
): Examples => {
if (!examples) {
return undefined;
}
if (Array.isArray(examples)) {
return examples.map((example) => {
if (isReference(example)) {
const { schema } = resolveRef<ExampleObject>(example, context);
return schema.value;
}
return example;
});
} else {
return Object.entries(examples).reduce((acc, [key, example]) => {
let schema = example;
if (isReference(example)) {
schema = resolveRef<ExampleObject>(example, context).schema.value;
}
return {
...acc,
[key]: schema,
};
}, {});
}
};
4 changes: 4 additions & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export type NormalizedOverrideOutput = {
required?: boolean;
baseUrl?: string;
delay?: number | (() => number);
useExamples?: boolean;
};
contentType?: OverrideOutputContentType;
header: false | ((info: InfoObject) => string[] | string);
Expand Down Expand Up @@ -263,6 +264,7 @@ export type OverrideOutput = {
required?: boolean;
baseUrl?: string;
delay?: number;
useExamples?: boolean;
};
contentType?: OverrideOutputContentType;
header?: boolean | ((info: InfoObject) => string[] | string);
Expand Down Expand Up @@ -754,6 +756,8 @@ export type ScalarValue = {
imports: GeneratorImport[];
schemas: GeneratorSchema[];
isRef: boolean;
example?: any;
examples?: Record<string, any>;
};

export type ResolverValue = ScalarValue & {
Expand Down
9 changes: 9 additions & 0 deletions packages/msw/src/getters/scalar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ export const getMockScalar = ({
return property;
}

if (context.override?.mock?.useExamples && item.example) {
return {
value: JSON.stringify(item.example),
imports: [],
name: item.name,
overrided: true,
};
}

const ALL_FORMAT: Record<string, string> = {
...DEFAULT_FORMAT_MOCK,
...(mockOptions?.format ?? {}),
Expand Down
20 changes: 19 additions & 1 deletion packages/msw/src/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,25 @@ export const getResponsesMockDefinition = ({
context: ContextSpecs;
}) => {
return response.types.success.reduce(
(acc, { value: definition, originalSchema, imports, isRef }) => {
(
acc,
{ value: definition, originalSchema, example, examples, imports, isRef },
) => {
if (context.override?.mock?.useExamples) {
const exampleValue =
example ||
originalSchema?.example ||
Object.values(examples || {})[0]?.value ||
originalSchema?.examples?.[0];
if (exampleValue) {
acc.definitions.push(
transformer
? transformer(exampleValue, response.definition.success)
: JSON.stringify(exampleValue),
);
return acc;
}
}
if (!definition || generalJSTypesWithArray.includes(definition)) {
const value = getMockScalarJsTypes(definition, mockOptionsWithoutFunc);

Expand Down

0 comments on commit c19e35c

Please sign in to comment.