spb/countryatlas
Public
1/**2 * URL contract of `/rankings/[indicator]`:3 * ?year=2020 ranking year (default: latest; the API falls back to the nearest available year)4 * &group=oecd group slug (world · regions · continents · income groups · organisations); default world5 * &sort=asc|desc default = the API's ("asc" when lower is better, else "desc")6 * &highlight=canada country slug pinned at the top of the list7 * &q=fra text filter within the ranking8 * &history=CAN,USA,FRA countries of the rank-over-time panel (≤ 5 ISO3)9 * Canonical URL = path + `group` (when not world); `year`, `sort`, `highlight`, `q`, `history` are volatile.10 */11export interface RankingState {12 year: number | null;13 group: string;14 sort: 'asc' | 'desc' | null;15 highlight: string | null;16 q: string;17 history: string[];18}1920type ParamsLike = { get(name: string): string | null } | Record<string, string | string[] | undefined>;21function read(params: ParamsLike, key: string): string | null {22 if (typeof (params as { get?: unknown }).get === 'function') return (params as { get(name: string): string | null }).get(key);23 const v = (params as Record<string, string | string[] | undefined>)[key];24 return Array.isArray(v) ? (v[0] ?? null) : (v ?? null);25}2627export function parseRankingState(params: ParamsLike): RankingState {28 const y = Number(read(params, 'year'));29 const sort = read(params, 'sort');30 const group = (read(params, 'group') ?? 'world').toLowerCase().replace(/[^a-z0-9-]/g, '') || 'world';31 const highlight = (read(params, 'highlight') ?? '').toLowerCase().replace(/[^a-z0-9-]/g, '') || null;32 const history = (read(params, 'history') ?? '')33 .split(',')34 .map((s) => s.trim().toUpperCase())35 .filter((s) => /^[A-Z]{3}$/.test(s))36 .slice(0, 5);37 return { year: Number.isInteger(y) && y >= 1800 && y <= 2100 ? y : null, group, sort: sort === 'asc' || sort === 'desc' ? sort : null, highlight, q: (read(params, 'q') ?? '').slice(0, 60), history };38}3940export function rankingQuery(state: Partial<RankingState>): string {41 const p = new URLSearchParams();42 if (state.year != null) p.set('year', String(state.year));43 if (state.group && state.group !== 'world') p.set('group', state.group);44 if (state.sort) p.set('sort', state.sort);45 if (state.highlight) p.set('highlight', state.highlight);46 if (state.q) p.set('q', state.q);47 if (state.history?.length) p.set('history', state.history.join(','));48 const s = p.toString();49 return s ? `?${s}` : '';50}51