Use a neutral compact switch codec and clarify provider size limits

This commit is contained in:
Sameh Khamis committed 2026-10-09 18:47:04 -07:00
1 parent fbe118aab9
commit 3ba8c6d61b
11 files changed
+127 -98

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
const MAX_SWITCHES_LENGTH = 256;
const ENTRY = /^([a-z][a-z0-9-]*)-([01])$/;
/** Canonical, bounded boolean switches keyed by stable slugs. */
export function normalizeIdSwitches(value, reservedIds = []) {
if (typeof value !== 'string' || value.length > MAX_SWITCHES_LENGTH)
return null;
if (!value) return '';
const entries = new Map();
for (const field of value.split('*')) {
const match = ENTRY.exec(field);
if (!match || reservedIds.includes(match[1]) || entries.has(match[1]))
return null;
entries.set(match[1], match[2] === '1');
}
return [...entries]
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
.map(([id, on]) => `${id}-${on ? '1' : '0'}`)
.join('*');
}
/** Encode by stable IDs rather than registration order or generated tokens. */
export function encodeIdSwitches(switches) {
const value = [...switches]
.map(([id, on]) => `${id}-${on === true ? '1' : '0'}`)
.join('*');
if (value.length > MAX_SWITCHES_LENGTH)
throw new RangeError(
`Switch IDs exceed the ${MAX_SWITCHES_LENGTH}-character share-link limit`,
);
const normalized = normalizeIdSwitches(value);
if (normalized === null) throw new TypeError('Invalid boolean switch IDs');
return normalized;
}
/** A Map keeps switch IDs separate from object prototype properties. */
export function decodeIdSwitches(value) {
const normalized = normalizeIdSwitches(value);
return new Map(
normalized
? normalized.split('*').map((entry) => {
const [, id, on] = ENTRY.exec(entry);
return [id, on === '1'];
})
: [],
);
}
+4 -4
View File
@@ -1,4 +1,4 @@
import { normalizeProviderSwitches } from '../layers/streetLevel/providerSwitches.js';
import { normalizeIdSwitches } from './idSwitches.js';
import reservationRows from './layerStateTokenReservations.json' with { type: 'json' };
const VALID_DISPOSITIONS = new Set([
@@ -418,9 +418,9 @@ const OPTION_GROUPS = Object.freeze({
key: 'providerSwitches',
token: 'r',
defaultValue: '',
normalize: normalizeProviderSwitches,
encode: normalizeProviderSwitches,
decode: normalizeProviderSwitches,
normalize: (value) => normalizeIdSwitches(value, ['mapillary']),
encode: normalizeIdSwitches,
decode: (value) => normalizeIdSwitches(value, ['mapillary']),
}),
enumOption('pano', 'p', 'all', ['all', 'pano', 'flat'], {
all: 'a',
+2 -7
View File
@@ -98,16 +98,11 @@ function withPublishedBase(
}
function writeCodecFixture(cwd, rows, entries) {
const providerCodec = path.join(
cwd,
'src/layers/streetLevel/providerSwitches.js',
);
const providerCodec = path.join(cwd, 'src/data/idSwitches.js');
mkdirSync(path.dirname(providerCodec), { recursive: true });
writeFileSync(
providerCodec,
readFileSync(
new URL('../layers/streetLevel/providerSwitches.js', import.meta.url),
),
readFileSync(new URL('./idSwitches.js', import.meta.url)),
);
const source = readFileSync(CODEC_SOURCE_PATH, 'utf8');
const registryEnd = ']);\n\nexport const REGISTERED_LAYER_IDS';