From 1bdbf517f08f9dcc605ca720271c6edc0e585f53 Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 10:52:55 -0700 Subject: [PATCH 1/7] feat(mock): add faker schema mock generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `schemas` and `operationResponses` options to the faker mock generator. When `schemas: true`, orval emits a consolidated `index.faker.ts` alongside the generated schema types with one `getMock()` factory per `components/schemas` entry — useful for unit tests, Storybook stories, and seed scripts that don't need MSW handlers. Splits the Validation & Mocking docs into separate MSW and Faker pages. --- docs/content/docs/guides/faker.mdx | 207 +++++++++++++ docs/content/docs/guides/meta.json | 1 + docs/content/docs/guides/msw.mdx | 63 +--- packages/core/src/types.ts | 8 + packages/core/src/writers/index.ts | 1 + packages/mock/src/faker/index.ts | 104 +++++++ packages/mock/src/index.ts | 9 +- packages/orval/src/client.ts | 31 +- packages/orval/src/write-specs.ts | 125 ++++++++ .../mock/petstore-faker-schemas/endpoints.ts | 103 +++++++ .../mock/petstore-faker-schemas/model/cat.ts | 12 + .../petstore-faker-schemas/model/catType.ts | 12 + .../model/createPetsBody.ts | 11 + .../model/createPetsParams.ts | 20 ++ .../model/createPetsSort.ts | 16 + .../petstore-faker-schemas/model/dachshund.ts | 12 + .../model/dachshundBreed.ts | 13 + .../mock/petstore-faker-schemas/model/dog.ts | 19 ++ .../petstore-faker-schemas/model/dogType.ts | 12 + .../petstore-faker-schemas/model/error.ts | 11 + .../model/index.faker.ts | 290 ++++++++++++++++++ .../petstore-faker-schemas/model/index.ts | 26 ++ .../model/labradoodle.ts | 12 + .../model/labradoodleBreed.ts | 13 + .../model/listPetsParams.ts | 20 ++ .../model/listPetsSort.ts | 15 + .../mock/petstore-faker-schemas/model/pet.ts | 30 ++ .../model/petCallingCode.ts | 14 + .../model/petCountry.ts | 13 + .../model/petWithTag.ts | 12 + .../mock/petstore-faker-schemas/model/pets.ts | 9 + tests/configs/mock.config.ts | 17 + 32 files changed, 1198 insertions(+), 63 deletions(-) create mode 100644 docs/content/docs/guides/faker.mdx create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/endpoints.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/cat.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/catType.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsBody.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsParams.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsSort.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/dachshund.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/dachshundBreed.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/dog.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/dogType.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/error.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/index.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodle.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodleBreed.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsParams.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsSort.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/pet.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/petCallingCode.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/petCountry.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/petWithTag.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas/model/pets.ts diff --git a/docs/content/docs/guides/faker.mdx b/docs/content/docs/guides/faker.mdx new file mode 100644 index 0000000000..9270259ab2 --- /dev/null +++ b/docs/content/docs/guides/faker.mdx @@ -0,0 +1,207 @@ +--- +title: Faker +description: Generate mock data factories with Faker.js from OpenAPI +--- + +Generate mock data factories powered by [Faker.js](https://fakerjs.dev/) from your OpenAPI specification. Faker output has no `msw` dependency, so it's useful for unit tests, Storybook stories, seed scripts, and any test setup that doesn't go through a network mock. + +For Mock Service Worker request handlers, see the [MSW guide](/docs/guides/msw). + +## Configuration + +Add a `faker` generator entry to `output.mock.generators`: + +```ts title="orval.config.ts" +import { defineConfig } from 'orval'; + +export default defineConfig({ + petstore: { + output: { + mode: 'single', + target: './src/api/petstore.ts', + schemas: './src/api/model', + mock: { + generators: [{ type: 'faker' }], + }, + }, + input: { + target: './petstore.yaml', + }, + }, +}); +``` + +You can also combine `msw` and `faker` to emit both files in the same run: + +```ts +mock: { + generators: [{ type: 'msw' }, { type: 'faker' }], +} +``` + +The Faker output is written to `.faker.ts` and only depends on `@faker-js/faker`. + +## Generated Output + +### Response Factories + +For each operation, Orval emits a `getResponseMock` factory that returns a fully-populated response value: + +```ts +import { faker } from '@faker-js/faker'; + +export const getShowPetByIdResponseMock = ( + overrideResponse: Partial = {}, +): Pet => ({ + id: faker.number.int({ min: undefined, max: undefined }), + name: faker.string.alpha(20), + tag: faker.string.alpha(20), + ...overrideResponse, +}); +``` + +Pass overrides for any subset of fields: + +```ts +const pet = getShowPetByIdResponseMock({ name: 'Buddy' }); +// => { id: 7272122785202176, name: "Buddy", tag: "..." } +``` + +## Options + +Set faker-specific options on the generator entry: + +```ts title="orval.config.ts" +mock: { + generators: [ + { + type: 'faker', + useExamples: true, + generateEachHttpStatus: true, + locale: 'en_GB', + preferredContentType: 'application/json', + }, + ], +} +``` + +| Option | Type | Default | Description | +|---|---|---|---| +| `useExamples` | `boolean` | `false` | Seed mock values from OpenAPI `example`/`examples` fields when present. | +| `generateEachHttpStatus` | `boolean` | `false` | Emit a separate factory per HTTP status code defined in the spec (not just the success response). | +| `locale` | `keyof typeof allLocales` | — | Faker locale. Switches the import to `@faker-js/faker/locale/` (e.g. `'en_GB'`, `'fr'`, `'ja'`). | +| `preferredContentType` | `string` | — | When an operation has multiple response content types, mock the one matching this MIME type. | + +## Customizing Mock Values + +Use `override.mock` to control how individual schemas, properties, and formats are mocked. These options apply to both `faker` and `msw` generators. + +```ts title="orval.config.ts" +override: { + mock: { + properties: { + // Match by property name (string or regex) + email: () => faker.internet.email(), + '/.*Id$/': () => faker.string.uuid(), + }, + format: { + // Match by OpenAPI `format` keyword + date: () => faker.date.past().toISOString(), + 'date-time': () => faker.date.recent().toISOString(), + }, + required: true, // Always populate optional fields + arrayMin: 3, + arrayMax: 5, + stringMin: 4, + stringMax: 20, + numberMin: 0, + numberMax: 100, + fractionDigits: 2, + }, +} +``` + +You can also scope overrides per-operation or per-tag via `override.operations` and `override.tags`. + +## Usage + +### Unit Tests + +```ts +import { describe, it, expect } from 'vitest'; +import { getShowPetByIdResponseMock } from './api/petstore.faker'; + +describe('PetDetails', () => { + it('renders the pet name', () => { + const pet = getShowPetByIdResponseMock({ name: 'Buddy' }); + render(); + expect(screen.getByText('Buddy')).toBeInTheDocument(); + }); +}); +``` + +### Storybook + +```ts +import type { Meta, StoryObj } from '@storybook/react'; +import { getShowPetByIdResponseMock } from '../api/petstore.faker'; +import { PetDetails } from './PetDetails'; + +const meta: Meta = { + component: PetDetails, +}; +export default meta; + +export const Default: StoryObj = { + args: { pet: getShowPetByIdResponseMock() }, +}; + +export const NamedPet: StoryObj = { + args: { pet: getShowPetByIdResponseMock({ name: 'Buddy' }) }, +}; +``` + +### Seed Scripts + +```ts +import { writeFile } from 'node:fs/promises'; +import { getListPetsResponseMock } from './api/petstore.faker'; + +const seed = Array.from({ length: 50 }, () => getListPetsResponseMock()); +await writeFile('seed/pets.json', JSON.stringify(seed, null, 2)); +``` + +## Deterministic Output + +Faker's PRNG is seedable. Set a seed before invoking factories to get reproducible output, which is helpful for snapshot testing: + +```ts +import { faker } from '@faker-js/faker'; +import { getShowPetByIdResponseMock } from './api/petstore.faker'; + +beforeEach(() => { + faker.seed(42); +}); + +it('matches snapshot', () => { + expect(getShowPetByIdResponseMock()).toMatchSnapshot(); +}); +``` + +## Dynamic Imports + +In `tags-split` mode, enable `mock.indexMockFiles` to emit an `index.faker.ts` aggregating all per-tag faker files: + +```ts title="orval.config.ts" +export default defineConfig({ + petstore: { + output: { + mode: 'tags-split', + mock: { + indexMockFiles: true, + generators: [{ type: 'faker' }], + }, + }, + }, +}); +``` diff --git a/docs/content/docs/guides/meta.json b/docs/content/docs/guides/meta.json index dcfc851fe2..4452f73f47 100644 --- a/docs/content/docs/guides/meta.json +++ b/docs/content/docs/guides/meta.json @@ -23,6 +23,7 @@ "zod", "client-with-zod", "msw", + "faker", "---Advanced---", "enums", "stream-ndjson", diff --git a/docs/content/docs/guides/msw.mdx b/docs/content/docs/guides/msw.mdx index 5625dbcf49..46abe64c4b 100644 --- a/docs/content/docs/guides/msw.mdx +++ b/docs/content/docs/guides/msw.mdx @@ -5,9 +5,11 @@ description: Generate Mock Service Worker handlers from OpenAPI Generate [MSW (Mock Service Worker)](https://mswjs.io/) handlers from your OpenAPI specification to mock your API during development and testing. +For mock data factories without MSW request handlers, see the [Faker guide](/docs/guides/faker). + ## Configuration -Set the `mock` option to `true`: +Set the `mock` option to `true` (emits both MSW handlers and Faker factories), or scope it to MSW only via the generator entry: ```ts title="orval.config.ts" import { defineConfig } from 'orval'; @@ -18,7 +20,9 @@ export default defineConfig({ mode: 'single', target: './src/api/petstore.ts', schemas: './src/api/model', - mock: true, + mock: { + generators: [{ type: 'msw' }], + }, }, input: { target: './petstore.yaml', @@ -29,33 +33,9 @@ export default defineConfig({ ## Generated Output -Orval generates three types of functions: +The MSW generator emits two types of functions per operation, plus an aggregator. Mock *data* (the `getResponseMock` factories) is produced by the Faker generator — see the [Faker guide](/docs/guides/faker) for details on overriding values and formats. -### 1. Mock Data Generators - -Functions that return mocked values using [Faker.js](https://fakerjs.dev/): - -```ts -import { faker } from '@faker-js/faker'; - -export const getShowPetByIdResponseMock = ( - overrideResponse: Partial = {}, -): Pet => ({ - id: faker.number.int({ min: undefined, max: undefined }), - name: faker.string.alpha(20), - tag: faker.string.alpha(20), - ...overrideResponse, -}); -``` - -Override values as needed: - -```ts -const pet = getShowPetByIdResponseMock({ name: 'Buddy' }); -// => { id: 7272122785202176, name: "Buddy", tag: "..." } -``` - -### 2. Request Handlers +### 1. Request Handlers Functions that bind mock data to [MSW](https://mswjs.io/) `http.*` handlers using the recommended [`HttpResponse`](https://mswjs.io/docs/api/http-response) class: @@ -115,7 +95,7 @@ export default defineConfig({ `preferredContentType` accepts common MIME literals and any custom string (via a loose `(string & {})` fallback), so vendor-specific types are supported too. -### 3. Aggregated Handlers +### 2. Aggregated Handlers Functions that combine all handlers for easy setup: @@ -269,31 +249,6 @@ export { server }; If both `msw` and `faker` generators are configured with `indexMockFiles: true`, you also get an `index.faker.ts` alongside `index.msw.ts`. -## Faker-only Output - -If you only need mock data factories without MSW request handlers, use a `faker` generator entry. It emits the same `getResponseMock` factory functions, but with no `msw` import or HTTP handler code: - -```ts title="orval.config.ts" -export default defineConfig({ - petstore: { - output: { - mock: { - generators: [{ type: 'faker' }], - }, - }, - }, -}); -``` - -The output is written to `.faker.ts` and only depends on `@faker-js/faker`. Useful for: - -- Unit tests with custom assertion libraries -- Storybook stories -- Seed scripts -- Any test setup that doesn't use MSW - -You can combine both generators (e.g. `generators: [{ type: 'msw' }, { type: 'faker' }]`) to emit both files in the same run. - ## MSW Best Practices The generated code follows [MSW best practices](https://mswjs.io/docs/best-practices): diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 0b5564f9aa..75df9b8053 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -435,6 +435,14 @@ export interface MswMockOptions extends CommonMockOptions { export interface FakerMockOptions extends CommonMockOptions { type: typeof OutputMockType.FAKER; + // Emit a consolidated mock factory file for every entry under + // `components/schemas` (one `getMock` per schema). Defaults to + // `false` — schema factories are opt-in to preserve existing output. + schemas?: boolean; + // Emit per-operation response mock factories (the historical behavior). + // Defaults to `true`. Set to `false` together with `schemas: true` to get + // only the consolidated schema factories. + operationResponses?: boolean; } export type GlobalMockOptions = MswMockOptions | FakerMockOptions; diff --git a/packages/core/src/writers/index.ts b/packages/core/src/writers/index.ts index 1b378f7326..9eb9c20400 100644 --- a/packages/core/src/writers/index.ts +++ b/packages/core/src/writers/index.ts @@ -1,3 +1,4 @@ +export * from './file'; export * from './schemas'; export * from './single-mode'; export * from './split-mode'; diff --git a/packages/mock/src/faker/index.ts b/packages/mock/src/faker/index.ts index 83aee10d0d..bcfa03a5ca 100644 --- a/packages/mock/src/faker/index.ts +++ b/packages/mock/src/faker/index.ts @@ -1,14 +1,19 @@ import { type ClientMockGeneratorBuilder, + type ContextSpec, generateDependencyImports, type GenerateMockImports, type GeneratorDependency, + type GeneratorImport, type GeneratorOptions, + type GeneratorSchema, type GeneratorVerbOptions, type GlobalMockOptions, + pascal, } from '@orval/core'; import { generateMSW } from '../msw'; +import { getMockScalar } from './getters'; function getFakerDependencies( options?: GlobalMockOptions, @@ -67,3 +72,102 @@ export function generateFaker( imports: result.imports, }; } + +export interface GenerateFakerForSchemasResult { + implementation: string; + imports: GeneratorImport[]; +} + +/** + * Builds the contents of a consolidated faker mock file for every entry under + * `components/schemas`. Each schema produces a `getMock(overrides)` + * factory in the spirit of the existing per-operation `getResponseMock` + * helpers. Opt in via `mock.generators: [{ type: 'faker', schemas: true }]`. + * + * Returns the function bodies plus any `GeneratorImport` references the + * factories need so the writer can hoist them into the file header. + */ +export function generateFakerForSchemas( + schemas: GeneratorSchema[], + context: ContextSpec, + options: GlobalMockOptions, +): GenerateFakerForSchemasResult { + const factories: string[] = []; + const allImports: GeneratorImport[] = []; + // Shared across schemas so we emit each helper (e.g. an `allOf`-discriminator + // sub-factory) once even when several schemas reference the same union arm. + const splitMockImplementations: string[] = []; + + const mockOptions = context.output.override.mock; + + for (const generatorSchema of schemas) { + const { name, schema } = generatorSchema; + if (!schema) continue; + + const factoryName = `get${pascal(name)}Mock`; + const factoryImports: GeneratorImport[] = []; + + const result = getMockScalar({ + item: { + ...(schema as Record), + name, + } as Parameters[0]['item'], + imports: factoryImports, + mockOptions, + operationId: name, + tags: [], + context, + existingReferencedProperties: [], + splitMockImplementations, + allowOverride: true, + isRef: false, + } as Parameters[0]); + + allImports.push(...result.imports, ...factoryImports); + + // Match the behavior of operation-response factories: only declare the + // `overrideResponse` parameter when the generated expression actually + // references it (top-level object schemas). Array / scalar / enum + // schemas don't splice an override, so we omit the parameter rather than + // emit a `Partial` signature TS can't satisfy. + const typeName = pascal(name); + const isOverridable = result.value?.includes('overrideResponse'); + const param = isOverridable + ? `overrideResponse: Partial<${typeName}> = {}` + : ''; + const factory = `export const ${factoryName} = (${param}): ${typeName} => (${result.value});\n`; + + factories.push(factory); + + // Track the schema type itself as an import so writers can reference it + // from the generated factory file. + allImports.push({ + name: pascal(name), + values: false, + }); + } + + // De-duplicate imports by name+alias so the header doesn't list the same + // schema twice when multiple factories reference it. + const seen = new Set(); + const uniqueImports = allImports.filter((imp) => { + const key = `${imp.name}::${imp.alias ?? ''}`; + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + + // Reference `options` so unused-parameter rules don't complain; future + // schema-specific behavior (e.g. naming convention) will read from it. + void options; + + // Helper factories from union/discriminator handling (`splitMockImplementations`) + // are emitted before the public `getMock` factories so call sites + // declared after them resolve cleanly without TS hoisting concerns. + const implementation = [...splitMockImplementations, ...factories].join('\n'); + + return { + implementation, + imports: uniqueImports, + }; +} diff --git a/packages/mock/src/index.ts b/packages/mock/src/index.ts index a221a2bc4e..438aae8a06 100644 --- a/packages/mock/src/index.ts +++ b/packages/mock/src/index.ts @@ -19,6 +19,8 @@ export const DEFAULT_MSW_OPTIONS: MswMockOptions = { export const DEFAULT_FAKER_OPTIONS: FakerMockOptions = { type: OutputMockType.FAKER, useExamples: false, + schemas: false, + operationResponses: true, }; /** @@ -75,5 +77,10 @@ export function generateMock( } } -export { generateFaker, generateFakerImports } from './faker'; +export type { GenerateFakerForSchemasResult } from './faker'; +export { + generateFaker, + generateFakerForSchemas, + generateFakerImports, +} from './faker'; export { generateMSW, generateMSWImports } from './msw'; diff --git a/packages/orval/src/client.ts b/packages/orval/src/client.ts index c069a3700d..f5a924f4e9 100644 --- a/packages/orval/src/client.ts +++ b/packages/orval/src/client.ts @@ -281,14 +281,29 @@ export const generateOperations = ( // Function-form entries (ClientMockBuilder) inherit the historical // `msw` file extension and are treated as msw outputs for downstream // bookkeeping. - const mockOutputs = output.mock.generators.map((entry) => { - const generated = invokeMockGenerator(verbOption, options, entry); - return { - type: isFunction(entry) ? OutputMockType.MSW : entry.type, - implementation: generated.implementation, - imports: generated.imports, - }; - }); + const mockOutputs = output.mock.generators + .filter((entry) => { + // A faker entry with `operationResponses: false` opts out of the + // per-operation `getResponseMock` factories. The consolidated + // schemas file (when `schemas: true`) is emitted separately and is + // unaffected by this filter. + if ( + !isFunction(entry) && + entry.type === OutputMockType.FAKER && + entry.operationResponses === false + ) { + return false; + } + return true; + }) + .map((entry) => { + const generated = invokeMockGenerator(verbOption, options, entry); + return { + type: isFunction(entry) ? OutputMockType.MSW : entry.type, + implementation: generated.implementation, + imports: generated.imports, + }; + }); const hasImplementation = client.implementation.trim().length > 0; const preferredOperationKey = verbOption.operationName; diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index 1f0fa45a5c..0a13a9c875 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -1,21 +1,27 @@ import path from 'node:path'; import { + type ContextSpec, createSuccessMessage, fixCrossDirectoryImports, fixRegularSchemaImports, + generateDependencyImports, getFileInfo, getMockFileExtensionByTypeName, + type GlobalMockOptions, + isFunction, isObject, isString, jsDoc, logWarning, type NormalizedOptions, type OpenApiInfoObject, + OutputMockType, OutputMode, splitSchemasByType, SupportedFormatter, upath, + writeGeneratedFile, writeSchemas, writeSingleMode, type WriteSpecBuilder, @@ -23,6 +29,7 @@ import { writeSplitTagsMode, writeTagsMode, } from '@orval/core'; +import { generateFakerForSchemas } from '@orval/mock'; import { execa, ExecaError } from 'execa'; import fs from 'fs-extra'; import { unique } from 'remeda'; @@ -133,6 +140,118 @@ async function addOperationSchemasReExport( } } +/** + * Emit `/index.faker.ts` (or `/schemas.faker.ts` + * when `output.schemas` is not configured) when a faker generator entry has + * `schemas: true`. Each `components/schemas` entry becomes a + * `getMock(overrides)` factory in the file. Returns the written + * file path so callers can include it in formatter / hook runs, or + * `undefined` if no file was written. + */ +async function writeFakerSchemaMocks( + builder: WriteSpecBuilder, + options: NormalizedOptions, + header: string, +): Promise { + const { output } = options; + const fakerEntry = output.mock.generators.find( + (g): g is GlobalMockOptions => + !isFunction(g) && g.type === OutputMockType.FAKER, + ); + if (fakerEntry?.schemas !== true) { + return undefined; + } + + const schemasWithDef = builder.schemas.filter((s) => !!s.schema); + if (schemasWithDef.length === 0) { + return undefined; + } + + const context: ContextSpec = { + spec: builder.spec, + target: builder.target, + workspace: '', + output, + }; + + const { implementation, imports } = generateFakerForSchemas( + schemasWithDef, + context, + fakerEntry, + ); + + if (!implementation.trim()) { + return undefined; + } + + let filePath: string; + let schemaImportPath: string | undefined; + const fileExtension = output.fileExtension || '.ts'; + + if (output.schemas) { + const schemasDir = isString(output.schemas) + ? output.schemas + : output.schemas.path; + filePath = path.join(schemasDir, `index.faker${fileExtension}`); + schemaImportPath = '.'; + } else { + const targetInfo = output.target + ? getFileInfo(output.target, { extension: fileExtension }) + : undefined; + const dir = targetInfo?.dirname ?? process.cwd(); + filePath = path.join(dir, `schemas.faker${fileExtension}`); + // Without a dedicated schemas dir we have no separate types file to + // import from; the factories will reference inline types declared in + // the main output target. + schemaImportPath = targetInfo ? `./${targetInfo.filename}` : undefined; + } + + // Force every schema-type import (`values: false`) onto the resolved + // schemas path so they're emitted as `import type { Pet } from '.'` + // instead of one file per schema. + const reroutedImports = imports.map((imp) => + imp.importPath || imp.values + ? imp + : { ...imp, importPath: schemaImportPath }, + ); + + // `generateDependencyImports` expects a list of `{ exports, dependency }` + // groups (one per source module). Bucket all rerouted imports by their + // resolved `importPath` so each module emits a single `import type { ... }` + // line. + const grouped = new Map(); + for (const imp of reroutedImports) { + const key = imp.importPath ?? ''; + if (!key) continue; + const bucket = grouped.get(key) ?? []; + bucket.push(imp); + grouped.set(key, bucket); + } + + const importsHeader = generateDependencyImports( + implementation, + [ + { + exports: [{ name: 'faker', values: true }], + dependency: fakerEntry.locale + ? `@faker-js/faker/locale/${fakerEntry.locale}` + : '@faker-js/faker', + }, + ...[...grouped.entries()].map(([dependency, exports]) => ({ + exports, + dependency, + })), + ], + undefined, + !!output.schemas, + false, + ); + + const content = `${header}${importsHeader}\n\n${implementation}`; + await writeGeneratedFile(filePath, content); + return filePath; +} + export async function writeSpecs( builder: WriteSpecBuilder, workspace: string, @@ -333,6 +452,11 @@ export async function writeSpecs( } } + // Emit a consolidated faker mock file for `components/schemas` when the + // faker generator opts in with `schemas: true`. Lives alongside the + // generated TS schema types so factories can import them directly. + const fakerSchemaPath = await writeFakerSchemaMocks(builder, options, header); + let implementationPaths: string[] = []; if (output.target) { @@ -443,6 +567,7 @@ export async function writeSpecs( ).dirname, ] : []), + ...(fakerSchemaPath ? [fakerSchemaPath] : []), ...(output.operationSchemas ? [getFileInfo(output.operationSchemas).dirname] : []), diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/endpoints.ts b/tests/__snapshots__/mock/petstore-faker-schemas/endpoints.ts new file mode 100644 index 0000000000..47486777c6 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/endpoints.ts @@ -0,0 +1,103 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import axios from 'axios'; +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; + +import type { + CreatePetsBody, + CreatePetsParams, + ListPetsParams, + Pet, + PetWithTag, + Pets, +} from './model'; + +export const getSwaggerPetstore = (axiosInstance: AxiosInstance = axios) => { + /** + * @summary List all pets + */ + const listPets = ( + params: ListPetsParams, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/pets`, { + ...options, + params: { ...params, ...options?.params }, + }); + }; + + /** + * @summary Create a pet + */ + const createPets = ( + createPetsBody: CreatePetsBody, + params: CreatePetsParams, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.post(`/pets`, createPetsBody, { + ...options, + params: { ...params, ...options?.params }, + }); + }; + + /** + * @summary Info for a specific pet + */ + const showPetById = ( + petId: string, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/pets/${petId}`, options); + }; + + /** + * @summary Deletes a specific pet + */ + const deletePetById = ( + petId: string, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.delete(`/pets/${petId}`, options); + }; + + /** + * @summary health check + */ + const healthCheck = ( + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/health`, { + responseType: 'text', + ...options, + }); + }; + + /** + * @summary combinate nullable and $ref + */ + const showPetWithOwner = ( + petId: string, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/pets/${petId}/owner`, options); + }; + + return { + listPets, + createPets, + showPetById, + deletePetById, + healthCheck, + showPetWithOwner, + }; +}; +export type ListPetsResult = AxiosResponse; +export type CreatePetsResult = AxiosResponse; +export type ShowPetByIdResult = AxiosResponse; +export type DeletePetByIdResult = AxiosResponse; +export type HealthCheckResult = AxiosResponse; +export type ShowPetWithOwnerResult = AxiosResponse; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/cat.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/cat.ts new file mode 100644 index 0000000000..dec05f4eaa --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/cat.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { CatType } from './catType'; + +export interface Cat { + petsRequested?: number; + type: CatType; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/catType.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/catType.ts new file mode 100644 index 0000000000..f8717f6194 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/catType.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type CatType = (typeof CatType)[keyof typeof CatType]; + +export const CatType = { + cat: 'cat', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsBody.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsBody.ts new file mode 100644 index 0000000000..5ff3a610e8 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsBody.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type CreatePetsBody = { + name: string; + tag: string; +}; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsParams.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsParams.ts new file mode 100644 index 0000000000..ea473ca17c --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsParams.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { CreatePetsSort } from './createPetsSort'; + +export type CreatePetsParams = { + /** + * How many items to return at one time (max 100) + */ + limit?: string; + /** + * Which property to sort by? + * Example: name sorts ASC while -name sorts DESC. + * + */ + sort: CreatePetsSort; +}; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsSort.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsSort.ts new file mode 100644 index 0000000000..0ab76c2466 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/createPetsSort.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type CreatePetsSort = + (typeof CreatePetsSort)[keyof typeof CreatePetsSort]; + +export const CreatePetsSort = { + name: 'name', + '-name': '-name', + email: 'email', + '-email': '-email', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/dachshund.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/dachshund.ts new file mode 100644 index 0000000000..7dcea1dd44 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/dachshund.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { DachshundBreed } from './dachshundBreed'; + +export interface Dachshund { + length: number; + breed: DachshundBreed; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/dachshundBreed.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/dachshundBreed.ts new file mode 100644 index 0000000000..13f7f2d23b --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/dachshundBreed.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type DachshundBreed = + (typeof DachshundBreed)[keyof typeof DachshundBreed]; + +export const DachshundBreed = { + Dachshund: 'Dachshund', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/dog.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/dog.ts new file mode 100644 index 0000000000..df6194262b --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/dog.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Dachshund } from './dachshund'; +import type { DogType } from './dogType'; +import type { Labradoodle } from './labradoodle'; + +export type Dog = + | (Labradoodle & { + barksPerMinute?: number; + type: DogType; + }) + | (Dachshund & { + barksPerMinute?: number; + type: DogType; + }); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/dogType.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/dogType.ts new file mode 100644 index 0000000000..801768166e --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/dogType.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type DogType = (typeof DogType)[keyof typeof DogType]; + +export const DogType = { + dog: 'dog', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/error.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/error.ts new file mode 100644 index 0000000000..0dd1b135d2 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/error.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export interface Error { + code: number; + message: string; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts new file mode 100644 index 0000000000..1fda5ef0d9 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts @@ -0,0 +1,290 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import { faker } from '@faker-js/faker'; + +import type { + Cat, + Dachshund, + Dog, + Error, + Labradoodle, + Pet, + PetWithTag, + Pets, +} from '.'; + +export const getDogResponseLabradoodleMock = ( + overrideResponse: Partial = {}, +): Labradoodle => ({ + ...{ + cuteness: faker.number.int(), + breed: faker.helpers.arrayElement(['Labradoodle'] as const), + }, + ...overrideResponse, +}); +export const getDogResponseDachshundMock = ( + overrideResponse: Partial = {}, +): Dachshund => ({ + ...{ + length: faker.number.int(), + breed: faker.helpers.arrayElement(['Dachshund'] as const), + }, + ...overrideResponse, +}); +export const getPetResponseLabradoodleMock = ( + overrideResponse: Partial = {}, +): Labradoodle => ({ + ...{ + cuteness: faker.number.int(), + breed: faker.helpers.arrayElement(['Labradoodle'] as const), + }, + ...overrideResponse, +}); +export const getPetResponseDachshundMock = ( + overrideResponse: Partial = {}, +): Dachshund => ({ + ...{ + length: faker.number.int(), + breed: faker.helpers.arrayElement(['Dachshund'] as const), + }, + ...overrideResponse, +}); +export const getPetResponseDogMock = ( + overrideResponse: Omit, 'breed'> = {}, +): Dog => ({ + ...{ + ...faker.helpers.arrayElement([ + { ...getPetResponseLabradoodleMock() }, + { ...getPetResponseDachshundMock() }, + ]), + barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['dog'] as const), + }, + ...overrideResponse, +}); +export const getPetResponseCatMock = ( + overrideResponse: Partial = {}, +): Cat => ({ + ...{ + petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['cat'] as const), + }, + ...overrideResponse, +}); +export const getPetsResponseLabradoodleMock = ( + overrideResponse: Partial = {}, +): Labradoodle => ({ + ...{ + cuteness: faker.number.int(), + breed: faker.helpers.arrayElement(['Labradoodle'] as const), + }, + ...overrideResponse, +}); +export const getPetsResponseDachshundMock = ( + overrideResponse: Partial = {}, +): Dachshund => ({ + ...{ + length: faker.number.int(), + breed: faker.helpers.arrayElement(['Dachshund'] as const), + }, + ...overrideResponse, +}); +export const getPetsResponseDogMock = ( + overrideResponse: Omit, 'breed'> = {}, +): Dog => ({ + ...{ + ...faker.helpers.arrayElement([ + { ...getPetsResponseLabradoodleMock() }, + { ...getPetsResponseDachshundMock() }, + ]), + barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['dog'] as const), + }, + ...overrideResponse, +}); +export const getPetsResponseCatMock = ( + overrideResponse: Partial = {}, +): Cat => ({ + ...{ + petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['cat'] as const), + }, + ...overrideResponse, +}); +export const getPetWithTagResponseLabradoodleMock = ( + overrideResponse: Partial = {}, +): Labradoodle => ({ + ...{ + cuteness: faker.number.int(), + breed: faker.helpers.arrayElement(['Labradoodle'] as const), + }, + ...overrideResponse, +}); +export const getPetWithTagResponseDachshundMock = ( + overrideResponse: Partial = {}, +): Dachshund => ({ + ...{ + length: faker.number.int(), + breed: faker.helpers.arrayElement(['Dachshund'] as const), + }, + ...overrideResponse, +}); +export const getPetWithTagResponseDogMock = ( + overrideResponse: Omit, 'breed'> = {}, +): Dog => ({ + ...{ + ...faker.helpers.arrayElement([ + { ...getPetWithTagResponseLabradoodleMock() }, + { ...getPetWithTagResponseDachshundMock() }, + ]), + barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['dog'] as const), + }, + ...overrideResponse, +}); +export const getPetWithTagResponseCatMock = ( + overrideResponse: Partial = {}, +): Cat => ({ + ...{ + petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['cat'] as const), + }, + ...overrideResponse, +}); +export const getLabradoodleMock = ( + overrideResponse: Partial = {}, +): Labradoodle => ({ + cuteness: faker.number.int(), + breed: faker.helpers.arrayElement(['Labradoodle'] as const), + ...overrideResponse, +}); + +export const getDachshundMock = ( + overrideResponse: Partial = {}, +): Dachshund => ({ + length: faker.number.int(), + breed: faker.helpers.arrayElement(['Dachshund'] as const), + ...overrideResponse, +}); + +export const getDogMock = (): Dog => ({ + ...faker.helpers.arrayElement([ + { ...getDogResponseLabradoodleMock() }, + { ...getDogResponseDachshundMock() }, + ]), + barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['dog'] as const), +}); + +export const getCatMock = (overrideResponse: Partial = {}): Cat => ({ + petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['cat'] as const), + ...overrideResponse, +}); + +export const getPetMock = (): Pet => ({ + ...faker.helpers.arrayElement([ + { ...getPetResponseDogMock() }, + { ...getPetResponseCatMock() }, + ]), + '@id': faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + id: faker.number.int(), + name: faker.string.alpha({ length: { min: 10, max: 20 } }), + tag: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33'] as const), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "People's Republic of China", + 'Uruguay', + ] as const), + undefined, + ]), +}); + +export const getPetsMock = (): Pets => + Array.from( + { length: faker.number.int({ min: 1, max: 10 }) }, + (_, i) => i + 1, + ).map(() => ({ + ...faker.helpers.arrayElement([ + { ...getPetsResponseDogMock() }, + { ...getPetsResponseCatMock() }, + ]), + '@id': faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + id: faker.number.int(), + name: faker.string.alpha({ length: { min: 10, max: 20 } }), + tag: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33'] as const), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "People's Republic of China", + 'Uruguay', + ] as const), + undefined, + ]), + })); + +export const getErrorMock = (overrideResponse: Partial = {}): Error => ({ + code: faker.number.int(), + message: faker.string.alpha({ length: { min: 10, max: 20 } }), + ...overrideResponse, +}); + +export const getPetWithTagMock = ( + overrideResponse: Partial = {}, +): PetWithTag => ({ + tag: faker.string.alpha({ length: { min: 10, max: 20 } }), + pet: { + ...faker.helpers.arrayElement([ + { ...getPetWithTagResponseDogMock() }, + { ...getPetWithTagResponseCatMock() }, + ]), + '@id': faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + id: faker.number.int(), + name: faker.string.alpha({ length: { min: 10, max: 20 } }), + tag: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33'] as const), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "People's Republic of China", + 'Uruguay', + ] as const), + undefined, + ]), + }, + ...overrideResponse, +}); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/index.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/index.ts new file mode 100644 index 0000000000..757f76a9f6 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/index.ts @@ -0,0 +1,26 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export * from './cat'; +export * from './catType'; +export * from './createPetsBody'; +export * from './createPetsParams'; +export * from './createPetsSort'; +export * from './dachshund'; +export * from './dachshundBreed'; +export * from './dog'; +export * from './dogType'; +export * from './error'; +export * from './labradoodle'; +export * from './labradoodleBreed'; +export * from './listPetsParams'; +export * from './listPetsSort'; +export * from './pet'; +export * from './petCallingCode'; +export * from './petCountry'; +export * from './pets'; +export * from './petWithTag'; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodle.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodle.ts new file mode 100644 index 0000000000..2bd34368b1 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodle.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { LabradoodleBreed } from './labradoodleBreed'; + +export interface Labradoodle { + cuteness: number; + breed: LabradoodleBreed; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodleBreed.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodleBreed.ts new file mode 100644 index 0000000000..a59fd1baa8 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/labradoodleBreed.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type LabradoodleBreed = + (typeof LabradoodleBreed)[keyof typeof LabradoodleBreed]; + +export const LabradoodleBreed = { + Labradoodle: 'Labradoodle', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsParams.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsParams.ts new file mode 100644 index 0000000000..7d5a1998a2 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsParams.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { ListPetsSort } from './listPetsSort'; + +export type ListPetsParams = { + /** + * How many items to return at one time (max 100) + */ + limit?: string; + /** + * Which property to sort by? + * Example: name sorts ASC while -name sorts DESC. + * + */ + sort: ListPetsSort; +}; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsSort.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsSort.ts new file mode 100644 index 0000000000..580e537eb7 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/listPetsSort.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type ListPetsSort = (typeof ListPetsSort)[keyof typeof ListPetsSort]; + +export const ListPetsSort = { + name: 'name', + '-name': '-name', + email: 'email', + '-email': '-email', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/pet.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/pet.ts new file mode 100644 index 0000000000..272899cbe9 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/pet.ts @@ -0,0 +1,30 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Cat } from './cat'; +import type { Dog } from './dog'; +import type { PetCallingCode } from './petCallingCode'; +import type { PetCountry } from './petCountry'; + +export type Pet = + | (Dog & { + '@id'?: string; + id: number; + name: string; + tag?: string; + email?: string; + callingCode?: PetCallingCode; + country?: PetCountry; + }) + | (Cat & { + '@id'?: string; + id: number; + name: string; + tag?: string; + email?: string; + callingCode?: PetCallingCode; + country?: PetCountry; + }); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/petCallingCode.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/petCallingCode.ts new file mode 100644 index 0000000000..2c69b445ac --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/petCallingCode.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type PetCallingCode = + (typeof PetCallingCode)[keyof typeof PetCallingCode]; + +export const PetCallingCode = { + '+33': '+33', + '+420': '+420', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/petCountry.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/petCountry.ts new file mode 100644 index 0000000000..34b590efc9 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/petCountry.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type PetCountry = (typeof PetCountry)[keyof typeof PetCountry]; + +export const PetCountry = { + "People's_Republic_of_China": "People's Republic of China", + Uruguay: 'Uruguay', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/petWithTag.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/petWithTag.ts new file mode 100644 index 0000000000..2c16182546 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/petWithTag.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Pet } from './pet'; + +export interface PetWithTag { + tag: string; + pet: Pet | null; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/pets.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/pets.ts new file mode 100644 index 0000000000..d5b8145922 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/pets.ts @@ -0,0 +1,9 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Pet } from './pet'; + +export type Pets = Pet[]; diff --git a/tests/configs/mock.config.ts b/tests/configs/mock.config.ts index d0e654dc22..247629945a 100644 --- a/tests/configs/mock.config.ts +++ b/tests/configs/mock.config.ts @@ -346,4 +346,21 @@ export default defineConfig({ target: '../specifications/msw-binary-multi-content.yaml', }, }, + petstoreFakerSchemas: { + output: { + target: '../generated/mock/petstore-faker-schemas/endpoints.ts', + schemas: '../generated/mock/petstore-faker-schemas/model', + client: 'axios', + mock: { + generators: [ + { type: 'faker', schemas: true, operationResponses: false }, + ], + }, + clean: true, + formatter: 'prettier', + }, + input: { + target: '../specifications/petstore.yaml', + }, + }, }); From 0e02d4f95270d82029cf3e9160f9db0f0950149e Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 12:09:09 -0700 Subject: [PATCH 2/7] fix(orval): narrow fakerEntry to FakerMockOptions for typecheck --- packages/orval/src/write-specs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index 0a13a9c875..afec38629d 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -3,12 +3,12 @@ import path from 'node:path'; import { type ContextSpec, createSuccessMessage, + type FakerMockOptions, fixCrossDirectoryImports, fixRegularSchemaImports, generateDependencyImports, getFileInfo, getMockFileExtensionByTypeName, - type GlobalMockOptions, isFunction, isObject, isString, @@ -155,7 +155,7 @@ async function writeFakerSchemaMocks( ): Promise { const { output } = options; const fakerEntry = output.mock.generators.find( - (g): g is GlobalMockOptions => + (g): g is FakerMockOptions => !isFunction(g) && g.type === OutputMockType.FAKER, ); if (fakerEntry?.schemas !== true) { From 19a78feee1718b7364e2315e01b526264e32fb4d Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 12:29:10 -0700 Subject: [PATCH 3/7] feat(mock): delegate operation responses to schema faker factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `schemas: true` and `operationResponses: true` are both set on the faker generator, operation-response factories now call `getMock()` for every `#/components/schemas/X` they reference instead of inlining the body. Falls back to inlining when an operation- or tag-level override touches a property of the referenced schema, so existing override semantics keep working. Also delegates `oneOf` discriminator arms that are themselves top-level schema refs (e.g. `Dog`/`Cat`) — those now resolve to `getDogMock()`/`getCatMock()` rather than emitting per-operation helper factories. --- packages/core/src/types.ts | 4 + .../writers/generate-imports-for-builder.ts | 23 +++- packages/mock/src/faker/index.ts | 2 +- packages/mock/src/faker/resolvers/value.ts | 115 ++++++++++++++++++ tests/configs/mock.config.ts | 17 +++ 5 files changed, 159 insertions(+), 2 deletions(-) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 75df9b8053..886b84890f 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1142,6 +1142,10 @@ export interface GeneratorImport { readonly syntheticDefaultImport?: boolean; readonly namespaceImport?: boolean; readonly importPath?: string; + // True when this import points at a generated schema-level faker factory + // (e.g. `getPetMock`). The mock-file writer routes it to + // `/index.faker` instead of `/`. + readonly schemaFactory?: boolean; } export interface GeneratorDependency { diff --git a/packages/core/src/writers/generate-imports-for-builder.ts b/packages/core/src/writers/generate-imports-for-builder.ts index 6db836ba9d..aa6ac57284 100644 --- a/packages/core/src/writers/generate-imports-for-builder.ts +++ b/packages/core/src/writers/generate-imports-for-builder.ts @@ -15,6 +15,27 @@ export function generateImportsForBuilder( const isZodSchemaOutput = isObject(output.schemas) && output.schemas.type === 'zod'; + // Schema-factory imports (`getPetMock` and friends) always resolve to the + // consolidated `/index.faker` file emitted by the faker + // schemas option. They bypass the per-schema convention naming below. + const schemaFactoryImports = imports.filter((i) => i.schemaFactory); + const schemaFactoryDeps: GeneratorDependency[] = + schemaFactoryImports.length > 0 + ? [ + { + exports: uniqueBy( + schemaFactoryImports, + (entry) => `${entry.name}|${entry.alias ?? ''}`, + ), + dependency: upath.joinSafe(relativeSchemasPath, 'index.faker'), + }, + ] + : []; + + // The rest of the schema-import bucket is for types emitted alongside + // each schema (`Pet`, `PetWithTag`, ...). They're routed below. + imports = imports.filter((i) => !i.schemaFactory); + let schemaImports: GeneratorDependency[]; if (output.indexFiles) { schemaImports = isZodSchemaOutput @@ -80,5 +101,5 @@ export function generateImportsForBuilder( }; }); - return [...schemaImports, ...otherImports]; + return [...schemaImports, ...schemaFactoryDeps, ...otherImports]; } diff --git a/packages/mock/src/faker/index.ts b/packages/mock/src/faker/index.ts index bcfa03a5ca..43e2a833a0 100644 --- a/packages/mock/src/faker/index.ts +++ b/packages/mock/src/faker/index.ts @@ -131,7 +131,7 @@ export function generateFakerForSchemas( // schemas don't splice an override, so we omit the parameter rather than // emit a `Partial` signature TS can't satisfy. const typeName = pascal(name); - const isOverridable = result.value?.includes('overrideResponse'); + const isOverridable = result.value.includes('overrideResponse'); const param = isOverridable ? `overrideResponse: Partial<${typeName}> = {}` : ''; diff --git a/packages/mock/src/faker/resolvers/value.ts b/packages/mock/src/faker/resolvers/value.ts index 65039f4d35..4a2420e89e 100644 --- a/packages/mock/src/faker/resolvers/value.ts +++ b/packages/mock/src/faker/resolvers/value.ts @@ -2,9 +2,11 @@ import { type ContextSpec, type GeneratorImport, getRefInfo, + isFunction, isReference, type MockOptions, type OpenApiSchemaObject, + OutputMockType, pascal, } from '@orval/core'; import { prop } from 'remeda'; @@ -55,6 +57,77 @@ export function getNullable(value: string, nullable?: boolean) { return nullable ? `faker.helpers.arrayElement([${value}, null])` : value; } +/** + * True when the active faker generator entry asks for consolidated schema + * mock factories and the output is configured to host them (i.e. there is a + * dedicated schemas directory we can import `index.faker` from). Used to + * decide whether an operation factory should inline a `$ref`'d schema or + * delegate to its `getMock` factory. + */ +function shouldDelegateToSchemaFactories(context: ContextSpec): boolean { + if (!context.output.schemas) return false; + for (const g of context.output.mock.generators) { + if (isFunction(g)) continue; + if (g.type === OutputMockType.FAKER && g.schemas === true) { + // `operationResponses` defaults to true; we only need to confirm + // schemas are being emitted at all. + return true; + } + } + return false; +} + +/** + * Predicate: this `$ref` points at a top-level `#/components/schemas/` + * (vs. a parameter, response, or inline schema). Only those have a + * corresponding `getMock` factory in the consolidated faker file. + */ +function isComponentsSchemaRef(refPaths: string[] | undefined): boolean { + return ( + Array.isArray(refPaths) && + refPaths[0] === 'components' && + refPaths[1] === 'schemas' + ); +} + +/** + * Returns true when an operation- or tag-level mock override touches any + * property declared on the referenced schema. In that case we must inline + * the schema body so the override actually applies; the shared + * `getMock` factory has no knowledge of operation-scoped overrides. + */ +function hasOverrideTouchingSchema( + schemaProperties: Record | undefined, + mockOptions: MockOptions | undefined, + operationId: string, + tags: string[], +): boolean { + if (!schemaProperties) return false; + const propertyNames = Object.keys(schemaProperties); + if (propertyNames.length === 0) return false; + + const overrideBuckets: (Record | undefined)[] = [ + mockOptions?.operations?.[operationId]?.properties, + ]; + for (const tag of tags) { + overrideBuckets.push(mockOptions?.tags?.[tag]?.properties); + } + + return overrideBuckets.some((bucket) => { + if (!bucket) return false; + for (const key of Object.keys(bucket)) { + // Bare key like `name` — matches any property of that name. + if (propertyNames.includes(key)) return true; + // Regex form `/pattern/` — matches if any schema property name matches. + if (key.startsWith('/') && key.endsWith('/')) { + const regex = new RegExp(key.slice(1, -1)); + if (propertyNames.some((p) => regex.test(p))) return true; + } + } + return false; + }); +} + interface ResolveMockValueOptions { schema: MockSchema; operationId: string; @@ -122,6 +195,48 @@ export function resolveMockValue({ ? 'oneOf' : 'anyOf'; + // When schema-level faker factories are being emitted (`schemas: true`), + // delegate to `getMock()` instead of inlining the body. The factory + // already encodes the same fields, so this both deduplicates the output + // and lets a single source of truth drive shared mocks. + const canDelegate = + shouldDelegateToSchemaFactories(context) && + isComponentsSchemaRef(refPaths) && + !hasOverrideTouchingSchema( + schemaRef?.properties as Record | undefined, + mockOptions, + operationId, + tags, + ); + + if (canDelegate) { + const factoryName = `get${pascal(name)}Mock`; + imports.push({ + name: factoryName, + values: true, + schemaFactory: true, + }); + // For object-like refs the historical inline output is `{ ...body }` + // so the spread form keeps callers (combineSchemasMock, object + // properties) working without other changes. For everything else + // (scalars, arrays, nullables) emit the bare call. + const isObjectLike = + newSchema.type === 'object' || + !!newSchema.allOf || + !!newSchema.oneOf || + !!newSchema.anyOf; + const callValue = isObjectLike + ? `{ ...${factoryName}() }` + : `${factoryName}()`; + + return { + value: getNullable(callValue, Boolean(newSchema.nullable)), + imports, + name: newSchema.name, + type: getType(newSchema), + }; + } + const scalar = getMockScalar({ item: newSchema, mockOptions, diff --git a/tests/configs/mock.config.ts b/tests/configs/mock.config.ts index 247629945a..79eb606dcc 100644 --- a/tests/configs/mock.config.ts +++ b/tests/configs/mock.config.ts @@ -363,4 +363,21 @@ export default defineConfig({ target: '../specifications/petstore.yaml', }, }, + petstoreFakerSchemasAndOps: { + output: { + target: '../generated/mock/petstore-faker-schemas-and-ops/endpoints.ts', + schemas: '../generated/mock/petstore-faker-schemas-and-ops/model', + client: 'axios', + mock: { + generators: [ + { type: 'faker', schemas: true, operationResponses: true }, + ], + }, + clean: true, + formatter: 'prettier', + }, + input: { + target: '../specifications/petstore.yaml', + }, + }, }); From 26ce0ee66b8cca03033bdb8e65184c6ab88fcb1f Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 20:38:58 +0000 Subject: [PATCH 4/7] test: snapshotzzz --- .../endpoints.ts | 175 +++++++++++++++ .../model/cat.ts | 12 + .../model/catType.ts | 12 + .../model/createPetsBody.ts | 11 + .../model/createPetsParams.ts | 20 ++ .../model/createPetsSort.ts | 16 ++ .../model/dachshund.ts | 12 + .../model/dachshundBreed.ts | 13 ++ .../model/dog.ts | 19 ++ .../model/dogType.ts | 12 + .../model/error.ts | 11 + .../model/index.faker.ts | 95 ++++++++ .../model/index.ts | 26 +++ .../model/labradoodle.ts | 12 + .../model/labradoodleBreed.ts | 13 ++ .../model/listPetsParams.ts | 20 ++ .../model/listPetsSort.ts | 15 ++ .../model/pet.ts | 30 +++ .../model/petCallingCode.ts | 14 ++ .../model/petCountry.ts | 13 ++ .../model/petWithTag.ts | 12 + .../model/pets.ts | 9 + .../model/index.faker.ts | 205 +----------------- 23 files changed, 577 insertions(+), 200 deletions(-) create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/endpoints.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/cat.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/catType.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsBody.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsParams.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsSort.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshund.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshundBreed.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dog.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dogType.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/error.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.faker.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodle.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodleBreed.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsParams.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsSort.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pet.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCallingCode.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCountry.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petWithTag.ts create mode 100644 tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pets.ts diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/endpoints.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/endpoints.ts new file mode 100644 index 0000000000..f828733fc9 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/endpoints.ts @@ -0,0 +1,175 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import axios from 'axios'; +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; + +import type { + CreatePetsBody, + CreatePetsParams, + ListPetsParams, + Pet, + PetWithTag, + Pets, +} from './model'; + +import { faker } from '@faker-js/faker'; + +import { getCatMock, getDogMock, getPetMock } from './model/index.faker'; + +export const getSwaggerPetstore = (axiosInstance: AxiosInstance = axios) => { + /** + * @summary List all pets + */ + const listPets = ( + params: ListPetsParams, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/pets`, { + ...options, + params: { ...params, ...options?.params }, + }); + }; + + /** + * @summary Create a pet + */ + const createPets = ( + createPetsBody: CreatePetsBody, + params: CreatePetsParams, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.post(`/pets`, createPetsBody, { + ...options, + params: { ...params, ...options?.params }, + }); + }; + + /** + * @summary Info for a specific pet + */ + const showPetById = ( + petId: string, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/pets/${petId}`, options); + }; + + /** + * @summary Deletes a specific pet + */ + const deletePetById = ( + petId: string, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.delete(`/pets/${petId}`, options); + }; + + /** + * @summary health check + */ + const healthCheck = ( + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/health`, { + responseType: 'text', + ...options, + }); + }; + + /** + * @summary combinate nullable and $ref + */ + const showPetWithOwner = ( + petId: string, + options?: AxiosRequestConfig, + ): Promise> => { + return axiosInstance.get(`/pets/${petId}/owner`, options); + }; + + return { + listPets, + createPets, + showPetById, + deletePetById, + healthCheck, + showPetWithOwner, + }; +}; +export type ListPetsResult = AxiosResponse; +export type CreatePetsResult = AxiosResponse; +export type ShowPetByIdResult = AxiosResponse; +export type DeletePetByIdResult = AxiosResponse; +export type HealthCheckResult = AxiosResponse; +export type ShowPetWithOwnerResult = AxiosResponse; + +export const getListPetsResponseMock = (): Pets => + Array.from( + { length: faker.number.int({ min: 1, max: 10 }) }, + (_, i) => i + 1, + ).map(() => ({ ...getPetMock() })); + +export const getCreatePetsResponseMock = (): Pet => ({ + ...faker.helpers.arrayElement([{ ...getDogMock() }, { ...getCatMock() }]), + '@id': faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + id: faker.number.int(), + name: faker.string.alpha({ length: { min: 10, max: 20 } }), + tag: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33'] as const), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "People's Republic of China", + 'Uruguay', + ] as const), + undefined, + ]), +}); + +export const getShowPetByIdResponseMock = (): Pet => ({ + ...faker.helpers.arrayElement([{ ...getDogMock() }, { ...getCatMock() }]), + '@id': faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + id: faker.number.int(), + name: faker.string.alpha({ length: { min: 10, max: 20 } }), + tag: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33'] as const), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "People's Republic of China", + 'Uruguay', + ] as const), + undefined, + ]), +}); + +export const getHealthCheckResponseMock = (): string => faker.word.sample(); + +export const getShowPetWithOwnerResponseMock = ( + overrideResponse: Partial> = {}, +): PetWithTag => ({ + tag: faker.string.alpha({ length: { min: 10, max: 20 } }), + pet: faker.helpers.arrayElement([{ ...getPetMock() }, null]), + ...overrideResponse, +}); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/cat.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/cat.ts new file mode 100644 index 0000000000..dec05f4eaa --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/cat.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { CatType } from './catType'; + +export interface Cat { + petsRequested?: number; + type: CatType; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/catType.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/catType.ts new file mode 100644 index 0000000000..f8717f6194 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/catType.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type CatType = (typeof CatType)[keyof typeof CatType]; + +export const CatType = { + cat: 'cat', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsBody.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsBody.ts new file mode 100644 index 0000000000..5ff3a610e8 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsBody.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type CreatePetsBody = { + name: string; + tag: string; +}; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsParams.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsParams.ts new file mode 100644 index 0000000000..ea473ca17c --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsParams.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { CreatePetsSort } from './createPetsSort'; + +export type CreatePetsParams = { + /** + * How many items to return at one time (max 100) + */ + limit?: string; + /** + * Which property to sort by? + * Example: name sorts ASC while -name sorts DESC. + * + */ + sort: CreatePetsSort; +}; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsSort.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsSort.ts new file mode 100644 index 0000000000..0ab76c2466 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/createPetsSort.ts @@ -0,0 +1,16 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type CreatePetsSort = + (typeof CreatePetsSort)[keyof typeof CreatePetsSort]; + +export const CreatePetsSort = { + name: 'name', + '-name': '-name', + email: 'email', + '-email': '-email', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshund.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshund.ts new file mode 100644 index 0000000000..7dcea1dd44 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshund.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { DachshundBreed } from './dachshundBreed'; + +export interface Dachshund { + length: number; + breed: DachshundBreed; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshundBreed.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshundBreed.ts new file mode 100644 index 0000000000..13f7f2d23b --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dachshundBreed.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type DachshundBreed = + (typeof DachshundBreed)[keyof typeof DachshundBreed]; + +export const DachshundBreed = { + Dachshund: 'Dachshund', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dog.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dog.ts new file mode 100644 index 0000000000..df6194262b --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dog.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Dachshund } from './dachshund'; +import type { DogType } from './dogType'; +import type { Labradoodle } from './labradoodle'; + +export type Dog = + | (Labradoodle & { + barksPerMinute?: number; + type: DogType; + }) + | (Dachshund & { + barksPerMinute?: number; + type: DogType; + }); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dogType.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dogType.ts new file mode 100644 index 0000000000..801768166e --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/dogType.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type DogType = (typeof DogType)[keyof typeof DogType]; + +export const DogType = { + dog: 'dog', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/error.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/error.ts new file mode 100644 index 0000000000..0dd1b135d2 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/error.ts @@ -0,0 +1,11 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export interface Error { + code: number; + message: string; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.faker.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.faker.ts new file mode 100644 index 0000000000..635971a601 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.faker.ts @@ -0,0 +1,95 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import { faker } from '@faker-js/faker'; + +import type { + Cat, + Dachshund, + Dog, + Error, + Labradoodle, + Pet, + PetWithTag, + Pets, +} from '.'; + +export const getLabradoodleMock = ( + overrideResponse: Partial = {}, +): Labradoodle => ({ + cuteness: faker.number.int(), + breed: faker.helpers.arrayElement(['Labradoodle'] as const), + ...overrideResponse, +}); + +export const getDachshundMock = ( + overrideResponse: Partial = {}, +): Dachshund => ({ + length: faker.number.int(), + breed: faker.helpers.arrayElement(['Dachshund'] as const), + ...overrideResponse, +}); + +export const getDogMock = (): Dog => ({ + ...faker.helpers.arrayElement([ + { ...getLabradoodleMock() }, + { ...getDachshundMock() }, + ]), + barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['dog'] as const), +}); + +export const getCatMock = (overrideResponse: Partial = {}): Cat => ({ + petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), + type: faker.helpers.arrayElement(['cat'] as const), + ...overrideResponse, +}); + +export const getPetMock = (): Pet => ({ + ...faker.helpers.arrayElement([{ ...getDogMock() }, { ...getCatMock() }]), + '@id': faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + id: faker.number.int(), + name: faker.string.alpha({ length: { min: 10, max: 20 } }), + tag: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + email: faker.helpers.arrayElement([faker.internet.email(), undefined]), + callingCode: faker.helpers.arrayElement([ + faker.helpers.arrayElement(['+33', '+420', '+33'] as const), + undefined, + ]), + country: faker.helpers.arrayElement([ + faker.helpers.arrayElement([ + "People's Republic of China", + 'Uruguay', + ] as const), + undefined, + ]), +}); + +export const getPetsMock = (): Pets => + Array.from( + { length: faker.number.int({ min: 1, max: 10 }) }, + (_, i) => i + 1, + ).map(() => ({ ...getPetMock() })); + +export const getErrorMock = (overrideResponse: Partial = {}): Error => ({ + code: faker.number.int(), + message: faker.string.alpha({ length: { min: 10, max: 20 } }), + ...overrideResponse, +}); + +export const getPetWithTagMock = ( + overrideResponse: Partial = {}, +): PetWithTag => ({ + tag: faker.string.alpha({ length: { min: 10, max: 20 } }), + pet: faker.helpers.arrayElement([{ ...getPetMock() }, null]), + ...overrideResponse, +}); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.ts new file mode 100644 index 0000000000..757f76a9f6 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/index.ts @@ -0,0 +1,26 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export * from './cat'; +export * from './catType'; +export * from './createPetsBody'; +export * from './createPetsParams'; +export * from './createPetsSort'; +export * from './dachshund'; +export * from './dachshundBreed'; +export * from './dog'; +export * from './dogType'; +export * from './error'; +export * from './labradoodle'; +export * from './labradoodleBreed'; +export * from './listPetsParams'; +export * from './listPetsSort'; +export * from './pet'; +export * from './petCallingCode'; +export * from './petCountry'; +export * from './pets'; +export * from './petWithTag'; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodle.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodle.ts new file mode 100644 index 0000000000..2bd34368b1 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodle.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { LabradoodleBreed } from './labradoodleBreed'; + +export interface Labradoodle { + cuteness: number; + breed: LabradoodleBreed; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodleBreed.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodleBreed.ts new file mode 100644 index 0000000000..a59fd1baa8 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/labradoodleBreed.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type LabradoodleBreed = + (typeof LabradoodleBreed)[keyof typeof LabradoodleBreed]; + +export const LabradoodleBreed = { + Labradoodle: 'Labradoodle', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsParams.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsParams.ts new file mode 100644 index 0000000000..7d5a1998a2 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsParams.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { ListPetsSort } from './listPetsSort'; + +export type ListPetsParams = { + /** + * How many items to return at one time (max 100) + */ + limit?: string; + /** + * Which property to sort by? + * Example: name sorts ASC while -name sorts DESC. + * + */ + sort: ListPetsSort; +}; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsSort.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsSort.ts new file mode 100644 index 0000000000..580e537eb7 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/listPetsSort.ts @@ -0,0 +1,15 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type ListPetsSort = (typeof ListPetsSort)[keyof typeof ListPetsSort]; + +export const ListPetsSort = { + name: 'name', + '-name': '-name', + email: 'email', + '-email': '-email', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pet.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pet.ts new file mode 100644 index 0000000000..272899cbe9 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pet.ts @@ -0,0 +1,30 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Cat } from './cat'; +import type { Dog } from './dog'; +import type { PetCallingCode } from './petCallingCode'; +import type { PetCountry } from './petCountry'; + +export type Pet = + | (Dog & { + '@id'?: string; + id: number; + name: string; + tag?: string; + email?: string; + callingCode?: PetCallingCode; + country?: PetCountry; + }) + | (Cat & { + '@id'?: string; + id: number; + name: string; + tag?: string; + email?: string; + callingCode?: PetCallingCode; + country?: PetCountry; + }); diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCallingCode.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCallingCode.ts new file mode 100644 index 0000000000..2c69b445ac --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCallingCode.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type PetCallingCode = + (typeof PetCallingCode)[keyof typeof PetCallingCode]; + +export const PetCallingCode = { + '+33': '+33', + '+420': '+420', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCountry.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCountry.ts new file mode 100644 index 0000000000..34b590efc9 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petCountry.ts @@ -0,0 +1,13 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ + +export type PetCountry = (typeof PetCountry)[keyof typeof PetCountry]; + +export const PetCountry = { + "People's_Republic_of_China": "People's Republic of China", + Uruguay: 'Uruguay', +} as const; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petWithTag.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petWithTag.ts new file mode 100644 index 0000000000..2c16182546 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/petWithTag.ts @@ -0,0 +1,12 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Pet } from './pet'; + +export interface PetWithTag { + tag: string; + pet: Pet | null; +} diff --git a/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pets.ts b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pets.ts new file mode 100644 index 0000000000..d5b8145922 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas-and-ops/model/pets.ts @@ -0,0 +1,9 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Swagger Petstore + * OpenAPI spec version: 1.0.0 + */ +import type { Pet } from './pet'; + +export type Pets = Pet[]; diff --git a/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts b/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts index 1fda5ef0d9..635971a601 100644 --- a/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts +++ b/tests/__snapshots__/mock/petstore-faker-schemas/model/index.faker.ts @@ -17,144 +17,6 @@ import type { Pets, } from '.'; -export const getDogResponseLabradoodleMock = ( - overrideResponse: Partial = {}, -): Labradoodle => ({ - ...{ - cuteness: faker.number.int(), - breed: faker.helpers.arrayElement(['Labradoodle'] as const), - }, - ...overrideResponse, -}); -export const getDogResponseDachshundMock = ( - overrideResponse: Partial = {}, -): Dachshund => ({ - ...{ - length: faker.number.int(), - breed: faker.helpers.arrayElement(['Dachshund'] as const), - }, - ...overrideResponse, -}); -export const getPetResponseLabradoodleMock = ( - overrideResponse: Partial = {}, -): Labradoodle => ({ - ...{ - cuteness: faker.number.int(), - breed: faker.helpers.arrayElement(['Labradoodle'] as const), - }, - ...overrideResponse, -}); -export const getPetResponseDachshundMock = ( - overrideResponse: Partial = {}, -): Dachshund => ({ - ...{ - length: faker.number.int(), - breed: faker.helpers.arrayElement(['Dachshund'] as const), - }, - ...overrideResponse, -}); -export const getPetResponseDogMock = ( - overrideResponse: Omit, 'breed'> = {}, -): Dog => ({ - ...{ - ...faker.helpers.arrayElement([ - { ...getPetResponseLabradoodleMock() }, - { ...getPetResponseDachshundMock() }, - ]), - barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), - type: faker.helpers.arrayElement(['dog'] as const), - }, - ...overrideResponse, -}); -export const getPetResponseCatMock = ( - overrideResponse: Partial = {}, -): Cat => ({ - ...{ - petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), - type: faker.helpers.arrayElement(['cat'] as const), - }, - ...overrideResponse, -}); -export const getPetsResponseLabradoodleMock = ( - overrideResponse: Partial = {}, -): Labradoodle => ({ - ...{ - cuteness: faker.number.int(), - breed: faker.helpers.arrayElement(['Labradoodle'] as const), - }, - ...overrideResponse, -}); -export const getPetsResponseDachshundMock = ( - overrideResponse: Partial = {}, -): Dachshund => ({ - ...{ - length: faker.number.int(), - breed: faker.helpers.arrayElement(['Dachshund'] as const), - }, - ...overrideResponse, -}); -export const getPetsResponseDogMock = ( - overrideResponse: Omit, 'breed'> = {}, -): Dog => ({ - ...{ - ...faker.helpers.arrayElement([ - { ...getPetsResponseLabradoodleMock() }, - { ...getPetsResponseDachshundMock() }, - ]), - barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), - type: faker.helpers.arrayElement(['dog'] as const), - }, - ...overrideResponse, -}); -export const getPetsResponseCatMock = ( - overrideResponse: Partial = {}, -): Cat => ({ - ...{ - petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), - type: faker.helpers.arrayElement(['cat'] as const), - }, - ...overrideResponse, -}); -export const getPetWithTagResponseLabradoodleMock = ( - overrideResponse: Partial = {}, -): Labradoodle => ({ - ...{ - cuteness: faker.number.int(), - breed: faker.helpers.arrayElement(['Labradoodle'] as const), - }, - ...overrideResponse, -}); -export const getPetWithTagResponseDachshundMock = ( - overrideResponse: Partial = {}, -): Dachshund => ({ - ...{ - length: faker.number.int(), - breed: faker.helpers.arrayElement(['Dachshund'] as const), - }, - ...overrideResponse, -}); -export const getPetWithTagResponseDogMock = ( - overrideResponse: Omit, 'breed'> = {}, -): Dog => ({ - ...{ - ...faker.helpers.arrayElement([ - { ...getPetWithTagResponseLabradoodleMock() }, - { ...getPetWithTagResponseDachshundMock() }, - ]), - barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), - type: faker.helpers.arrayElement(['dog'] as const), - }, - ...overrideResponse, -}); -export const getPetWithTagResponseCatMock = ( - overrideResponse: Partial = {}, -): Cat => ({ - ...{ - petsRequested: faker.helpers.arrayElement([faker.number.int(), undefined]), - type: faker.helpers.arrayElement(['cat'] as const), - }, - ...overrideResponse, -}); export const getLabradoodleMock = ( overrideResponse: Partial = {}, ): Labradoodle => ({ @@ -173,8 +35,8 @@ export const getDachshundMock = ( export const getDogMock = (): Dog => ({ ...faker.helpers.arrayElement([ - { ...getDogResponseLabradoodleMock() }, - { ...getDogResponseDachshundMock() }, + { ...getLabradoodleMock() }, + { ...getDachshundMock() }, ]), barksPerMinute: faker.helpers.arrayElement([faker.number.int(), undefined]), type: faker.helpers.arrayElement(['dog'] as const), @@ -187,10 +49,7 @@ export const getCatMock = (overrideResponse: Partial = {}): Cat => ({ }); export const getPetMock = (): Pet => ({ - ...faker.helpers.arrayElement([ - { ...getPetResponseDogMock() }, - { ...getPetResponseCatMock() }, - ]), + ...faker.helpers.arrayElement([{ ...getDogMock() }, { ...getCatMock() }]), '@id': faker.helpers.arrayElement([ faker.string.alpha({ length: { min: 10, max: 20 } }), undefined, @@ -219,34 +78,7 @@ export const getPetsMock = (): Pets => Array.from( { length: faker.number.int({ min: 1, max: 10 }) }, (_, i) => i + 1, - ).map(() => ({ - ...faker.helpers.arrayElement([ - { ...getPetsResponseDogMock() }, - { ...getPetsResponseCatMock() }, - ]), - '@id': faker.helpers.arrayElement([ - faker.string.alpha({ length: { min: 10, max: 20 } }), - undefined, - ]), - id: faker.number.int(), - name: faker.string.alpha({ length: { min: 10, max: 20 } }), - tag: faker.helpers.arrayElement([ - faker.string.alpha({ length: { min: 10, max: 20 } }), - undefined, - ]), - email: faker.helpers.arrayElement([faker.internet.email(), undefined]), - callingCode: faker.helpers.arrayElement([ - faker.helpers.arrayElement(['+33', '+420', '+33'] as const), - undefined, - ]), - country: faker.helpers.arrayElement([ - faker.helpers.arrayElement([ - "People's Republic of China", - 'Uruguay', - ] as const), - undefined, - ]), - })); + ).map(() => ({ ...getPetMock() })); export const getErrorMock = (overrideResponse: Partial = {}): Error => ({ code: faker.number.int(), @@ -258,33 +90,6 @@ export const getPetWithTagMock = ( overrideResponse: Partial = {}, ): PetWithTag => ({ tag: faker.string.alpha({ length: { min: 10, max: 20 } }), - pet: { - ...faker.helpers.arrayElement([ - { ...getPetWithTagResponseDogMock() }, - { ...getPetWithTagResponseCatMock() }, - ]), - '@id': faker.helpers.arrayElement([ - faker.string.alpha({ length: { min: 10, max: 20 } }), - undefined, - ]), - id: faker.number.int(), - name: faker.string.alpha({ length: { min: 10, max: 20 } }), - tag: faker.helpers.arrayElement([ - faker.string.alpha({ length: { min: 10, max: 20 } }), - undefined, - ]), - email: faker.helpers.arrayElement([faker.internet.email(), undefined]), - callingCode: faker.helpers.arrayElement([ - faker.helpers.arrayElement(['+33', '+420', '+33'] as const), - undefined, - ]), - country: faker.helpers.arrayElement([ - faker.helpers.arrayElement([ - "People's Republic of China", - 'Uruguay', - ] as const), - undefined, - ]), - }, + pet: faker.helpers.arrayElement([{ ...getPetMock() }, null]), ...overrideResponse, }); From 5eb8c3204f8e54d4621c12e1c603b21b1fcc359a Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 14:08:59 -0700 Subject: [PATCH 5/7] fix(mock): emit value imports for runtime-used schemas in index.faker.ts When a schema referenced via \$ref is a string-typed enum, the faker generator emits a runtime call (Object.values(EnumName)) for its value. The consolidated schemas-faker file was importing such names as type-only, producing TS1361 ('X' cannot be used as a value because it was imported using 'import type'). Two fixes in the schemas-faker emission path: - generateFakerForSchemas: dedupe imports by name+alias with an "any value wins" merge so a value-position usage upgrades a type-only push to a value import. addDependency then emits a single `import { Foo }` line that works in both annotation and runtime positions. - writeFakerSchemaMocks: route value-flagged imports through the same schemaImportPath as type imports so they reach generateDependencyImports with the correct dependency string. Also drops self-import of `getMock` when the delegation logic references a factory defined in the same file. Adds a tags-split repro config exercising the failure mode. --- packages/mock/src/faker/index.ts | 41 +++++++++++++++---- packages/orval/src/write-specs.ts | 12 +++--- tests/configs/mock.config.ts | 18 ++++++++ .../faker-schemas-string-enum-ref.yaml | 41 +++++++++++++++++++ 4 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 tests/specifications/faker-schemas-string-enum-ref.yaml diff --git a/packages/mock/src/faker/index.ts b/packages/mock/src/faker/index.ts index 43e2a833a0..7eecb72215 100644 --- a/packages/mock/src/faker/index.ts +++ b/packages/mock/src/faker/index.ts @@ -98,6 +98,15 @@ export function generateFakerForSchemas( // sub-factory) once even when several schemas reference the same union arm. const splitMockImplementations: string[] = []; + // Names of the factories we're about to emit in this file. When the + // delegation logic in `resolveMockValue` produces a `getXMock()` call for + // a `components/schemas` ref, it pushes a `{ schemaFactory: true }` import + // — but if `X` is itself one of the schemas being generated here, the + // factory lives in this very file and must not be imported. + const localFactoryNames = new Set( + schemas.filter((s) => !!s.schema).map((s) => `get${pascal(s.name)}Mock`), + ); + const mockOptions = context.output.override.mock; for (const generatorSchema of schemas) { @@ -148,14 +157,32 @@ export function generateFakerForSchemas( } // De-duplicate imports by name+alias so the header doesn't list the same - // schema twice when multiple factories reference it. - const seen = new Set(); - const uniqueImports = allImports.filter((imp) => { + // schema twice when multiple factories reference it. "Any value wins": + // if the same name is pushed both as a type-only import and as a value + // import (e.g. an enum used both in an `as Foo` cast and an + // `Object.values(Foo)` call), we keep the value form. A plain + // `import { Foo }` works in both annotation and runtime positions, so + // emitting the value form avoids `TS1361: 'X' cannot be used as a value + // because it was imported using 'import type'`. + const mergedImports = new Map(); + for (const imp of allImports) { + // Drop self-references: `getMock` factories generated in this + // very file (pushed when delegation in `resolveMockValue` produced a + // local factory call). Without this we'd emit + // `import { getPetMock } from '.'` next to its own `export const`. + if (imp.schemaFactory && localFactoryNames.has(imp.name)) continue; + const key = `${imp.name}::${imp.alias ?? ''}`; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); + const existing = mergedImports.get(key); + if (!existing) { + mergedImports.set(key, imp); + continue; + } + if (!existing.values && imp.values) { + mergedImports.set(key, imp); + } + } + const uniqueImports = [...mergedImports.values()]; // Reference `options` so unused-parameter rules don't complain; future // schema-specific behavior (e.g. naming convention) will read from it. diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index afec38629d..e3eda9c953 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -206,13 +206,13 @@ async function writeFakerSchemaMocks( schemaImportPath = targetInfo ? `./${targetInfo.filename}` : undefined; } - // Force every schema-type import (`values: false`) onto the resolved - // schemas path so they're emitted as `import type { Pet } from '.'` - // instead of one file per schema. + // Route every schema-related import (both type-only and runtime value + // forms) onto the consolidated schemas path. Both `import { Foo }` and + // `import type { Foo }` come from the same module here, so we treat + // them uniformly — `generateDependencyImports` splits values vs types + // back out into separate `import` / `import type` lines as needed. const reroutedImports = imports.map((imp) => - imp.importPath || imp.values - ? imp - : { ...imp, importPath: schemaImportPath }, + imp.importPath ? imp : { ...imp, importPath: schemaImportPath }, ); // `generateDependencyImports` expects a list of `{ exports, dependency }` diff --git a/tests/configs/mock.config.ts b/tests/configs/mock.config.ts index 79eb606dcc..abad555258 100644 --- a/tests/configs/mock.config.ts +++ b/tests/configs/mock.config.ts @@ -380,4 +380,22 @@ export default defineConfig({ target: '../specifications/petstore.yaml', }, }, + stringEnumRefFakerSchemasTagsSplit: { + output: { + workspace: '../generated/mock/string-enum-ref-faker-schemas-tags-split/', + target: './index.ts', + mode: 'tags-split', + client: 'react-query', + mock: { + generators: [ + { type: 'faker', schemas: true, operationResponses: true }, + ], + }, + clean: true, + formatter: 'prettier', + }, + input: { + target: '../specifications/faker-schemas-string-enum-ref.yaml', + }, + }, }); diff --git a/tests/specifications/faker-schemas-string-enum-ref.yaml b/tests/specifications/faker-schemas-string-enum-ref.yaml new file mode 100644 index 0000000000..5eed99e382 --- /dev/null +++ b/tests/specifications/faker-schemas-string-enum-ref.yaml @@ -0,0 +1,41 @@ +openapi: 3.0.3 +info: + title: Faker schemas string enum ref + description: | + Reproduces the case where a parent schema references a string-typed enum + component via $ref, so the generated faker mock calls Object.values(EnumName) + at runtime. Without the fix, the consolidated index.faker.ts imports the + enum as `import type { ... }`, causing TS1361. + version: 1.0.0 +paths: + /display: + get: + operationId: getDisplay + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DisplayValueDto' +components: + schemas: + DisplayColor: + description: A text color that can be used for display purposes. + type: string + enum: + - TEXT_01 + - TEXT_04 + - TEXT_CURRENCY_GAIN + DisplayValueDto: + type: object + required: + - value + properties: + color: + allOf: + - $ref: '#/components/schemas/DisplayColor' + value: + type: string + boldPart: + type: string From 08015976c153d1787739109ee21847c71136f747 Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 21:30:06 +0000 Subject: [PATCH 6/7] test: snapshots --- .../default/default.faker.ts | 30 +++ .../default/default.ts | 173 ++++++++++++++++++ .../index.schemas.ts | 27 +++ .../index.ts | 2 + .../schemas.faker.ts | 37 ++++ 5 files changed, 269 insertions(+) create mode 100644 tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.faker.ts create mode 100644 tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.ts create mode 100644 tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.schemas.ts create mode 100644 tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.ts create mode 100644 tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/schemas.faker.ts diff --git a/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.faker.ts b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.faker.ts new file mode 100644 index 0000000000..3825c32f7d --- /dev/null +++ b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.faker.ts @@ -0,0 +1,30 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Faker schemas string enum ref + * Reproduces the case where a parent schema references a string-typed enum + * component via $ref, so the generated faker mock calls Object.values(EnumName) + * at runtime. Without the fix, the consolidated index.faker.ts imports the + * enum as `import type { ... }`, causing TS1361. + * + * OpenAPI spec version: 1.0.0 + */ +import { faker } from '@faker-js/faker'; + +import { DisplayColor } from '../index.schemas'; +import type { DisplayValueDto } from '../index.schemas'; + +export const getGetDisplayResponseMock = ( + overrideResponse: Partial> = {}, +): DisplayValueDto => ({ + color: faker.helpers.arrayElement([ + faker.helpers.arrayElement(Object.values(DisplayColor)), + undefined, + ]), + value: faker.string.alpha({ length: { min: 10, max: 20 } }), + boldPart: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + ...overrideResponse, +}); diff --git a/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.ts b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.ts new file mode 100644 index 0000000000..95986ef2a5 --- /dev/null +++ b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/default/default.ts @@ -0,0 +1,173 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Faker schemas string enum ref + * Reproduces the case where a parent schema references a string-typed enum + * component via $ref, so the generated faker mock calls Object.values(EnumName) + * at runtime. Without the fix, the consolidated index.faker.ts imports the + * enum as `import type { ... }`, causing TS1361. + * + * OpenAPI spec version: 1.0.0 + */ +import { useQuery } from '@tanstack/react-query'; +import type { + DataTag, + DefinedInitialDataOptions, + DefinedUseQueryResult, + QueryClient, + QueryFunction, + QueryKey, + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, +} from '@tanstack/react-query'; + +import type { DisplayValueDto } from '../index.schemas'; + +export type getDisplayResponse200 = { + data: DisplayValueDto; + status: 200; +}; + +export type getDisplayResponseSuccess = getDisplayResponse200 & { + headers: Headers; +}; +export type getDisplayResponse = getDisplayResponseSuccess; + +export const getGetDisplayUrl = () => { + return `/display`; +}; + +export const getDisplay = async ( + options?: RequestInit, +): Promise => { + const res = await fetch(getGetDisplayUrl(), { + ...options, + method: 'GET', + }); + + const body = [204, 205, 304].includes(res.status) ? null : await res.text(); + + const data: getDisplayResponse['data'] = body ? JSON.parse(body) : {}; + return { + data, + status: res.status, + headers: res.headers, + } as getDisplayResponse; +}; + +export const getGetDisplayQueryKey = () => { + return [`/display`] as const; +}; + +export const getGetDisplayQueryOptions = < + TData = Awaited>, + TError = unknown, +>(options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + fetch?: RequestInit; +}) => { + const { query: queryOptions, fetch: fetchOptions } = options ?? {}; + + const queryKey = queryOptions?.queryKey ?? getGetDisplayQueryKey(); + + const queryFn: QueryFunction>> = ({ + signal, + }) => getDisplay({ signal, ...fetchOptions }); + + return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< + Awaited>, + TError, + TData + > & { queryKey: DataTag }; +}; + +export type GetDisplayQueryResult = NonNullable< + Awaited> +>; +export type GetDisplayQueryError = unknown; + +export function useGetDisplay< + TData = Awaited>, + TError = unknown, +>( + options: { + query: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< + DefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + 'initialData' + >; + fetch?: RequestInit; + }, + queryClient?: QueryClient, +): DefinedUseQueryResult & { + queryKey: DataTag; +}; +export function useGetDisplay< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + > & + Pick< + UndefinedInitialDataOptions< + Awaited>, + TError, + Awaited> + >, + 'initialData' + >; + fetch?: RequestInit; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; +export function useGetDisplay< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + fetch?: RequestInit; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +}; + +export function useGetDisplay< + TData = Awaited>, + TError = unknown, +>( + options?: { + query?: Partial< + UseQueryOptions>, TError, TData> + >; + fetch?: RequestInit; + }, + queryClient?: QueryClient, +): UseQueryResult & { + queryKey: DataTag; +} { + const queryOptions = getGetDisplayQueryOptions(options); + + const query = useQuery(queryOptions, queryClient) as UseQueryResult< + TData, + TError + > & { queryKey: DataTag }; + + return { ...query, queryKey: queryOptions.queryKey }; +} diff --git a/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.schemas.ts b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.schemas.ts new file mode 100644 index 0000000000..77b9d04472 --- /dev/null +++ b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.schemas.ts @@ -0,0 +1,27 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Faker schemas string enum ref + * Reproduces the case where a parent schema references a string-typed enum + * component via $ref, so the generated faker mock calls Object.values(EnumName) + * at runtime. Without the fix, the consolidated index.faker.ts imports the + * enum as `import type { ... }`, causing TS1361. + * + * OpenAPI spec version: 1.0.0 + */ +/** + * A text color that can be used for display purposes. + */ +export type DisplayColor = (typeof DisplayColor)[keyof typeof DisplayColor]; + +export const DisplayColor = { + TEXT_01: 'TEXT_01', + TEXT_04: 'TEXT_04', + TEXT_CURRENCY_GAIN: 'TEXT_CURRENCY_GAIN', +} as const; + +export interface DisplayValueDto { + color?: DisplayColor; + value: string; + boldPart?: string; +} diff --git a/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.ts b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.ts new file mode 100644 index 0000000000..24dc94a6bd --- /dev/null +++ b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/index.ts @@ -0,0 +1,2 @@ +export * from './default/default'; +export * from './index.schemas'; diff --git a/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/schemas.faker.ts b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/schemas.faker.ts new file mode 100644 index 0000000000..a3da346be9 --- /dev/null +++ b/tests/__snapshots__/mock/string-enum-ref-faker-schemas-tags-split/schemas.faker.ts @@ -0,0 +1,37 @@ +/** + * Generated by orval v8.12.3 🍺 + * Do not edit manually. + * Faker schemas string enum ref + * Reproduces the case where a parent schema references a string-typed enum + * component via $ref, so the generated faker mock calls Object.values(EnumName) + * at runtime. Without the fix, the consolidated index.faker.ts imports the + * enum as `import type { ... }`, causing TS1361. + * + * OpenAPI spec version: 1.0.0 + */ +import { faker } from '@faker-js/faker'; + +import { DisplayColor } from './index'; +import type { DisplayValueDto } from './index'; + +export const getDisplayColorMock = (): DisplayColor => + faker.helpers.arrayElement([ + 'TEXT_01', + 'TEXT_04', + 'TEXT_CURRENCY_GAIN', + ] as const); + +export const getDisplayValueDtoMock = ( + overrideResponse: Partial = {}, +): DisplayValueDto => ({ + color: faker.helpers.arrayElement([ + faker.helpers.arrayElement(Object.values(DisplayColor)), + undefined, + ]), + value: faker.string.alpha({ length: { min: 10, max: 20 } }), + boldPart: faker.helpers.arrayElement([ + faker.string.alpha({ length: { min: 10, max: 20 } }), + undefined, + ]), + ...overrideResponse, +}); From 4177de8dde0785d3eeba65dc4a49ea046cc414c3 Mon Sep 17 00:00:00 2001 From: Jacob Kelley Date: Fri, 22 May 2026 15:05:05 -0700 Subject: [PATCH 7/7] fix(mock): address CodeRabbit feedback on faker schemas PR - Append getImportExtension to the consolidated index.faker dependency so NodeNext/Node16 resolution gets the local-file extension. Also fix the same omission on the fallback schemaImportPath when output.schemas isn't configured. - Reuse resolveMockOverride in hasOverrideTouchingSchema so #.path-form overrides (e.g. '#.color.value') block delegation, mirroring the matching rules the rest of the faker pipeline already honors. - Tighten the fakerEntry lookup in writeFakerSchemaMocks and shouldDelegateToSchemaFactories to find the opted-in faker entry (schemas: true) directly rather than the first faker entry, so the resolver and writer stay aligned even if the duplicate-type guard in normalizeMocksOption ever loosens. - Document the new schemas and operationResponses options in the Faker guide, including a Schema Factories subsection and table rows. --- docs/content/docs/guides/faker.mdx | 43 ++++++++++++++++++- .../writers/generate-imports-for-builder.ts | 11 ++++- packages/mock/src/faker/resolvers/value.ts | 43 +++++++++++-------- packages/orval/src/write-specs.ts | 20 +++++++-- 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/docs/content/docs/guides/faker.mdx b/docs/content/docs/guides/faker.mdx index 9270259ab2..909d9d9825 100644 --- a/docs/content/docs/guides/faker.mdx +++ b/docs/content/docs/guides/faker.mdx @@ -45,7 +45,7 @@ The Faker output is written to `.faker.ts` and only depends on `@faker ### Response Factories -For each operation, Orval emits a `getResponseMock` factory that returns a fully-populated response value: +By default, Orval emits a `getResponseMock` factory per operation that returns a fully-populated response value. Disable this with `operationResponses: false` (typically when combined with `schemas: true` — see below). ```ts import { faker } from '@faker-js/faker'; @@ -67,6 +67,45 @@ const pet = getShowPetByIdResponseMock({ name: 'Buddy' }); // => { id: 7272122785202176, name: "Buddy", tag: "..." } ``` +### Schema Factories + +Set `schemas: true` to emit a `getMock()` factory per entry under `components/schemas`. Factories are written to a single consolidated file at `/index.faker.ts` so they can be imported uniformly and reference each other directly: + +```ts title="orval.config.ts" +mock: { + generators: [ + { + type: 'faker', + schemas: true, // emit components/schemas factories + operationResponses: true, // also emit per-operation response factories (default) + }, + ], +} +``` + +```ts title="src/api/model/index.faker.ts" +import { faker } from '@faker-js/faker'; +import type { Pet } from '.'; + +export const getPetMock = (overrideResponse: Partial = {}): Pet => ({ + id: faker.number.int(), + name: faker.string.alpha(20), + ...overrideResponse, +}); +``` + +When both options are enabled, the per-operation `getResponseMock` factories **delegate** to the schema-level factories instead of re-inlining the schema body: + +```ts +import { getPetMock } from './model/index.faker'; + +export const getShowPetByIdResponseMock = (): Pet => ({ ...getPetMock() }); +``` + +If an operation- or tag-level `override.mock` rule targets a property of a referenced schema (by name, regex, or exact `#.path`), that single ref falls back to inlining so the override actually applies. + +Requires `output.schemas` to be configured (the consolidated file is written into that directory). + ## Options Set faker-specific options on the generator entry: @@ -91,6 +130,8 @@ mock: { | `generateEachHttpStatus` | `boolean` | `false` | Emit a separate factory per HTTP status code defined in the spec (not just the success response). | | `locale` | `keyof typeof allLocales` | — | Faker locale. Switches the import to `@faker-js/faker/locale/` (e.g. `'en_GB'`, `'fr'`, `'ja'`). | | `preferredContentType` | `string` | — | When an operation has multiple response content types, mock the one matching this MIME type. | +| `schemas` | `boolean` | `false` | Emit a consolidated `getMock` factory per `components/schemas` entry into `/index.faker.ts`. See [Schema Factories](#schema-factories). | +| `operationResponses` | `boolean` | `true` | Emit per-operation `getResponseMock` factories. Set to `false` (typically with `schemas: true`) to skip operation-level factories. | ## Customizing Mock Values diff --git a/packages/core/src/writers/generate-imports-for-builder.ts b/packages/core/src/writers/generate-imports-for-builder.ts index aa6ac57284..52f7d5a488 100644 --- a/packages/core/src/writers/generate-imports-for-builder.ts +++ b/packages/core/src/writers/generate-imports-for-builder.ts @@ -18,7 +18,13 @@ export function generateImportsForBuilder( // Schema-factory imports (`getPetMock` and friends) always resolve to the // consolidated `/index.faker` file emitted by the faker // schemas option. They bypass the per-schema convention naming below. + // Append `getImportExtension` so NodeNext / Node16 module resolution + // gets the required local-file extension (e.g. `.js`). const schemaFactoryImports = imports.filter((i) => i.schemaFactory); + const schemaFactoryImportExtension = getImportExtension( + output.fileExtension, + output.tsconfig, + ); const schemaFactoryDeps: GeneratorDependency[] = schemaFactoryImports.length > 0 ? [ @@ -27,7 +33,10 @@ export function generateImportsForBuilder( schemaFactoryImports, (entry) => `${entry.name}|${entry.alias ?? ''}`, ), - dependency: upath.joinSafe(relativeSchemasPath, 'index.faker'), + dependency: upath.joinSafe( + relativeSchemasPath, + `index.faker${schemaFactoryImportExtension}`, + ), }, ] : []; diff --git a/packages/mock/src/faker/resolvers/value.ts b/packages/mock/src/faker/resolvers/value.ts index 4a2420e89e..3c1e3f0df7 100644 --- a/packages/mock/src/faker/resolvers/value.ts +++ b/packages/mock/src/faker/resolvers/value.ts @@ -66,15 +66,15 @@ export function getNullable(value: string, nullable?: boolean) { */ function shouldDelegateToSchemaFactories(context: ContextSpec): boolean { if (!context.output.schemas) return false; - for (const g of context.output.mock.generators) { - if (isFunction(g)) continue; - if (g.type === OutputMockType.FAKER && g.schemas === true) { - // `operationResponses` defaults to true; we only need to confirm - // schemas are being emitted at all. - return true; - } - } - return false; + // The duplicate-type guard in `normalizeMocksOption` (see + // `packages/orval/src/utils/options.ts`) ensures at most one faker entry + // exists per output, so finding the first one that opted into schemas is + // unambiguous today and remains correct if that guard ever loosens. + const fakerEntry = context.output.mock.generators.find( + (g) => + !isFunction(g) && g.type === OutputMockType.FAKER && g.schemas === true, + ); + return !!fakerEntry; } /** @@ -95,12 +95,19 @@ function isComponentsSchemaRef(refPaths: string[] | undefined): boolean { * property declared on the referenced schema. In that case we must inline * the schema body so the override actually applies; the shared * `getMock` factory has no knowledge of operation-scoped overrides. + * + * Reuses `resolveMockOverride` so the same matching rules apply as for + * regular property mocks — bare name, regex (`/.../`), and exact-path + * (`#.foo.bar`). The parent's `path` (where the `$ref` appears in the + * surrounding schema) gets composed into each synthetic property item so + * exact-path overrides like `#.color.value` resolve correctly. */ function hasOverrideTouchingSchema( schemaProperties: Record | undefined, mockOptions: MockOptions | undefined, operationId: string, tags: string[], + parentPath: string | undefined, ): boolean { if (!schemaProperties) return false; const propertyNames = Object.keys(schemaProperties); @@ -115,16 +122,13 @@ function hasOverrideTouchingSchema( return overrideBuckets.some((bucket) => { if (!bucket) return false; - for (const key of Object.keys(bucket)) { - // Bare key like `name` — matches any property of that name. - if (propertyNames.includes(key)) return true; - // Regex form `/pattern/` — matches if any schema property name matches. - if (key.startsWith('/') && key.endsWith('/')) { - const regex = new RegExp(key.slice(1, -1)); - if (propertyNames.some((p) => regex.test(p))) return true; - } - } - return false; + return propertyNames.some((propertyName) => { + const synthetic = { + name: propertyName, + path: parentPath ? `${parentPath}.${propertyName}` : propertyName, + } as OpenApiSchemaObject & { name: string; path?: string }; + return !!resolveMockOverride(bucket, synthetic); + }); }); } @@ -207,6 +211,7 @@ export function resolveMockValue({ mockOptions, operationId, tags, + schemaReference.path, ); if (canDelegate) { diff --git a/packages/orval/src/write-specs.ts b/packages/orval/src/write-specs.ts index e3eda9c953..977c16fd52 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -8,6 +8,7 @@ import { fixRegularSchemaImports, generateDependencyImports, getFileInfo, + getImportExtension, getMockFileExtensionByTypeName, isFunction, isObject, @@ -154,11 +155,16 @@ async function writeFakerSchemaMocks( header: string, ): Promise { const { output } = options; + // Pick the opted-in faker entry directly. The duplicate-type guard in + // `normalizeMocksOption` keeps this unambiguous today, and finding by + // `schemas: true` also makes the intent obvious if that guard ever + // loosens (so a `faker({ schemas: false })` entry can't accidentally + // win the lookup). const fakerEntry = output.mock.generators.find( (g): g is FakerMockOptions => - !isFunction(g) && g.type === OutputMockType.FAKER, + !isFunction(g) && g.type === OutputMockType.FAKER && g.schemas === true, ); - if (fakerEntry?.schemas !== true) { + if (!fakerEntry) { return undefined; } @@ -202,8 +208,14 @@ async function writeFakerSchemaMocks( filePath = path.join(dir, `schemas.faker${fileExtension}`); // Without a dedicated schemas dir we have no separate types file to // import from; the factories will reference inline types declared in - // the main output target. - schemaImportPath = targetInfo ? `./${targetInfo.filename}` : undefined; + // the main output target. Append `getImportExtension` so NodeNext / + // Node16 module resolution gets the required local-file extension. + schemaImportPath = targetInfo + ? `./${targetInfo.filename}${getImportExtension( + fileExtension, + output.tsconfig, + )}` + : undefined; } // Route every schema-related import (both type-only and runtime value