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

A ValidationError thrown by createGetResilienceIndicators in server/worldmonitor/resilience/v1/get-resilience-indicators.ts when req.countryCode fails normalizedCountryCode() — i.e., it is not a string that trims/uppercases into a 2-letter ISO 3166-1 alpha-2 code. The RPC layer deliberately rejects bad input before touching caches or the seed reader, so clients get a structured field-level error instead of an internal failure.

Source

Thrown at server/worldmonitor/resilience/v1/get-resilience-indicators.ts:285

  const now = dependencies.now ?? (() => new Date());
  const readStaticMeta = dependencies.readStaticMeta
    ?? (() => strictResilienceSeedReader(RESILIENCE_STATIC_META_KEY));
  const hasInjectedBuildDependency = dependencies.reader != null
    || dependencies.now != null
    || dependencies.readStaticMeta != null;
  const responseCache = dependencies.responseCache === undefined
    ? hasInjectedBuildDependency
      ? null
      : cacheResilienceIndicatorResponse
    : dependencies.responseCache;

  return async (
    _ctx: ServerContext,
    req: GetResilienceIndicatorsRequest,
  ): Promise<GetResilienceIndicatorsResponse> => {
    const countryCode = normalizedCountryCode(req.countryCode);
    if (!countryCode) {
      throw new ValidationError([{
        field: 'countryCode',
        description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code',
      }]);
    }

    if (!hasInjectedBuildDependency && dependencies.responseCache === undefined) {
      const generation = await ensureResilienceScoreGenerationCached(countryCode);
      return toGetResilienceIndicatorsResponse(
        countryCode,
        null,
        generation.trace.snapshot,
        {
          now: now(),
          dataVersion: generation.trace.dataVersion,
          formula: generation.trace.formula,
          schemaVersion: generation.trace.schemaVersion,
          constructVersions: generation.trace.constructVersions,
        },

View on GitHub (pinned to 9361220cc0)

Solutions

  1. Send a valid ISO 3166-1 alpha-2 code, uppercase, exactly 2 letters (e.g., 'DE', 'US').
  2. Normalize on the client: value.trim().toUpperCase() and validate with /^[A-Z]{2}$/ before sending.
  3. Map names/ISO-3/demonym inputs through a lookup table to ISO-2 before calling.
  4. Handle the ValidationError response (field: 'countryCode') in the client by prompting the user to reselect a country rather than retrying blindly.

Example fix

// before
const res = await getResilienceIndicators({ countryCode: country.name }); // 'Germany'
// after
const cc = country.iso2?.trim().toUpperCase() ?? '';
if (!/^[A-Z]{2}$/.test(cc)) throw new Error(`Cannot map ${country.name} to ISO-2`);
const res = await getResilienceIndicators({ countryCode: cc });
Defensive patterns

Strategy: validation

Validate before calling

const cc = String(req.countryCode ?? '').trim().toUpperCase();
if (!/^[A-Z]{2}$/.test(cc)) throw new ValidationError([{ field: 'countryCode', description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code' }]);

Type guard

function hasValidCountryCode(req: { countryCode?: string | null }): req is { countryCode: string } {
  return typeof req.countryCode === 'string' && /^[A-Z]{2}$/.test(req.countryCode.trim().toUpperCase());
}

Try / catch

try {
  return await getResilienceIndicators(req);
} catch (err) {
  if (err.name === 'ValidationError' && err.fields?.some((f) => f.field === 'countryCode')) {
    return 400 response prompting reselection; // not retryable
  }
  throw err;
}

Prevention

When it happens

Trigger: Any call to getResilienceIndicators whose request has countryCode set to '', undefined, null, a lowercase-with-garbage string, an ISO-3 code ('USA'), a full country name ('Germany'), or a code longer/shorter than 2 letters.

Common situations: 1) Client sends a country name or numeric UN M49 code from a search box without mapping to ISO-2; 2) frontend sends lowercase 'de' from a locale tag split incorrectly ('de-DE'.split('-')[0] works, but 'de_DE' fails); 3) tests omit the field entirely; 4) version drift where an older client sends a deprecated 'cc' field while the API reads 'countryCode'.

Related errors


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