koala73/worldmonitor · warning · ValidationError

year must be 0 or between 1951 and ${currentYear}

Error message

year must be 0 or between 1951 and ${currentYear}

What it means

getDisplacementSummary requires req.year to be an integer that is either 0 (meaning 'latest') or within the supported UNHCR range 1951..currentYear. Anything else (floats, strings pre-coercion, years outside the range) throws a ValidationError carrying the field 'year' and the dynamic range message.

Solutions

  1. Clamp client-side: year = Math.round(year) and reject <1951 or >new Date().getFullYear(), allowing 0
  2. Send 0 when you want the latest data instead of new Date().getFullYear()
  3. Catch ValidationError and read the field/description array to prompt the user for a valid year

Example fix

// before
await getDisplacementSummary(ctx, { year: 1949 });
// after
const year = requested < 1951 ? 0 : requested; // 0 = latest
await getDisplacementSummary(ctx, { year });
Defensive patterns

Strategy: validation

Validate before calling

const y = req.year;
const valid = Number.isInteger(y) && (y === 0 || (y >= 1951 && y <= new Date().getFullYear()));
if (!valid) throw new Error(`year must be 0 or between 1951 and ${new Date().getFullYear()}`);

Type guard

const isValidYear = (v: unknown): v is number =>
  typeof v === 'number' && Number.isInteger(v) && (v === 0 || (v >= 1951 && v <= new Date().getFullYear()));

Try / catch

try {
  return await getDisplacementSummary(ctx, { year });
} catch (e) {
  if (e instanceof ValidationError && e.issues.some(i => i.field === 'year')) {
    return await getDisplacementSummary(ctx, { year: 0 }); // fall back to latest
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing year=1949 (before UNHCR reporting starts), year=2077 (beyond currentYear), 2023.5, or a non-integer; client sending the year as a string when the schema expects a number.

Common situations: Hardcoded historical-year presets predating 1951; date pickers producing fractional epochs; forgetting that 0 is the sentinel for 'current year' and blocking it client-side.

Understand the failure class

Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.

Related errors


AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15). Data as JSON: /api/errors/0a32951f63946e25. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/displacement/v1/get-displacement-summary.ts:16

import type {
  ServerContext,
  GetDisplacementSummaryRequest,
  GetDisplacementSummaryResponse,
} from '../../../../src/generated/server/worldmonitor/displacement/v1/service_server';
import { ValidationError } from '../../../../src/generated/server/worldmonitor/displacement/v1/service_server';
import { getCachedJson } from '../../../_shared/redis';

// Railway owns fetching, aggregation and publication; RPC callers only select seed data.
export async function getDisplacementSummary(
  _ctx: ServerContext,
  req: GetDisplacementSummaryRequest,
): Promise<GetDisplacementSummaryResponse> {
  const currentYear = new Date().getFullYear();
  if (!Number.isInteger(req.year) || (req.year !== 0 && (req.year < 1951 || req.year > currentYear))) {
    throw new ValidationError([{ field: 'year', description: `year must be 0 or between 1951 and ${currentYear}` }]);
  }
  const emptyResponse: GetDisplacementSummaryResponse = {
    summary: {
      year: req.year || currentYear,
      globalTotals: { refugees: 0, asylumSeekers: 0, idps: 0, stateless: 0, total: 0 },
      countries: [],
      topFlows: [],
    },
    fetchedAt: 0,
    dataAvailable: false,
  };

  try {
    // The current-year key can contain prior-year data when UNHCR has not published yet.
    const [seedData, seedMeta] = await Promise.all([
      getCachedJson(`displacement:summary:v1:${currentYear}`, true) as Promise<GetDisplacementSummaryResponse | null>,
      getCachedJson('seed-meta:displacement:summary', true) as Promise<{ fetchedAt?: number } | null>,
    ]);

View on GitHub (pinned to 7d06c8633d)