fix(omptype): emit postfix bound for min-only numeric typebox schemas
tNumber rendered a min-only bound as the left-bound-only DSL
"1 <= number.integer", which parseBounded rejects because that range
spelling requires a trailing right bound. Standard TypeBox schemas like
Type.Integer({ minimum: 1 }) therefore failed to build, breaking
extension tool loading. Emit the postfix "number.integer >= 1" form when
there is no upper bound instead.
Fixes #7648
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the TypeBox adapter emitting an invalid left-bound-only DSL for min-only numeric schemas (e.g. `Type.Integer({ minimum: 1 })`), which threw `left bound requires a corresponding right bound` and broke extension tool loading ([#7648](https://github.com/can1357/oh-my-pi/issues/7648)).
|
||||
|
||||
## [17.2.8] - 2026-08-04
|
||||
|
||||
### Added
|
||||
|
||||
@@ -267,9 +267,19 @@ function tNumber(opts?: NumberOpts, integer = false): TNumber {
|
||||
upper = { value: opts.exclusiveMaximum, exclusive: true };
|
||||
}
|
||||
const keyword = integer ? "number.integer" : "number";
|
||||
const lowerDsl = lower ? `${lower.value} ${lower.exclusive ? "<" : "<="} ` : "";
|
||||
const upperDsl = upper ? ` ${upper.exclusive ? "<" : "<="} ${upper.value}` : "";
|
||||
let schema = asRuntime<number>(type.raw(`${lowerDsl}${keyword}${upperDsl}`));
|
||||
// The `LO <= TYPE <= HI` range spelling requires both bounds; a min-only
|
||||
// bound must use the postfix `TYPE >= LO` form (see parseBounded in ir.ts).
|
||||
let src: string;
|
||||
if (lower && upper) {
|
||||
src = `${lower.value} ${lower.exclusive ? "<" : "<="} ${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
|
||||
} else if (lower) {
|
||||
src = `${keyword} ${lower.exclusive ? ">" : ">="} ${lower.value}`;
|
||||
} else if (upper) {
|
||||
src = `${keyword} ${upper.exclusive ? "<" : "<="} ${upper.value}`;
|
||||
} else {
|
||||
src = keyword;
|
||||
}
|
||||
let schema = asRuntime<number>(type.raw(src));
|
||||
if (opts?.multipleOf !== undefined) {
|
||||
const divisor = opts.multipleOf;
|
||||
schema = schema.narrow((value, ctx) => {
|
||||
|
||||
@@ -56,6 +56,22 @@ describe("TypeBox adapter", () => {
|
||||
expect(valid(exclusive, 1)).toBe(false);
|
||||
expect(valid(exclusive, 3)).toBe(false);
|
||||
expect(exclusive.toJsonSchema()).toEqual({ type: "number", exclusiveMinimum: 1, exclusiveMaximum: 3 });
|
||||
|
||||
const minOnly = Type.Integer({ minimum: 1 });
|
||||
expect(valid(minOnly, 1)).toBe(true);
|
||||
expect(valid(minOnly, 0)).toBe(false);
|
||||
expect(valid(minOnly, 1.5)).toBe(false);
|
||||
expect(minOnly.toJsonSchema()).toEqual({ type: "integer", minimum: 1 });
|
||||
const exclusiveMinOnly = Type.Number({ exclusiveMinimum: 0 });
|
||||
expect(valid(exclusiveMinOnly, 0)).toBe(false);
|
||||
expect(valid(exclusiveMinOnly, 0.5)).toBe(true);
|
||||
const maxOnly = Type.Number({ maximum: 5 });
|
||||
expect(valid(maxOnly, 5)).toBe(true);
|
||||
expect(valid(maxOnly, 6)).toBe(false);
|
||||
const minWithMultiple = Type.Integer({ minimum: 1, multipleOf: 2 });
|
||||
expect(valid(minWithMultiple, 4)).toBe(true);
|
||||
expect(valid(minWithMultiple, 3)).toBe(false);
|
||||
expect(valid(minWithMultiple, 0)).toBe(false);
|
||||
});
|
||||
|
||||
test("arrays, tuples, objects, records and intersections validate", () => {
|
||||
|
||||
Reference in New Issue
Block a user