Skip to content

Commit 9f8c350

Browse files
authored
fix(mock): avoid double-wrapping null branch for required nullable scalars (#3501)
1 parent ebd2e3c commit 9f8c350

27 files changed

Lines changed: 329 additions & 310 deletions

File tree

packages/mock/src/faker/getters/object.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ export function getMockObject({
209209
Array.isArray(prop.type) && prop.type.includes('null');
210210
if (
211211
isNullable &&
212+
!resolvedValue.nullWrapped &&
212213
!resolvedValue.overrided &&
213214
!mockOptions?.nonNullable
214215
) {

packages/mock/src/faker/getters/scalar.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,9 +143,13 @@ export function getMockScalar({
143143
),
144144
};
145145

146-
// OpenAPI 3.1 null unions only — 3.0 `nullable: true` is handled in object.ts
147-
// to avoid double-wrapping scalar values that object.ts already null-randomizes.
146+
// Both OpenAPI 3.1 `type: [..., 'null']` and OpenAPI 3.0 `nullable: true`
147+
// reach here as a null union, because @scalar/openapi-parser upgrades 3.0
148+
// inputs to 3.1 before mock generation. When this getter wraps the value via
149+
// `getNullable` it flags the returned MockDefinition with `nullWrapped` so the
150+
// object property layer does not add a second `arrayElement([..., null])`.
148151
const isNullable = Array.isArray(item.type) && item.type.includes('null');
152+
const nullWrapped = isNullable && !nonNullableOption;
149153
// The @scalar/openapi-parser upgrader rewrites `format: binary` to
150154
// `contentMediaType: application/octet-stream` when upgrading OAS 3.0 → 3.1;
151155
// treat both equivalently so the mock emits the binary format value
@@ -161,6 +165,7 @@ export function getMockScalar({
161165
imports: [],
162166
name: item.name,
163167
overrided: false,
168+
nullWrapped,
164169
};
165170
}
166171
if (item.format && ALL_FORMAT[item.format]) {
@@ -176,6 +181,7 @@ export function getMockScalar({
176181
imports: [],
177182
name: item.name,
178183
overrided: false,
184+
nullWrapped,
179185
};
180186
}
181187

@@ -249,6 +255,9 @@ export function getMockScalar({
249255
enums: item.enum,
250256
imports: numberImports,
251257
name: item.name,
258+
// `item.enum` / `const` reassign `value` after `getNullable`, discarding
259+
// the wrap — so only the plain numeric path is actually null-wrapped.
260+
nullWrapped: nullWrapped && !item.enum && !('const' in item),
252261
};
253262
}
254263

@@ -450,6 +459,7 @@ export function getMockScalar({
450459
enums: item.enum,
451460
name: item.name,
452461
imports: stringImports,
462+
nullWrapped,
453463
};
454464
}
455465

packages/mock/src/faker/resolvers/value.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ export function resolveMockValue({
359359
imports,
360360
name: newSchema.name,
361361
type: getType(newSchema),
362+
nullWrapped: Boolean(newSchema.nullable) && !mockOptions?.nonNullable,
362363
};
363364
}
364365

packages/mock/src/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export interface MockDefinition {
1111
name: string;
1212
overrided?: boolean;
1313
includedProperties?: string[];
14+
// True when `value` already embeds its own null branch (e.g. the scalar
15+
// getter wrapped it via `getNullable`). The object property layer reads this
16+
// to avoid wrapping the value in a second `arrayElement([..., null])`.
17+
nullWrapped?: boolean;
1418
}
1519

1620
type OpenApiObjectSchema = Extract<OpenApiSchemaObject, object>;

samples/angular-app/__snapshots__/api/endpoints-zod/pets/pets.msw.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ export const getSearchPetsResponseMock = (): Pets =>
3939
undefined,
4040
]))(),
4141
requiredNullableString: faker.helpers.arrayElement([
42-
faker.helpers.arrayElement([
43-
faker.string.alpha({ length: { min: 10, max: 20 } }),
44-
null,
45-
]),
42+
faker.string.alpha({ length: { min: 10, max: 20 } }),
4643
null,
4744
]),
4845
optionalNullableString: faker.helpers.arrayElement([
@@ -83,10 +80,7 @@ export const getListPetsResponseMock = (): Pets =>
8380
undefined,
8481
]))(),
8582
requiredNullableString: faker.helpers.arrayElement([
86-
faker.helpers.arrayElement([
87-
faker.string.alpha({ length: { min: 10, max: 20 } }),
88-
null,
89-
]),
83+
faker.string.alpha({ length: { min: 10, max: 20 } }),
9084
null,
9185
]),
9286
optionalNullableString: faker.helpers.arrayElement([
@@ -124,10 +118,7 @@ export const getListPetsResponseMock = (): Pets =>
124118
undefined,
125119
]))(),
126120
requiredNullableString: faker.helpers.arrayElement([
127-
faker.helpers.arrayElement([
128-
faker.string.alpha({ length: { min: 10, max: 20 } }),
129-
null,
130-
]),
121+
faker.string.alpha({ length: { min: 10, max: 20 } }),
131122
null,
132123
]),
133124
optionalNullableString: faker.helpers.arrayElement([
@@ -186,10 +177,7 @@ export const getUpdatePetByIdResponseMock = (
186177
undefined,
187178
]))(),
188179
requiredNullableString: faker.helpers.arrayElement([
189-
faker.helpers.arrayElement([
190-
faker.string.alpha({ length: { min: 10, max: 20 } }),
191-
null,
192-
]),
180+
faker.string.alpha({ length: { min: 10, max: 20 } }),
193181
null,
194182
]),
195183
optionalNullableString: faker.helpers.arrayElement([
@@ -232,10 +220,7 @@ export const getPatchPetByIdResponseMock = (
232220
undefined,
233221
]))(),
234222
requiredNullableString: faker.helpers.arrayElement([
235-
faker.helpers.arrayElement([
236-
faker.string.alpha({ length: { min: 10, max: 20 } }),
237-
null,
238-
]),
223+
faker.string.alpha({ length: { min: 10, max: 20 } }),
239224
null,
240225
]),
241226
optionalNullableString: faker.helpers.arrayElement([

samples/angular-app/__snapshots__/api/http-both/pets/pets.msw.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ export const getSearchPetsResponseMock = (): Pets =>
3939
undefined,
4040
]))(),
4141
requiredNullableString: faker.helpers.arrayElement([
42-
faker.helpers.arrayElement([
43-
faker.string.alpha({ length: { min: 10, max: 20 } }),
44-
null,
45-
]),
42+
faker.string.alpha({ length: { min: 10, max: 20 } }),
4643
null,
4744
]),
4845
optionalNullableString: faker.helpers.arrayElement([
@@ -83,10 +80,7 @@ export const getListPetsResponseMock = (): Pets =>
8380
undefined,
8481
]))(),
8582
requiredNullableString: faker.helpers.arrayElement([
86-
faker.helpers.arrayElement([
87-
faker.string.alpha({ length: { min: 10, max: 20 } }),
88-
null,
89-
]),
83+
faker.string.alpha({ length: { min: 10, max: 20 } }),
9084
null,
9185
]),
9286
optionalNullableString: faker.helpers.arrayElement([
@@ -124,10 +118,7 @@ export const getListPetsResponseMock = (): Pets =>
124118
undefined,
125119
]))(),
126120
requiredNullableString: faker.helpers.arrayElement([
127-
faker.helpers.arrayElement([
128-
faker.string.alpha({ length: { min: 10, max: 20 } }),
129-
null,
130-
]),
121+
faker.string.alpha({ length: { min: 10, max: 20 } }),
131122
null,
132123
]),
133124
optionalNullableString: faker.helpers.arrayElement([
@@ -186,10 +177,7 @@ export const getUpdatePetByIdResponseMock = (
186177
undefined,
187178
]))(),
188179
requiredNullableString: faker.helpers.arrayElement([
189-
faker.helpers.arrayElement([
190-
faker.string.alpha({ length: { min: 10, max: 20 } }),
191-
null,
192-
]),
180+
faker.string.alpha({ length: { min: 10, max: 20 } }),
193181
null,
194182
]),
195183
optionalNullableString: faker.helpers.arrayElement([
@@ -232,10 +220,7 @@ export const getPatchPetByIdResponseMock = (
232220
undefined,
233221
]))(),
234222
requiredNullableString: faker.helpers.arrayElement([
235-
faker.helpers.arrayElement([
236-
faker.string.alpha({ length: { min: 10, max: 20 } }),
237-
null,
238-
]),
223+
faker.string.alpha({ length: { min: 10, max: 20 } }),
239224
null,
240225
]),
241226
optionalNullableString: faker.helpers.arrayElement([

samples/angular-app/__snapshots__/api/http-client-custom-params/pets/pets.msw.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ export const getSearchPetsResponseMock = (): Pets =>
3939
undefined,
4040
]))(),
4141
requiredNullableString: faker.helpers.arrayElement([
42-
faker.helpers.arrayElement([
43-
faker.string.alpha({ length: { min: 10, max: 20 } }),
44-
null,
45-
]),
42+
faker.string.alpha({ length: { min: 10, max: 20 } }),
4643
null,
4744
]),
4845
optionalNullableString: faker.helpers.arrayElement([
@@ -83,10 +80,7 @@ export const getListPetsResponseMock = (): Pets =>
8380
undefined,
8481
]))(),
8582
requiredNullableString: faker.helpers.arrayElement([
86-
faker.helpers.arrayElement([
87-
faker.string.alpha({ length: { min: 10, max: 20 } }),
88-
null,
89-
]),
83+
faker.string.alpha({ length: { min: 10, max: 20 } }),
9084
null,
9185
]),
9286
optionalNullableString: faker.helpers.arrayElement([
@@ -124,10 +118,7 @@ export const getListPetsResponseMock = (): Pets =>
124118
undefined,
125119
]))(),
126120
requiredNullableString: faker.helpers.arrayElement([
127-
faker.helpers.arrayElement([
128-
faker.string.alpha({ length: { min: 10, max: 20 } }),
129-
null,
130-
]),
121+
faker.string.alpha({ length: { min: 10, max: 20 } }),
131122
null,
132123
]),
133124
optionalNullableString: faker.helpers.arrayElement([
@@ -186,10 +177,7 @@ export const getUpdatePetByIdResponseMock = (
186177
undefined,
187178
]))(),
188179
requiredNullableString: faker.helpers.arrayElement([
189-
faker.helpers.arrayElement([
190-
faker.string.alpha({ length: { min: 10, max: 20 } }),
191-
null,
192-
]),
180+
faker.string.alpha({ length: { min: 10, max: 20 } }),
193181
null,
194182
]),
195183
optionalNullableString: faker.helpers.arrayElement([
@@ -232,10 +220,7 @@ export const getPatchPetByIdResponseMock = (
232220
undefined,
233221
]))(),
234222
requiredNullableString: faker.helpers.arrayElement([
235-
faker.helpers.arrayElement([
236-
faker.string.alpha({ length: { min: 10, max: 20 } }),
237-
null,
238-
]),
223+
faker.string.alpha({ length: { min: 10, max: 20 } }),
239224
null,
240225
]),
241226
optionalNullableString: faker.helpers.arrayElement([

samples/angular-app/__snapshots__/api/http-client/pets/pets.msw.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ export const getSearchPetsResponseMock = (): Pets =>
3939
undefined,
4040
]))(),
4141
requiredNullableString: faker.helpers.arrayElement([
42-
faker.helpers.arrayElement([
43-
faker.string.alpha({ length: { min: 10, max: 20 } }),
44-
null,
45-
]),
42+
faker.string.alpha({ length: { min: 10, max: 20 } }),
4643
null,
4744
]),
4845
optionalNullableString: faker.helpers.arrayElement([
@@ -83,10 +80,7 @@ export const getListPetsResponseMock = (): Pets =>
8380
undefined,
8481
]))(),
8582
requiredNullableString: faker.helpers.arrayElement([
86-
faker.helpers.arrayElement([
87-
faker.string.alpha({ length: { min: 10, max: 20 } }),
88-
null,
89-
]),
83+
faker.string.alpha({ length: { min: 10, max: 20 } }),
9084
null,
9185
]),
9286
optionalNullableString: faker.helpers.arrayElement([
@@ -124,10 +118,7 @@ export const getListPetsResponseMock = (): Pets =>
124118
undefined,
125119
]))(),
126120
requiredNullableString: faker.helpers.arrayElement([
127-
faker.helpers.arrayElement([
128-
faker.string.alpha({ length: { min: 10, max: 20 } }),
129-
null,
130-
]),
121+
faker.string.alpha({ length: { min: 10, max: 20 } }),
131122
null,
132123
]),
133124
optionalNullableString: faker.helpers.arrayElement([
@@ -186,10 +177,7 @@ export const getUpdatePetByIdResponseMock = (
186177
undefined,
187178
]))(),
188179
requiredNullableString: faker.helpers.arrayElement([
189-
faker.helpers.arrayElement([
190-
faker.string.alpha({ length: { min: 10, max: 20 } }),
191-
null,
192-
]),
180+
faker.string.alpha({ length: { min: 10, max: 20 } }),
193181
null,
194182
]),
195183
optionalNullableString: faker.helpers.arrayElement([
@@ -232,10 +220,7 @@ export const getPatchPetByIdResponseMock = (
232220
undefined,
233221
]))(),
234222
requiredNullableString: faker.helpers.arrayElement([
235-
faker.helpers.arrayElement([
236-
faker.string.alpha({ length: { min: 10, max: 20 } }),
237-
null,
238-
]),
223+
faker.string.alpha({ length: { min: 10, max: 20 } }),
239224
null,
240225
]),
241226
optionalNullableString: faker.helpers.arrayElement([

samples/angular-app/__snapshots__/api/http-resource-zod/pets/pets.msw.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ export const getSearchPetsResponseMock = (): Pets =>
3939
undefined,
4040
]))(),
4141
requiredNullableString: faker.helpers.arrayElement([
42-
faker.helpers.arrayElement([
43-
faker.string.alpha({ length: { min: 10, max: 20 } }),
44-
null,
45-
]),
42+
faker.string.alpha({ length: { min: 10, max: 20 } }),
4643
null,
4744
]),
4845
optionalNullableString: faker.helpers.arrayElement([
@@ -83,10 +80,7 @@ export const getListPetsResponseMock = (): Pets =>
8380
undefined,
8481
]))(),
8582
requiredNullableString: faker.helpers.arrayElement([
86-
faker.helpers.arrayElement([
87-
faker.string.alpha({ length: { min: 10, max: 20 } }),
88-
null,
89-
]),
83+
faker.string.alpha({ length: { min: 10, max: 20 } }),
9084
null,
9185
]),
9286
optionalNullableString: faker.helpers.arrayElement([
@@ -124,10 +118,7 @@ export const getListPetsResponseMock = (): Pets =>
124118
undefined,
125119
]))(),
126120
requiredNullableString: faker.helpers.arrayElement([
127-
faker.helpers.arrayElement([
128-
faker.string.alpha({ length: { min: 10, max: 20 } }),
129-
null,
130-
]),
121+
faker.string.alpha({ length: { min: 10, max: 20 } }),
131122
null,
132123
]),
133124
optionalNullableString: faker.helpers.arrayElement([
@@ -186,10 +177,7 @@ export const getUpdatePetByIdResponseMock = (
186177
undefined,
187178
]))(),
188179
requiredNullableString: faker.helpers.arrayElement([
189-
faker.helpers.arrayElement([
190-
faker.string.alpha({ length: { min: 10, max: 20 } }),
191-
null,
192-
]),
180+
faker.string.alpha({ length: { min: 10, max: 20 } }),
193181
null,
194182
]),
195183
optionalNullableString: faker.helpers.arrayElement([
@@ -232,10 +220,7 @@ export const getPatchPetByIdResponseMock = (
232220
undefined,
233221
]))(),
234222
requiredNullableString: faker.helpers.arrayElement([
235-
faker.helpers.arrayElement([
236-
faker.string.alpha({ length: { min: 10, max: 20 } }),
237-
null,
238-
]),
223+
faker.string.alpha({ length: { min: 10, max: 20 } }),
239224
null,
240225
]),
241226
optionalNullableString: faker.helpers.arrayElement([

0 commit comments

Comments
 (0)