mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
perf(api): fast path json bodies with no large integers (#2214)
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
const MAX_SAFE_INTEGER_DECIMAL = Number.MAX_SAFE_INTEGER.toString();
|
const MAX_SAFE_INTEGER_DECIMAL = Number.MAX_SAFE_INTEGER.toString();
|
||||||
|
const UNSAFE_INTEGER_DIGIT_RUN = new RegExp(String.raw`\d{${MAX_SAFE_INTEGER_DECIMAL.length}}`);
|
||||||
|
|
||||||
function isDigit(char: string): boolean {
|
function isDigit(char: string): boolean {
|
||||||
return char >= '0' && char <= '9';
|
return char >= '0' && char <= '9';
|
||||||
@@ -21,7 +22,7 @@ function isUnsafeIntegerToken(token: string): boolean {
|
|||||||
return digits > MAX_SAFE_INTEGER_DECIMAL;
|
return digits > MAX_SAFE_INTEGER_DECIMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
function coerceUnsafeIntegersToStrings(jsonText: string): string {
|
export function coerceUnsafeIntegersToStrings(jsonText: string): string {
|
||||||
let inString = false;
|
let inString = false;
|
||||||
let escaped = false;
|
let escaped = false;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
@@ -76,7 +77,7 @@ function coerceUnsafeIntegersToStrings(jsonText: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function parseJsonPreservingLargeIntegers(jsonText: string): unknown {
|
export function parseJsonPreservingLargeIntegers(jsonText: string): unknown {
|
||||||
const processed = coerceUnsafeIntegersToStrings(jsonText);
|
const processed = UNSAFE_INTEGER_DIGIT_RUN.test(jsonText) ? coerceUnsafeIntegersToStrings(jsonText) : jsonText;
|
||||||
const parsed: unknown = JSON.parse(processed);
|
const parsed: unknown = JSON.parse(processed);
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
import {describe, expect, it} from 'vitest';
|
import {describe, expect, it} from 'vitest';
|
||||||
import {parseJsonPreservingLargeIntegers} from '../LosslessJsonParser';
|
import {coerceUnsafeIntegersToStrings, parseJsonPreservingLargeIntegers} from '../LosslessJsonParser';
|
||||||
|
|
||||||
describe('parseJsonPreservingLargeIntegers', () => {
|
describe('parseJsonPreservingLargeIntegers', () => {
|
||||||
it('keeps safe integers as numbers', () => {
|
it('keeps safe integers as numbers', () => {
|
||||||
@@ -41,3 +41,103 @@ describe('parseJsonPreservingLargeIntegers', () => {
|
|||||||
expect(parsed.arr[1]).toBe('1472109478688579732');
|
expect(parsed.arr[1]).toBe('1472109478688579732');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const CORPUS: Array<string> = [
|
||||||
|
'{"content":"hey","nonce":"1472109478688579732","channel_id":"1472109478688579731"}',
|
||||||
|
'{"message_ids":["1472109478688579732","1472109478688579733","1472109478688579734"]}',
|
||||||
|
'{"id":9007199254740991}',
|
||||||
|
'{"id":9007199254740992}',
|
||||||
|
'{"id":-9007199254740991}',
|
||||||
|
'{"id":-9007199254740992}',
|
||||||
|
'{"id":-1472109478688579732}',
|
||||||
|
'{"id":999999999999999}',
|
||||||
|
'{"id":1000000000000000}',
|
||||||
|
'{"id":9999999999999999}',
|
||||||
|
'{"id":99999999999999999}',
|
||||||
|
'{"id":123456789012345}',
|
||||||
|
'{"id":1234567890123456}',
|
||||||
|
'{"id":12345678901234567}',
|
||||||
|
'{"id":0}',
|
||||||
|
'{"id":-0}',
|
||||||
|
'{"id":00}',
|
||||||
|
'{"id":0.00000000000000012345}',
|
||||||
|
'{"id":1.2345678901234567890}',
|
||||||
|
'{"id":123456789012345678.9}',
|
||||||
|
'{"id":1e20}',
|
||||||
|
'{"id":1E+20}',
|
||||||
|
'{"id":12345678901234567890e-5}',
|
||||||
|
'{"id":-1.7976931348623157e308}',
|
||||||
|
'{"a":1234567890,"b":12345678,"c":9012345678}',
|
||||||
|
'[1234567890123456789,1,2,3]',
|
||||||
|
'{"12345678901234567890":1}',
|
||||||
|
'{"content":"call me at 15551234567890123456789 ok"}',
|
||||||
|
'{"content":"he said \\"1472109478688579732\\" loudly","id":1472109478688579732}',
|
||||||
|
'{"content":"trailing backslash \\\\","id":9007199254740993}',
|
||||||
|
'{"content":"0001472109478688579732"}',
|
||||||
|
'{"id":0001472109478688579732}',
|
||||||
|
'{"nested":{"arr":[{"id":1472109478688579732},{"id":15}]},"n":15}',
|
||||||
|
'{"content":"no digits at all"}',
|
||||||
|
'{}',
|
||||||
|
'[]',
|
||||||
|
'null',
|
||||||
|
'"1472109478688579732"',
|
||||||
|
'1472109478688579732',
|
||||||
|
'{"took":0.062,"id":1472109478688579732}',
|
||||||
|
];
|
||||||
|
|
||||||
|
type ParseResult = {ok: true; value: unknown} | {ok: false; error: string};
|
||||||
|
|
||||||
|
function toResult(parse: () => unknown): ParseResult {
|
||||||
|
try {
|
||||||
|
return {ok: true, value: parse()};
|
||||||
|
} catch (error) {
|
||||||
|
return {ok: false, error: String(error)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseWithDigitRunGate(jsonText: string, digitRun: number): ParseResult {
|
||||||
|
const gate = new RegExp(String.raw`\d{${digitRun}}`);
|
||||||
|
return toResult(() => JSON.parse(gate.test(jsonText) ? coerceUnsafeIntegersToStrings(jsonText) : jsonText));
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseUngated(jsonText: string): ParseResult {
|
||||||
|
return toResult(() => JSON.parse(coerceUnsafeIntegersToStrings(jsonText)));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('parseJsonPreservingLargeIntegers digit-run gate', () => {
|
||||||
|
it('matches the ungated scanner across the corpus', () => {
|
||||||
|
for (const body of CORPUS) {
|
||||||
|
expect(
|
||||||
|
toResult(() => parseJsonPreservingLargeIntegers(body)),
|
||||||
|
body,
|
||||||
|
).toEqual(parseUngated(body));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('exercises both the fast path and the scan', () => {
|
||||||
|
const gate = /\d{16}/;
|
||||||
|
expect(CORPUS.some((body) => !gate.test(body))).toBe(true);
|
||||||
|
expect(CORPUS.some((body) => gate.test(body))).toBe(true);
|
||||||
|
});
|
||||||
|
it('rejects malformed bodies identically on both paths', () => {
|
||||||
|
for (const body of ['{"id":00}', '{"id":0001472109478688579732}']) {
|
||||||
|
const gated = toResult(() => parseJsonPreservingLargeIntegers(body));
|
||||||
|
expect(gated.ok).toBe(false);
|
||||||
|
expect(gated, body).toEqual(parseUngated(body));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it('diverges from the ungated scanner when the gate is loosened to 17 digits', () => {
|
||||||
|
const broken = CORPUS.filter((body) => {
|
||||||
|
const loosened = parseWithDigitRunGate(body, 17);
|
||||||
|
const ungated = parseUngated(body);
|
||||||
|
return JSON.stringify(loosened) !== JSON.stringify(ungated);
|
||||||
|
});
|
||||||
|
expect(broken).toContain('{"id":9007199254740992}');
|
||||||
|
expect(broken).toContain('{"id":9999999999999999}');
|
||||||
|
expect(broken).toContain('{"id":-9007199254740992}');
|
||||||
|
});
|
||||||
|
it('agrees with the ungated scanner when the gate is tightened to 15 digits', () => {
|
||||||
|
for (const body of CORPUS) {
|
||||||
|
expect(parseWithDigitRunGate(body, 15), body).toEqual(parseUngated(body));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user