Skip to content

Commit cc6034c

Browse files
committed
handle circular reference
1 parent 51115cc commit cc6034c

31 files changed

Lines changed: 1253 additions & 28 deletions

File tree

docs/openapi-ts/plugins/faker.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ const data = fakeFoo({ useDefault: true }); // uses schema defaults when availab
185185
const data = fakeFoo({ useDefault: 0.5 }); // 50% probability of using defaults
186186
```
187187

188+
## Circular References
189+
190+
Schemas with circular references are automatically detected, whether self-referencing (a schema that refers to itself) or mutually recursive (two or more schemas that refer to each other). The generated factories use a depth counter to prevent infinite recursion, returning empty arrays or omitting optional properties once the limit is reached.
191+
192+
The maximum depth defaults to `10` and can be configured via the `maxCallDepth` option.
193+
188194
## Locale
189195

190196
You can configure the locale for generated faker factories. When set, the generated code imports the faker instance from the locale-specific build of `@faker-js/faker`.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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 { Comment, Department, Employee, Member, Org, Project, TreeNode } 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 MAX_CALL_DEPTH = 10;
24+
25+
const resolveCondition = (condition: boolean | number, faker: Faker): boolean => condition === true || typeof condition === 'number' && faker.datatype.boolean({ probability: condition });
26+
27+
export const fakeTreeNode = (options: Options = {}, _callDepth = 0): TreeNode => {
28+
const f = options?.faker ?? faker;
29+
return {
30+
id: f.string.uuid(),
31+
value: f.string.sample(),
32+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { children: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeTreeNode(options, _callDepth + 1)) }
33+
};
34+
};
35+
36+
export const fakeComment = (options: Options = {}, _callDepth = 0): Comment => {
37+
const f = options?.faker ?? faker;
38+
return {
39+
id: f.string.uuid(),
40+
text: f.string.sample(),
41+
parent: _callDepth > MAX_CALL_DEPTH ? null : f.datatype.boolean() ? fakeComment(options, _callDepth + 1) : null
42+
};
43+
};
44+
45+
export const fakeOrg = (options: Options = {}, _callDepth = 0): Org => {
46+
const f = options?.faker ?? faker;
47+
return {
48+
id: f.string.uuid(),
49+
name: f.string.sample(),
50+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { members: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeMember(options, _callDepth + 1)) }
51+
};
52+
};
53+
54+
export const fakeMember = (options: Options = {}, _callDepth = 0): Member => {
55+
const f = options?.faker ?? faker;
56+
return {
57+
id: f.string.uuid(),
58+
name: f.string.sample(),
59+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { org: fakeOrg(options, _callDepth + 1) }
60+
};
61+
};
62+
63+
export const fakeDepartment = (options: Options = {}, _callDepth = 0): Department => {
64+
const f = options?.faker ?? faker;
65+
return {
66+
id: f.string.uuid(),
67+
name: f.string.sample(),
68+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { employees: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeEmployee(options, _callDepth + 1)) }
69+
};
70+
};
71+
72+
export const fakeEmployee = (options: Options = {}, _callDepth = 0): Employee => {
73+
const f = options?.faker ?? faker;
74+
return {
75+
id: f.string.uuid(),
76+
name: f.person.fullName(),
77+
projects: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeProject(options, _callDepth + 1))
78+
};
79+
};
80+
81+
export const fakeProject = (options: Options = {}, _callDepth = 0): Project => {
82+
const f = options?.faker ?? faker;
83+
return {
84+
id: f.string.uuid(),
85+
name: f.string.sample(),
86+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { department: fakeDepartment(options, _callDepth + 1) }
87+
};
88+
};
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 { ClientOptions, Comment, Department, Employee, Member, Org, Project, TreeNode } from './types.gen';
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// This file is auto-generated by @hey-api/openapi-ts
2+
3+
export type ClientOptions = {
4+
baseUrl: string;
5+
};
6+
7+
export type TreeNode = {
8+
id: string;
9+
value: string;
10+
children?: Array<TreeNode>;
11+
};
12+
13+
export type Org = {
14+
id: string;
15+
name: string;
16+
members?: Array<Member>;
17+
};
18+
19+
export type Member = {
20+
id: string;
21+
name: string;
22+
org?: Org;
23+
};
24+
25+
export type Department = {
26+
id: string;
27+
name: string;
28+
employees?: Array<Employee>;
29+
};
30+
31+
export type Employee = {
32+
id: string;
33+
name: string;
34+
projects: Array<Project>;
35+
};
36+
37+
export type Project = {
38+
id: string;
39+
name: string;
40+
department?: Department;
41+
};
42+
43+
export type Comment = {
44+
id: string;
45+
text: string;
46+
parent: Comment | null;
47+
};
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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 { Bar, Baz, Comment, Corge, Department, Employee, Foo, Member, Org, Project, Quux, Qux, TreeNode } 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 MAX_CALL_DEPTH = 10;
24+
25+
const resolveCondition = (condition: boolean | number, faker: Faker): boolean => condition === true || typeof condition === 'number' && faker.datatype.boolean({ probability: condition });
26+
27+
export const fakeTreeNode = (options: Options = {}, _callDepth = 0): TreeNode => {
28+
const f = options?.faker ?? faker;
29+
return {
30+
id: f.string.uuid(),
31+
value: f.string.sample(),
32+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { children: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeTreeNode(options, _callDepth + 1)) }
33+
};
34+
};
35+
36+
export const fakeComment = (options: Options = {}, _callDepth = 0): Comment => {
37+
const f = options?.faker ?? faker;
38+
return {
39+
id: f.string.uuid(),
40+
text: f.string.sample(),
41+
parent: _callDepth > MAX_CALL_DEPTH ? null : f.datatype.boolean() ? fakeComment(options, _callDepth + 1) : null
42+
};
43+
};
44+
45+
export const fakeFoo = (options: Options = {}, _callDepth = 0): Foo => {
46+
const f = options?.faker ?? faker;
47+
return {
48+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { quux: fakeQuux(options, _callDepth + 1) }
49+
};
50+
};
51+
52+
export const fakeBar = (options: Options = {}, _callDepth = 0): Bar => {
53+
const f = options?.faker ?? faker;
54+
return {
55+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { bar: fakeBar(options, _callDepth + 1) },
56+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { baz: fakeBaz(options, _callDepth + 1) }
57+
};
58+
};
59+
60+
export const fakeBaz = (options: Options = {}, _callDepth = 0): Baz => {
61+
const f = options?.faker ?? faker;
62+
return {
63+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { quux: fakeQuux(options, _callDepth + 1) }
64+
};
65+
};
66+
67+
export const fakeQux = (options: Options = {}, _callDepth = 0): Qux => {
68+
const f = options?.faker ?? faker;
69+
return f.helpers.arrayElement([() => ({
70+
...fakeCorge(options, _callDepth + 1),
71+
type: 'struct' as const
72+
}), () => ({
73+
...fakeFoo(options, _callDepth + 1),
74+
type: 'array' as const
75+
})])();
76+
};
77+
78+
export const fakeQuux = (options: Options = {}, _callDepth = 0): Quux => {
79+
const f = options?.faker ?? faker;
80+
return {
81+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { qux: fakeQux(options, _callDepth + 1) }
82+
};
83+
};
84+
85+
export const fakeCorge = (options: Options = {}, _callDepth = 0): Corge => {
86+
const f = options?.faker ?? faker;
87+
return {
88+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { baz: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeBaz(options, _callDepth + 1)) }
89+
};
90+
};
91+
92+
export const fakeOrg = (options: Options = {}, _callDepth = 0): Org => {
93+
const f = options?.faker ?? faker;
94+
return {
95+
id: f.string.uuid(),
96+
name: f.string.sample(),
97+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { members: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeMember(options, _callDepth + 1)) }
98+
};
99+
};
100+
101+
export const fakeMember = (options: Options = {}, _callDepth = 0): Member => {
102+
const f = options?.faker ?? faker;
103+
return {
104+
id: f.string.uuid(),
105+
name: f.string.sample(),
106+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { org: fakeOrg(options, _callDepth + 1) }
107+
};
108+
};
109+
110+
export const fakeDepartment = (options: Options = {}, _callDepth = 0): Department => {
111+
const f = options?.faker ?? faker;
112+
return {
113+
id: f.string.uuid(),
114+
name: f.string.sample(),
115+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { employees: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeEmployee(options, _callDepth + 1)) }
116+
};
117+
};
118+
119+
export const fakeEmployee = (options: Options = {}, _callDepth = 0): Employee => {
120+
const f = options?.faker ?? faker;
121+
return {
122+
id: f.string.uuid(),
123+
name: f.person.fullName(),
124+
projects: _callDepth > MAX_CALL_DEPTH ? [] : f.helpers.multiple(() => fakeProject(options, _callDepth + 1))
125+
};
126+
};
127+
128+
export const fakeProject = (options: Options = {}, _callDepth = 0): Project => {
129+
const f = options?.faker ?? faker;
130+
return {
131+
id: f.string.uuid(),
132+
name: f.string.sample(),
133+
..._callDepth > MAX_CALL_DEPTH || !resolveCondition(options?.includeOptional ?? true, f) ? {} : { department: fakeDepartment(options, _callDepth + 1) }
134+
};
135+
};
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 { Bar, Baz, ClientOptions, Comment, Corge, Department, Employee, Foo, Member, Org, Project, Quux, Qux, TreeNode } from './types.gen';
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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 Foo = {
8+
quux?: Quux;
9+
};
10+
11+
export type Bar = {
12+
bar?: Bar;
13+
baz?: Baz;
14+
};
15+
16+
export type Baz = {
17+
quux?: Quux;
18+
};
19+
20+
export type Qux = ({
21+
type: 'struct';
22+
} & Corge) | ({
23+
type: 'array';
24+
} & Foo);
25+
26+
export type Quux = {
27+
qux?: Qux;
28+
};
29+
30+
export type Corge = {
31+
baz?: Array<Baz>;
32+
};
33+
34+
export type TreeNode = {
35+
id: string;
36+
value: string;
37+
children?: Array<TreeNode>;
38+
};
39+
40+
export type Org = {
41+
id: string;
42+
name: string;
43+
members?: Array<Member>;
44+
};
45+
46+
export type Member = {
47+
id: string;
48+
name: string;
49+
org?: Org;
50+
};
51+
52+
export type Department = {
53+
id: string;
54+
name: string;
55+
employees?: Array<Employee>;
56+
};
57+
58+
export type Employee = {
59+
id: string;
60+
name: string;
61+
projects: Array<Project>;
62+
};
63+
64+
export type Project = {
65+
id: string;
66+
name: string;
67+
department?: Department;
68+
};
69+
70+
export type Comment = {
71+
id: string;
72+
text: string;
73+
parent: Comment | null;
74+
};

0 commit comments

Comments
 (0)