Skip to content

Commit 81a6f9c

Browse files
authored
fix(core): treat a blank optional integer as missing (oomol-lab#277)
`optionalIntegerOrNull("")` returned `0` rather than `null`, because `Number("")` is `0` and passes `Number.isInteger`. A blank optional field reached the provider as a real zero. ``` optionalIntegerOrNull("") before: 0 after: null optionalIntegerOrNull(" ") before: 0 after: null ``` Blank strings are now reported as missing. That matches the sibling helpers `integer` and `optionalIntegerLike`, which already exclude an empty string before parsing. Eight providers read optional integers through this helper.
1 parent 2ca1e8e commit 81a6f9c

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

src/core/cast.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, it } from "vitest";
22
import {
33
base64Bytes,
4+
optionalIntegerOrNull,
45
optionalStringArray,
56
positiveInteger,
67
requiredBoolean,
@@ -18,6 +19,20 @@ describe("cast helpers", () => {
1819
expect(() => base64Bytes("", "payload")).toThrow("payload must be valid base64");
1920
});
2021

22+
it("reports a blank optional integer as missing instead of zero", () => {
23+
expect(optionalIntegerOrNull("")).toBeNull();
24+
expect(optionalIntegerOrNull(" ")).toBeNull();
25+
expect(optionalIntegerOrNull("\n")).toBeNull();
26+
});
27+
28+
it("still reads optional integers that are present", () => {
29+
expect(optionalIntegerOrNull("2")).toBe(2);
30+
expect(optionalIntegerOrNull(" 2 ")).toBe(2);
31+
expect(optionalIntegerOrNull(0)).toBe(0);
32+
expect(optionalIntegerOrNull("0")).toBe(0);
33+
expect(optionalIntegerOrNull("2.5")).toBeNull();
34+
});
35+
2136
it("rejects zero for positive integer strings", () => {
2237
expect(() => positiveInteger("0", "page")).toThrow("page must be a positive integer");
2338
});

src/core/cast.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,13 +391,17 @@ export function optionalStringOrNull(value: unknown): string | null {
391391
}
392392

393393
/**
394-
* Return an integer from an integer number or numeric string, or null.
394+
* Return an integer from an integer number or numeric string, or null. Examples:
395+
* `optionalIntegerOrNull("2") => 2`, `optionalIntegerOrNull("") => null`.
396+
*
397+
* A blank string is reported as missing rather than parsed, because `Number("")`
398+
* is `0` and an empty field would otherwise reach a provider as a real zero.
395399
*/
396400
export function optionalIntegerOrNull(value: unknown): number | null {
397401
if (Number.isInteger(value)) {
398402
return value as number;
399403
}
400-
if (typeof value === "string") {
404+
if (typeof value === "string" && value.trim() !== "") {
401405
const parsed = Number(value);
402406
return Number.isInteger(parsed) ? parsed : null;
403407
}

0 commit comments

Comments
 (0)