mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-02 21:04:06 +03: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
|
||||
|
||||
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 {
|
||||
return char >= '0' && char <= '9';
|
||||
@@ -21,7 +22,7 @@ function isUnsafeIntegerToken(token: string): boolean {
|
||||
return digits > MAX_SAFE_INTEGER_DECIMAL;
|
||||
}
|
||||
|
||||
function coerceUnsafeIntegersToStrings(jsonText: string): string {
|
||||
export function coerceUnsafeIntegersToStrings(jsonText: string): string {
|
||||
let inString = false;
|
||||
let escaped = false;
|
||||
let i = 0;
|
||||
@@ -76,7 +77,7 @@ function coerceUnsafeIntegersToStrings(jsonText: string): string {
|
||||
}
|
||||
|
||||
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);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {parseJsonPreservingLargeIntegers} from '../LosslessJsonParser';
|
||||
import {coerceUnsafeIntegersToStrings, parseJsonPreservingLargeIntegers} from '../LosslessJsonParser';
|
||||
|
||||
describe('parseJsonPreservingLargeIntegers', () => {
|
||||
it('keeps safe integers as numbers', () => {
|
||||
@@ -41,3 +41,103 @@ describe('parseJsonPreservingLargeIntegers', () => {
|
||||
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