diff --git a/docs/content/docs/guides/faker.mdx b/docs/content/docs/guides/faker.mdx new file mode 100644 index 0000000000..909d9d9825 --- /dev/null +++ b/docs/content/docs/guides/faker.mdx @@ -0,0 +1,248 @@ +--- +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 + +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'; + +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: "..." } +``` + +### 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: + +```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. | +| `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 + +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..886b84890f 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; @@ -1134,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..52f7d5a488 100644 --- a/packages/core/src/writers/generate-imports-for-builder.ts +++ b/packages/core/src/writers/generate-imports-for-builder.ts @@ -15,6 +15,36 @@ 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. + // 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 + ? [ + { + exports: uniqueBy( + schemaFactoryImports, + (entry) => `${entry.name}|${entry.alias ?? ''}`, + ), + dependency: upath.joinSafe( + relativeSchemasPath, + `index.faker${schemaFactoryImportExtension}`, + ), + }, + ] + : []; + + // 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 +110,5 @@ export function generateImportsForBuilder( }; }); - return [...schemaImports, ...otherImports]; + return [...schemaImports, ...schemaFactoryDeps, ...otherImports]; } 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..7eecb72215 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,129 @@ 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[] = []; + + // 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) { + 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. "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 ?? ''}`; + 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. + 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/faker/resolvers/value.ts b/packages/mock/src/faker/resolvers/value.ts index 65039f4d35..3c1e3f0df7 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,81 @@ 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; + // 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; +} + +/** + * 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. + * + * 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); + 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; + return propertyNames.some((propertyName) => { + const synthetic = { + name: propertyName, + path: parentPath ? `${parentPath}.${propertyName}` : propertyName, + } as OpenApiSchemaObject & { name: string; path?: string }; + return !!resolveMockOverride(bucket, synthetic); + }); + }); +} + interface ResolveMockValueOptions { schema: MockSchema; operationId: string; @@ -122,6 +199,49 @@ 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, + schemaReference.path, + ); + + 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/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..977c16fd52 100644 --- a/packages/orval/src/write-specs.ts +++ b/packages/orval/src/write-specs.ts @@ -1,21 +1,28 @@ import path from 'node:path'; import { + type ContextSpec, createSuccessMessage, + type FakerMockOptions, fixCrossDirectoryImports, fixRegularSchemaImports, + generateDependencyImports, getFileInfo, + getImportExtension, getMockFileExtensionByTypeName, + isFunction, isObject, isString, jsDoc, logWarning, type NormalizedOptions, type OpenApiInfoObject, + OutputMockType, OutputMode, splitSchemasByType, SupportedFormatter, upath, + writeGeneratedFile, writeSchemas, writeSingleMode, type WriteSpecBuilder, @@ -23,6 +30,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 +141,129 @@ 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; + // 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 && g.schemas === true, + ); + if (!fakerEntry) { + 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. 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 + // 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 : { ...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 +464,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 +579,7 @@ export async function writeSpecs( ).dirname, ] : []), + ...(fakerSchemaPath ? [fakerSchemaPath] : []), ...(output.operationSchemas ? [getFileInfo(output.operationSchemas).dirname] : []), 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/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..635971a601 --- /dev/null +++ b/tests/__snapshots__/mock/petstore-faker-schemas/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/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/__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, +}); diff --git a/tests/configs/mock.config.ts b/tests/configs/mock.config.ts index d0e654dc22..abad555258 100644 --- a/tests/configs/mock.config.ts +++ b/tests/configs/mock.config.ts @@ -346,4 +346,56 @@ 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', + }, + }, + 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', + }, + }, + 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