mirror of
https://github.com/fluxerapp/fluxer.git
synced 2026-09-03 05:10:25 +03:00
perf(cassandra): drop allocations from the undefined param guard (#2152)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {describe, expect, it} from 'vitest';
|
||||
import {assertNoUndefinedParams} from './CassandraTypes';
|
||||
|
||||
function messageFor(path: string): string {
|
||||
return `Undefined value at "${path}". This project forbids undefined in Cassandra params; use null explicitly or omit the column via PATCH.`;
|
||||
}
|
||||
|
||||
function guardError(params: Record<string, unknown>): string {
|
||||
try {
|
||||
assertNoUndefinedParams(params);
|
||||
} catch (error) {
|
||||
return (error as Error).message;
|
||||
}
|
||||
throw new Error('Expected assertNoUndefinedParams to throw');
|
||||
}
|
||||
|
||||
describe('assertNoUndefinedParams', () => {
|
||||
it('accepts a realistic message row without throwing', () => {
|
||||
expect(() =>
|
||||
assertNoUndefinedParams({
|
||||
message_id: 123n,
|
||||
channel_id: 456n,
|
||||
content: 'hello',
|
||||
edited_at: null,
|
||||
pinned: false,
|
||||
created_at: new Date(0),
|
||||
blob: Buffer.from('x'),
|
||||
mention_users: [1n, 2n, 3n],
|
||||
nsfw_emojis: new Set(['a', 'b']),
|
||||
reactions: new Map([['a', 1]]),
|
||||
embeds: {title: 'a', fields: [{name: 'n', value: 'v'}], footer: {text: null}},
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('reports the dotted path of a top level undefined param', () => {
|
||||
expect(guardError({user_id: undefined})).toBe(messageFor(':user_id'));
|
||||
});
|
||||
|
||||
it('reports the dotted path of an undefined array element', () => {
|
||||
expect(guardError({mention_users: [1n, undefined, 3n]})).toBe(messageFor(':mention_users[1]'));
|
||||
});
|
||||
|
||||
it('reports the dotted path of an undefined set member', () => {
|
||||
expect(guardError({nsfw_emojis: new Set(['a', undefined])})).toBe(messageFor(':nsfw_emojis{set:1}'));
|
||||
});
|
||||
|
||||
it('reports the dotted path of an undefined map key', () => {
|
||||
expect(guardError({reactions: new Map([['a', 1] as const, [undefined, 2] as const])})).toBe(
|
||||
messageFor(':reactions{mapKey:1}'),
|
||||
);
|
||||
});
|
||||
|
||||
it('reports the dotted path of an undefined map value', () => {
|
||||
expect(
|
||||
guardError({
|
||||
reactions: new Map([
|
||||
['a', 1],
|
||||
['b', undefined],
|
||||
]),
|
||||
}),
|
||||
).toBe(messageFor(':reactions{mapVal:1}'));
|
||||
});
|
||||
|
||||
it('reports the dotted path of an undefined nested object property', () => {
|
||||
expect(guardError({embeds: {footer: {icon_url: undefined}}})).toBe(messageFor(':embeds.footer.icon_url'));
|
||||
});
|
||||
|
||||
it('reports the dotted path of an undefined value nested through mixed containers', () => {
|
||||
expect(guardError({embeds: [{fields: new Set([new Map([['inline', undefined]])])}]})).toBe(
|
||||
messageFor(':embeds[0].fields{set:0}{mapVal:0}'),
|
||||
);
|
||||
});
|
||||
|
||||
it('reports the first undefined in parameter order', () => {
|
||||
expect(guardError({a: 1, b: [undefined], c: undefined})).toBe(messageFor(':b[0]'));
|
||||
});
|
||||
});
|
||||
@@ -254,6 +254,41 @@ export function isUnsafePreparedStatement(query: string): boolean {
|
||||
return tokens.length >= 2 && tokens[0].toLowerCase() === 'select' && tokens[1] === '*';
|
||||
}
|
||||
|
||||
function hasUndefinedDeep(value: unknown): boolean {
|
||||
if (value === undefined) return true;
|
||||
if (value === null) return false;
|
||||
const t = typeof value;
|
||||
if (t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean') return false;
|
||||
if (value instanceof Date) return false;
|
||||
if (value instanceof Buffer) return false;
|
||||
if (t === 'object' && value.constructor?.name === 'LocalDate') return false;
|
||||
if (Array.isArray(value)) {
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
if (hasUndefinedDeep(value[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Set) {
|
||||
for (const v of value.values()) {
|
||||
if (hasUndefinedDeep(v)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (value instanceof Map) {
|
||||
for (const [k, v] of value.entries()) {
|
||||
if (hasUndefinedDeep(k) || hasUndefinedDeep(v)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (t === 'object') {
|
||||
const keys = Object.keys(value as Record<string, unknown>);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (hasUndefinedDeep((value as Record<string, unknown>)[keys[i]!])) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function assertNoUndefinedDeep(value: unknown, path: string): void {
|
||||
if (value === undefined) {
|
||||
throw new Error(
|
||||
@@ -297,8 +332,13 @@ function assertNoUndefinedDeep(value: unknown, path: string): void {
|
||||
}
|
||||
|
||||
export function assertNoUndefinedParams(params: Record<string, unknown>): void {
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
assertNoUndefinedDeep(v, `:${k}`);
|
||||
const keys = Object.keys(params);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const k = keys[i]!;
|
||||
const v = params[k];
|
||||
if (hasUndefinedDeep(v)) {
|
||||
assertNoUndefinedDeep(v, `:${k}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user