{"record":{"id":"28a945d30d1ae7db","repo":"koala73/worldmonitor","slug":"countrycode-must-be-a-2-letter-iso-3166-1-alpha-2-28a945","errorCode":null,"errorMessage":"countryCode must be a 2-letter ISO 3166-1 alpha-2 code","messagePattern":"countryCode must be a 2-letter ISO 3166-1 alpha-2 code","errorType":"validation","errorClass":"ValidationError","httpStatus":400,"severity":"error","filePath":"server/worldmonitor/resilience/v1/get-resilience-indicators.ts","lineNumber":285,"sourceCode":"  const now = dependencies.now ?? (() => new Date());\n  const readStaticMeta = dependencies.readStaticMeta\n    ?? (() => strictResilienceSeedReader(RESILIENCE_STATIC_META_KEY));\n  const hasInjectedBuildDependency = dependencies.reader != null\n    || dependencies.now != null\n    || dependencies.readStaticMeta != null;\n  const responseCache = dependencies.responseCache === undefined\n    ? hasInjectedBuildDependency\n      ? null\n      : cacheResilienceIndicatorResponse\n    : dependencies.responseCache;\n\n  return async (\n    _ctx: ServerContext,\n    req: GetResilienceIndicatorsRequest,\n  ): Promise<GetResilienceIndicatorsResponse> => {\n    const countryCode = normalizedCountryCode(req.countryCode);\n    if (!countryCode) {\n      throw new ValidationError([{\n        field: 'countryCode',\n        description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code',\n      }]);\n    }\n\n    if (!hasInjectedBuildDependency && dependencies.responseCache === undefined) {\n      const generation = await ensureResilienceScoreGenerationCached(countryCode);\n      return toGetResilienceIndicatorsResponse(\n        countryCode,\n        null,\n        generation.trace.snapshot,\n        {\n          now: now(),\n          dataVersion: generation.trace.dataVersion,\n          formula: generation.trace.formula,\n          schemaVersion: generation.trace.schemaVersion,\n          constructVersions: generation.trace.constructVersions,\n        },","sourceCodeStart":267,"sourceCodeEnd":303,"githubUrl":"https://github.com/koala73/worldmonitor/blob/9361220cc013571781071f0206e4d80fd14b2f7f/server/worldmonitor/resilience/v1/get-resilience-indicators.ts#L267-L303","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","solutions":["Send a valid ISO 3166-1 alpha-2 code, uppercase, exactly 2 letters (e.g., 'DE', 'US').","Normalize on the client: value.trim().toUpperCase() and validate with /^[A-Z]{2}$/ before sending.","Map names/ISO-3/demonym inputs through a lookup table to ISO-2 before calling.","Handle the ValidationError response (field: 'countryCode') in the client by prompting the user to reselect a country rather than retrying blindly."],"exampleFix":"// before\nconst res = await getResilienceIndicators({ countryCode: country.name }); // 'Germany'\n// after\nconst cc = country.iso2?.trim().toUpperCase() ?? '';\nif (!/^[A-Z]{2}$/.test(cc)) throw new Error(`Cannot map ${country.name} to ISO-2`);\nconst res = await getResilienceIndicators({ countryCode: cc });","handlingStrategy":"validation","validationCode":"const cc = String(req.countryCode ?? '').trim().toUpperCase();\nif (!/^[A-Z]{2}$/.test(cc)) throw new ValidationError([{ field: 'countryCode', description: 'countryCode must be a 2-letter ISO 3166-1 alpha-2 code' }]);","typeGuard":"function hasValidCountryCode(req: { countryCode?: string | null }): req is { countryCode: string } {\n  return typeof req.countryCode === 'string' && /^[A-Z]{2}$/.test(req.countryCode.trim().toUpperCase());\n}","tryCatchPattern":"try {\n  return await getResilienceIndicators(req);\n} catch (err) {\n  if (err.name === 'ValidationError' && err.fields?.some((f) => f.field === 'countryCode')) {\n    return 400 response prompting reselection; // not retryable\n  }\n  throw err;\n}","preventionTips":["Validate ISO-2 format client-side before every RPC call","Use a country picker bound to ISO-2 codes, never free text","Map locale tags / names to ISO-2 with a lookup table","Snapshot-test request builders so countryCode is always populated"],"tags":["validation","country-code","validationerror","rpc"],"backgroundTag":"invalid-country-code","analyzedSha":"9361220cc013571781071f0206e4d80fd14b2f7f","analyzedAt":"2026-09-01T10:32:37.851Z","contentChangedAt":"2026-09-01T10:32:37.851Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}