Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions packages/core/src/ai-model/shared/model-locate-result/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,18 @@ export function parseCoordinateList(input: unknown, label: string): number[] {
throw new Error(`invalid ${label} data: ${JSON.stringify(input)} `);
}

const numericValues = values.map((value) =>
typeof value === 'number' ? value : Number(value),
);
const numericValues = values.map((value) => {
if (typeof value === 'number') {
return value;
}
if (
typeof value !== 'string' ||
!/^[+-]?(?:\d+\.?\d*|\.\d+)$/.test(value.trim())
) {
return Number.NaN;
}
return Number(value);
});

if (!numericValues.every((value) => Number.isFinite(value))) {
throw new Error(`invalid ${label} data: ${JSON.stringify(input)} `);
Expand Down Expand Up @@ -90,7 +99,7 @@ export function parseNumericLocateResult(
): LocateResultValue {
if (resolvedCoordinates.shape === 'point') {
const point = parseCoordinateList(input, 'point');
if (point.length < 2) {
if (point.length !== 2) {
throw new Error(`invalid point data: ${JSON.stringify(input)} `);
}
return createLocateResultValue(resolvedCoordinates, point);
Expand Down
39 changes: 39 additions & 0 deletions packages/core/tests/unit-test/locate-result-codec.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,35 @@ describe('createLocateResultCodec', () => {
).toThrow(/invalid bbox data/);
});

it.each([null, true, false, '', ' '])(
'rejects coercible non-coordinate bbox value: %j',
(invalidValue) => {
const codec = createLocateResultCodec({
coordinates: { shape: 'bbox', order: 'xy', normalizedBy: 1000 },
});

expect(() =>
codec.toPixelBbox(
[invalidValue, 100, 300, 400] as never,
locateCtx(640, 360),
),
).toThrow(/invalid bbox data/);
},
);

it('accepts decimal coordinate strings without general JavaScript coercion', () => {
const codec = createLocateResultCodec({
coordinates: { shape: 'bbox', order: 'xy', normalizedBy: 1000 },
});

expect(
codec.toPixelBbox(
['100', '200.5', '300.25', '400'] as never,
locateCtx(200, 100),
),
).toEqual([20, 20, 60, 40]);
});

it('rejects invalid parsed adapter results before coordinate range checks', () => {
const codec = createLocateResultCodec({
coordinates: { shape: 'bbox', order: 'xy', normalizedBy: 1000 },
Expand Down Expand Up @@ -158,6 +187,16 @@ describe('createLocateResultCodec', () => {
);
});

it('rejects point coordinate values with more than two entries', () => {
const codec = createLocateResultCodec({
coordinates: { shape: 'point', order: 'xy', normalizedBy: 1000 },
});

expect(() =>
codec.toPixelBbox([500, 500, 500], locateCtx(640, 360)),
).toThrow(/invalid point data/);
});

it('rejects non-positive normalizedBy values', () => {
expect(() =>
createLocateResultCodec({
Expand Down
Loading