Skip to content

Commit 9187ccc

Browse files
committed
implement m3
1 parent 723ac4e commit 9187ccc

12 files changed

Lines changed: 425 additions & 49 deletions

File tree

packages/openapi-ts-tests/faker/v1/__snapshots__/3.1.x/faker-m1/@faker-js/faker.gen.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ export const fakePet = (options?: Options): Pet => ({
5858
...!resolveCondition(options?.includeOptional ?? true, ensureFaker(options)) ? {} : { tag: fakeTag(options) }
5959
});
6060

61-
export const fakePetOrTag = (options?: Options): PetOrTag => fakePet(options);
61+
export const fakePetOrTag = (options?: Options): PetOrTag => ensureFaker(options).helpers.arrayElement([() => fakePet(options), () => fakeTag(options)])();
6262

63-
export const fakeStringOrNumber = (options?: Options): StringOrNumber => ensureFaker(options).string.sample();
63+
export const fakeStringOrNumber = (options?: Options): StringOrNumber => ensureFaker(options).helpers.arrayElement([() => ensureFaker(options).string.sample(), () => ensureFaker(options).number.float()])();
6464

6565
export const fakePetList = (options?: Options): PetList => ensureFaker(options).helpers.multiple(() => fakePet(options));
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
import { faker, type Faker } from '@faker-js/faker';
4+
5+
import type { Animal, Cat, Circle, Dog, NullablePetOrTag, Pet, PetOrTag, PetWithOwner, Shape, Square, StringOrNumber, Tag } from '../types.gen';
6+
7+
export type Options = {
8+
faker?: Faker;
9+
/**
10+
* Whether to include optional properties.
11+
* Provide a number between 0 and 1 to randomly include based on that probability.
12+
* @default true
13+
*/
14+
includeOptional?: boolean | number;
15+
/**
16+
* Whether to use schema default values instead of generating fake data.
17+
* Provide a number between 0 and 1 to randomly use defaults based on that probability.
18+
* @default false
19+
*/
20+
useDefault?: boolean | number;
21+
};
22+
23+
const ensureFaker = (options?: Options): Faker => options?.faker ?? faker;
24+
25+
export const fakePet = (options?: Options): Pet => ({
26+
name: ensureFaker(options).string.sample(),
27+
age: ensureFaker(options).number.int()
28+
});
29+
30+
export const fakeTag = (options?: Options): Tag => ({
31+
id: ensureFaker(options).number.int(),
32+
label: ensureFaker(options).string.sample()
33+
});
34+
35+
export const fakePetOrTag = (options?: Options): PetOrTag => ensureFaker(options).helpers.arrayElement([() => fakePet(options), () => fakeTag(options)])();
36+
37+
export const fakeStringOrNumber = (options?: Options): StringOrNumber => ensureFaker(options).helpers.arrayElement([() => ensureFaker(options).string.sample(), () => ensureFaker(options).number.float()])();
38+
39+
export const fakeNullablePetOrTag = (options?: Options): NullablePetOrTag => ensureFaker(options).helpers.arrayElement([
40+
() => fakePet(options),
41+
() => fakeTag(options),
42+
() => null
43+
])();
44+
45+
export const fakePetWithOwner = (options?: Options): PetWithOwner => ({
46+
...fakePet(options),
47+
...{
48+
owner: ensureFaker(options).string.sample()
49+
}
50+
});
51+
52+
export const fakeCircle = (options?: Options): Circle => ({
53+
kind: ensureFaker(options).string.sample(),
54+
radius: ensureFaker(options).number.float()
55+
});
56+
57+
export const fakeSquare = (options?: Options): Square => ({
58+
kind: ensureFaker(options).string.sample(),
59+
side: ensureFaker(options).number.float()
60+
});
61+
62+
export const fakeShape = (options?: Options): Shape => ensureFaker(options).helpers.arrayElement([() => ({
63+
...fakeCircle(options),
64+
kind: 'Circle' as const
65+
}), () => ({
66+
...fakeSquare(options),
67+
kind: 'Square' as const
68+
})])();
69+
70+
export const fakeDog = (options?: Options): Dog => ({
71+
type: ensureFaker(options).string.sample(),
72+
bark: ensureFaker(options).datatype.boolean()
73+
});
74+
75+
export const fakeCat = (options?: Options): Cat => ({
76+
type: ensureFaker(options).string.sample(),
77+
purr: ensureFaker(options).datatype.boolean()
78+
});
79+
80+
export const fakeAnimal = (options?: Options): Animal => ensureFaker(options).helpers.arrayElement([() => ({
81+
...fakeDog(options),
82+
type: 'dog' as const
83+
}), () => ({
84+
...fakeCat(options),
85+
type: 'cat' as const
86+
})])();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type { Animal, Cat, Circle, ClientOptions, Dog, NullablePetOrTag, Pet, PetOrTag, PetWithOwner, Shape, Square, StringOrNumber, Tag } from './types.gen';
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type ClientOptions = {
4+
baseUrl: `${string}://${string}` | (string & {});
5+
};
6+
7+
export type Pet = {
8+
name: string;
9+
age: number;
10+
};
11+
12+
export type Tag = {
13+
id: number;
14+
label: string;
15+
};
16+
17+
export type PetOrTag = Pet | Tag;
18+
19+
export type StringOrNumber = string | number;
20+
21+
export type NullablePetOrTag = Pet | Tag | null;
22+
23+
export type PetWithOwner = Pet & {
24+
owner: string;
25+
};
26+
27+
export type Circle = {
28+
kind: string;
29+
radius: number;
30+
};
31+
32+
export type Square = {
33+
kind: string;
34+
side: number;
35+
};
36+
37+
export type Shape = ({
38+
kind: 'Circle';
39+
} & Circle) | ({
40+
kind: 'Square';
41+
} & Square);
42+
43+
export type Dog = {
44+
type: string;
45+
bark: boolean;
46+
};
47+
48+
export type Cat = {
49+
type: string;
50+
purr: boolean;
51+
};
52+
53+
export type Animal = ({
54+
type: 'dog';
55+
} & Dog) | ({
56+
type: 'cat';
57+
} & Cat);

