Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 2192x 2192x 2192x 2192x 2192x 2192x 2192x | import { isUnsafeKey } from '../safeFunctions/schema';
import type { SplitRule, SplitRuleSet } from './types';
/**
* Throws with the offending entry named - a rule set is usually merged from
* several customization layers, so the message must say which rule is wrong.
*/
function invalidEntry(id: string, message: string): never {
throw new Error(`Invalid split rule set entry "${id}": ${message}`);
}
/**
* Checks one rule set entry, and says whether it is included.
*
* @returns false for an entry with a `null` priority, which excludes it.
* @throws if the entry is not an object, has a priority that is neither `null`
* nor a finite number, or states an `id` that differs from its key.
*/
export function validateSplitRuleSetEntry(
id: string,
entry: { id?: string; priority?: number | null } | null | undefined
): boolean {
// A rule set is a plain object keyed by id, and `rules['__proto__'] = rule`
// replaces the prototype of the rule set instead of adding the rule.
Iif (isUnsafeKey(id)) {
invalidEntry(id, 'the id is not allowed; give the rule another id');
}
Iif (!entry || typeof entry !== 'object') {
invalidEntry(id, 'the entry must be an object');
}
Iif (entry.id !== undefined && entry.id !== id) {
invalidEntry(
id,
`the entry states id "${entry.id}", but its key is the id. Remove the id, or make it equal to the key.`
);
}
const { priority } = entry;
Iif (priority === null) {
return false;
}
Iif (typeof priority !== 'number' || !Number.isFinite(priority)) {
invalidEntry(
id,
`priority must be a finite number, or null to exclude the rule; got ${JSON.stringify(priority)}`
);
}
return true;
}
/**
* The included entries of a keyed rule set, in evaluation order, each with its
* key as its `id` and without its `priority`.
*
* - A `null` priority excludes the rule.
* - Rules are ordered by ascending priority. Equal priorities are ordered by id
* (UTF-16 code unit order, not a locale collation), so the result never
* depends on the order the keys were inserted in.
*
* Generic over the entry shape so that the rules as data and the compiled
* rules share one ordering implementation.
*
* @throws see {@link validateSplitRuleSetEntry}.
*/
export function orderSplitRuleSetEntries<
Entry extends { id?: string; priority?: number | null },
>(
ruleSet: Record<string, Entry>
): (Omit<Entry, 'priority'> & { id: string })[] {
if (!ruleSet || typeof ruleSet !== 'object' || Array.isArray(ruleSet)) {
throw new Error(
`A split rule set must be an object keyed by rule id: ${JSON.stringify(ruleSet)}`
);
}
const included: { id: string; priority: number; entry: Entry }[] = [];
for (const [id, entry] of Object.entries(ruleSet)) {
if (validateSplitRuleSetEntry(id, entry)) {
included.push({ id, priority: entry.priority as number, entry });
}
}
included.sort((a, b) => {
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
});
return included.map(({ id, entry }) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { priority, ...rule } = entry;
return { ...rule, id };
});
}
/**
* The rules of a {@link SplitRuleSet} in evaluation order - the order the split
* engine tries them in. See {@link orderSplitRuleSetEntries}.
*/
export function resolveSplitRuleSet(ruleSet: SplitRuleSet): SplitRule[] {
return orderSplitRuleSetEntries(ruleSet) as SplitRule[];
}
|