koala73/worldmonitor · error · ValidationError

countryCode must be a 2-letter ISO 3166-1 alpha-2 code

Error message

countryCode must be a 2-letter ISO 3166-1 alpha-2 code

What it means

getFiveFactorScorecard normalizes req.countryCode (trim + uppercase) and requires it to match /^[A-Z]{2}$/ before reading the scorecard snapshot. Anything else (empty, full country names, 3-letter codes, digits) throws this ValidationError on field 'countryCode'.

Source

Thrown at server/worldmonitor/scorecard/v1/get-five-factor-scorecard.ts:21

  GetFiveFactorScorecardResponse,
  ScorecardServiceHandler,
  ServerContext,
} from '../../../../src/generated/server/worldmonitor/scorecard/v1/service_server';
import { ValidationError } from '../../../../src/generated/server/worldmonitor/scorecard/v1/service_server';
// @ts-expect-error — JS module, no declaration file
import { captureSilentError } from '../../../../api/_sentry-edge.js';
import { markNoStoreFallbackResponse } from '../../../_shared/response-headers';
import { asFiveFactorSnapshot, readFiveFactorSnapshot, type ScorecardSnapshotReader } from './_read-snapshot';
import { toPublicCountryScorecard } from './_response';

export async function getFiveFactorScorecardWithReader(
  ctx: ServerContext,
  req: GetFiveFactorScorecardRequest,
  reader: ScorecardSnapshotReader,
): Promise<GetFiveFactorScorecardResponse> {
  const countryCode = String(req.countryCode || '').trim().toUpperCase();
  if (!/^[A-Z]{2}$/.test(countryCode)) {
    throw new ValidationError([{ field: 'countryCode', description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code' }]);
  }
  let snapshotValue: unknown;
  try {
    snapshotValue = await reader([countryCode]);
  } catch (error) {
    console.warn('[scorecard] snapshot read failed operation=get-five-factor-scorecard', error instanceof Error ? error.message : 'unknown');
    void captureSilentError(error, { tags: { route: 'scorecard/get-five-factor-scorecard', step: 'snapshot-read' } });
    return markNoStoreFallbackResponse(ctx.request, {
      unavailable: true,
      unavailableReason: 'scorecard-snapshot-unavailable',
    });
  }
  const snapshot = asFiveFactorSnapshot(snapshotValue);
  const record = snapshot?.countries[countryCode];
  if (!snapshot || !record) {
    return markNoStoreFallbackResponse(ctx.request, {
      unavailable: true,
      unavailableReason: snapshot ? 'country-unavailable' : 'scorecard-snapshot-unavailable',

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Send a 2-letter ISO 3166-1 alpha-2 code (e.g. 'DE', 'JP')
  2. Trim and uppercase the input client-side before sending (server uppercases too, so 'de' works)
  3. Validate with /^[A-Z]{2}$/ before the call
  4. Map ISO-3 or country names to alpha-2 in the client before invoking

Example fix

// before
await getFiveFactorScorecard({ countryCode: 'USA' })
// after
const code = String(raw).trim().toUpperCase();
if (!/^[A-Z]{2}$/.test(code)) throw new Error('countryCode must be alpha-2');
await getFiveFactorScorecard({ countryCode: code })
Defensive patterns

Strategy: validation

Validate before calling

function normalizeCountryCode(v) {
  const code = String(v || '').trim().toUpperCase();
  return /^[A-Z]{2}$/.test(code) ? code : null;
}

Type guard

function isIso2(v) {
  return typeof v === 'string' && /^[A-Z]{2}$/.test(v);
}

Try / catch

try {
  return await getFiveFactorScorecard({ countryCode });
} catch (e) {
  if (e.name === 'ValidationError' && e.field === 'countryCode') {
    return null; // or re-prompt with e.description
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling the five-factor scorecard endpoint with countryCode missing, empty string, 'USA', 'us' pre-normalization edge cases won't pass since it uppercases but length still matters, numeric input, or names like 'Germany'.

Common situations: Clients storing ISO-3 codes or display names; form inputs not constrained to 2 letters; query params arriving as numbers; regional codes like 'EU' that pass the regex but are not real countries (those pass validation and fail later at snapshot read).

Related errors


AI-assisted analysis of koala73/worldmonitor@9361220cc0 (2026-09-01). Data as JSON: /api/errors/bc528c676360f41b. Report an issue: GitHub.