const numberPattern = /^\d+$/; const letterPattern = /^[a-z]$/i; // ranges: 3 is exactly 3, 2- is at least 2, -4 is at most 4, 2-4 is between 2 and 4 inclusive // comma is accepted as a legacy separator for closed ranges, e.g. age=22,30 from older links // values not matching the pattern invalidate the range, which keeps them safe to use in queries export function parseRange(rangeString, { pattern = numberPattern, parse = Number } = {}) { const parts = String(rangeString ?? '').trim().split(/[-,]/); if (parts.length > 2 || parts.every((part) => part === '') || parts.some((part) => part !== '' && !pattern.test(part))) { return null; } const [minString, maxString = minString] = parts; // no separator means an exact value return { min: minString ? parse(minString) : null, max: maxString ? parse(maxString) : null, }; } // single letter cup sizes, matching the cup filter's regex character class export function parseLetterRange(rangeString) { return parseRange(rangeString, { pattern: letterPattern, parse: (letter) => letter.toUpperCase(), }); } export function formatRange(range) { if (!range || (range.min === null && range.max === null)) { return undefined; } if (range.min === range.max) { return String(range.min); } return `${range.min ?? ''}-${range.max ?? ''}`; }