packages/openapi-ts-tests/faker/v1/test/3.1.x.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ describe(`OpenAPI ${version}`, () => {
4747
description:
4848
'handles string formats, numeric constraints, array constraints, and default values',
4949
},
50+
{
51+
config: createConfig({
52+
input: 'faker-m3.yaml',
53+
output: 'faker-m3',
54+
plugins: ['@hey-api/typescript', '@faker-js/faker'],
55+
}),
56+
description: 'handles union randomization, allOf intersections, and discriminated unions',
57+
},
5058
];
5159

5260
it.each(scenarios)('$description', async ({ config }) => {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { $ } from '../../../../ts-dsl';
2-
import type { FakerWalkerContext } from './types';
2+
import type { EmitTracking, FakerWalkerContext } from './types';
33

44
/**
55
* Creates the shared walker context with the faker accessor expression.
66
*
77
* The accessor is `ensureFaker(options)` — the helper resolves
88
* `options?.faker ?? faker` so each call site stays clean.
99
*/
10-
export function createFakerWalkerContext(): FakerWalkerContext {
10+
export function createFakerWalkerContext(tracking: EmitTracking): FakerWalkerContext {
1111
const optionsId = $('options');
1212

1313
// ensureFaker(options)
@@ -16,5 +16,6 @@ export function createFakerWalkerContext(): FakerWalkerContext {
1616
return {
1717
fakerAccessor,
1818
optionsId,
19+
tracking,
1920
};
2021
}

packages/openapi-ts/src/plugins/@faker-js/faker/shared/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,14 @@ export interface FakerResult {
1818
usesFaker: boolean;
1919
}
2020

21+
/**
22+
* Mutable tracking of which helpers are actually referenced in the generated output.
23+
* Used to conditionally emit helper declarations only when needed.
24+
*/
25+
export interface EmitTracking {
26+
needsResolveCondition: boolean;
27+
}
28+
2129
/**
2230
* Context carried through the walker for building faker expressions.
2331
*/
@@ -31,4 +39,6 @@ export interface FakerWalkerContext {
3139
* The `options` identifier, used when calling referenced factories.
3240
*/
3341
optionsId: ReturnType<typeof $.expr>;
42+
/** Shared tracking object for conditional helper emission. */
43+
tracking: EmitTracking;
3444
}

packages/openapi-ts/src/plugins/@faker-js/faker/v1/plugin.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { pathToJsonPointer } from '@hey-api/shared';
22

33
import { $ } from '../../../../ts-dsl';
4+
import type { EmitTracking } from '../shared/types';
45
import type { FakerJsFakerPlugin } from '../types';
56
import { createProcessor } from './processor';
67

@@ -43,34 +44,8 @@ export const handlerV1: FakerJsFakerPlugin['Handler'] = ({ plugin }) => {
4344
);
4445
plugin.node($.type.alias(optionsSymbol).export().type(optionsType));
4546

46-
// Emit helper: const resolveCondition = (condition: boolean | number, faker: Faker): boolean =>
47-
// condition === true || typeof condition === 'number' && faker.datatype.boolean({ probability: condition })
48-
const conditionParamType = $.type.or($.type('boolean'), $.type('number'));
49-
const resolveConditionFn = $.func()
50-
.arrow()
51-
.param('condition', (p) => p.type(conditionParamType))
52-
.param('faker', (p) => p.type($.type(fakerTypeSymbol)))
53-
.returns('boolean')
54-
.do(
55-
$.return(
56-
$.binary(
57-
$('condition').eq($.literal(true)),
58-
'||',
59-
$(
60-
$.binary(
61-
$('condition').typeofExpr().eq($.literal('number')),
62-
'&&',
63-
$('faker')
64-
.attr('datatype')
65-
.attr('boolean')
66-
.call($.object().prop('probability', $('condition'))),
67-
),
68-
),
69-
),
70-
),
71-
);
72-
const resolveConditionSymbol = plugin.symbol('resolveCondition');
73-
plugin.node($.const(resolveConditionSymbol).assign(resolveConditionFn));
47+
// Reserve slot for resolveCondition — only filled if actually referenced
48+
const resolveConditionSlot = plugin.node(null);
7449

7550
// Emit helper: const ensureFaker = (options?: Options): Faker => options?.faker ?? faker
7651
const fakerSymbol = plugin.external('@faker-js/faker.faker');
@@ -82,7 +57,8 @@ export const handlerV1: FakerJsFakerPlugin['Handler'] = ({ plugin }) => {
8257
const ensureFakerSymbol = plugin.symbol('ensureFaker');
8358
plugin.node($.const(ensureFakerSymbol).assign(ensureFakerFn));
8459

85-
const processor = createProcessor(plugin);
60+
const tracking: EmitTracking = { needsResolveCondition: false };
61+
const processor = createProcessor(plugin, tracking);
8662

8763
plugin.forEach('parameter', 'requestBody', 'schema', (event) => {
8864
switch (event.type) {
@@ -127,4 +103,34 @@ export const handlerV1: FakerJsFakerPlugin['Handler'] = ({ plugin }) => {
127103
break;
128104
}
129105
});
106+
107+
// Conditionally emit resolveCondition helper only when referenced
108+
if (tracking.needsResolveCondition) {
109+
const conditionParamType = $.type.or($.type('boolean'), $.type('number'));
110+
const resolveConditionFn = $.func()
111+
.arrow()
112+
.param('condition', (p) => p.type(conditionParamType))
113+
.param('faker', (p) => p.type($.type(fakerTypeSymbol)))
114+
.returns('boolean')
115+
.do(
116+
$.return(
117+
$.binary(
118+
$('condition').eq($.literal(true)),
119+
'||',
120+
$(
121+
$.binary(
122+
$('condition').typeofExpr().eq($.literal('number')),
123+
'&&',
124+
$('faker')
125+
.attr('datatype')
126+
.attr('boolean')
127+
.call($.object().prop('probability', $('condition'))),
128+
),
129+
),
130+
),
131+
),
132+
);
133+
const resolveConditionSymbol = plugin.symbol('resolveCondition');
134+
plugin.node($.const(resolveConditionSymbol).assign(resolveConditionFn), resolveConditionSlot);
135+
}
130136
};

packages/openapi-ts/src/plugins/@faker-js/faker/v1/processor.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import { createSchemaProcessor, createSchemaWalker, pathToJsonPointer } from '@h
44

55
import { exportAst } from '../shared/export';
66
import type { ProcessorContext, ProcessorResult } from '../shared/processor';
7-
import type { FakerResult } from '../shared/types';
7+
import type { EmitTracking, FakerResult } from '../shared/types';
88
import type { FakerJsFakerPlugin } from '../types';
99
import { createVisitor } from './walker';
1010

11-
export function createProcessor(plugin: FakerJsFakerPlugin['Instance']): ProcessorResult {
11+
export function createProcessor(
12+
plugin: FakerJsFakerPlugin['Instance'],
13+
tracking: EmitTracking,
14+
): ProcessorResult {
1215
const processor = createSchemaProcessor();
1316

1417
const extractorHooks: ReadonlyArray<NonNullable<Hooks['schemas']>['shouldExtract']> = [
@@ -42,7 +45,7 @@ export function createProcessor(plugin: FakerJsFakerPlugin['Instance']): Process
4245
const shouldExport = ctx.export !== false;
4346

4447
return processor.withContext({ anchor: ctx.namingAnchor, tags: ctx.tags }, () => {
45-
const visitor = createVisitor({ plugin, schemaExtractor: extractor });
48+
const visitor = createVisitor({ plugin, schemaExtractor: extractor, tracking });
4649
const walk = createSchemaWalker(visitor);
4750

4851
const result = walk(ctx.schema, {

packages/openapi-ts/src/plugins/@faker-js/faker/v1/toAst/object.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export function objectToExpression({
3737
} else {
3838
// Optional property: conditionally spread based on options.includeOptional
3939
// Generated: ...(!resolveCondition(options?.includeOptional ?? true, faker) ? {} : { prop: value })
40+
fakerCtx.tracking.needsResolveCondition = true;
4041
const includeCondition = $('resolveCondition').call(
4142
$.binary($(fakerCtx.optionsId).attr('includeOptional').optional(), '??', $.literal(true)),
4243
fakerCtx.fakerAccessor,

0 commit comments

Comments
 (0)