koala73/worldmonitor · error · ApiError

Expected at most 20 IATA airport codes

Error message

Expected at most 20 IATA airport codes

What it means

getAirportOpsSummary validates req.airports before use and throws ApiError 400 when the input clearly exceeds the 20-code limit or individual entries are too long. The library enforces MAX_OPS_AIRPORTS (20) and MAX_AIRPORT_INPUT_LENGTH to cap request size. A string input longer than MAX_AIRPORT_INPUT_LENGTH, or an array with more than 20 entries or entries exceeding the per-code length cap, fails this first guard.

Solutions

  1. Reduce the airports input to at most 20 IATA codes per request.
  2. Pass airports as an array of short strings rather than one concatenated long string.
  3. Validate/slice the list client-side before constructing ListAirportDelaysRequest-style airport-ops requests.
  4. Paginate or chunk large airport watchlists into batches of 20 with multiple calls.

Example fix

// before
getAirportOpsSummary(ctx, { airports: allAirports }); // allAirports.length === 120
// after
const batch = allAirports.slice(0, 20).map(c => c.trim().toUpperCase());
if (batch.some(c => c.length > 3)) throw new Error('invalid airport code length');
getAirportOpsSummary(ctx, { airports: batch });
Defensive patterns

Strategy: validation

Validate before calling

function canCallAirportOps(codes) {
  return Array.isArray(codes)
    && codes.length <= 20
    && codes.every(c => typeof c === 'string' && c.length <= 3);
}

Type guard

function isAirportOpsInput(raw) {
  if (typeof raw === 'string') return raw.length <= 3;
  return Array.isArray(raw) && raw.length <= 20
    && raw.every(c => typeof c === 'string' && c.length <= 3);
}

Try / catch

try {
  return await getAirportOpsSummary(ctx, req);
} catch (e) {
  if (e instanceof ApiError && e.status === 400) {
    return emptyOpsSummary(req); // or rethrow after chunking
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling getAirportOpsSummary with req.airports as a string longer than MAX_AIRPORT_INPUT_LENGTH, or as an array with more than 20 elements, or an array containing a non-string element or a string longer than MAX_AIRPORT_INPUT_LENGTH.

Common situations: A client joins codes into one long comma string instead of an array, a batch job passes hundreds of airports at once, or a caller forwards unvalidated query params straight into the request object.

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@e586b8b4b8 (2026-09-22). Data as JSON: /api/errors/b4d88c64dadb00b6. Report an issue: GitHub.

Appendix: source

Thrown at server/worldmonitor/aviation/v1/get-airport-ops-summary.ts:39

    isValidIntlCoverage,
    loadNotamClosures,
    IATA_RE,
} from './_shared';

const SEED_CACHE_KEY = 'aviation:delays:intl:v3';
const MAX_OPS_AIRPORTS = 20; // get_airport_ops_summary.proto repeated.max_items
const MAX_AIRPORT_INPUT_LENGTH = 1024;
const AVIATIONSTACK_AIRPORT_SET = new Set(AVIATIONSTACK_AIRPORTS);
export async function getAirportOpsSummary(
    ctx: ServerContext,
    req: GetAirportOpsSummaryRequest,
): Promise<GetAirportOpsSummaryResponse> {
    const raw: unknown = req.airports;
    if (raw != null && !(typeof raw === 'string'
        ? raw.length <= MAX_AIRPORT_INPUT_LENGTH
        : Array.isArray(raw) && raw.length <= MAX_OPS_AIRPORTS
            && raw.every(code => typeof code === 'string' && code.length <= MAX_AIRPORT_INPUT_LENGTH))) {
        throw new ApiError(400, 'Expected at most 20 IATA airport codes', '');
    }
    const rawAirports = parseStringArray(raw);
    if (rawAirports.length > MAX_OPS_AIRPORTS) {
        throw new ApiError(400, 'Expected at most 20 IATA airport codes', '');
    }
    const normalized = rawAirports.map(code => code.trim().toUpperCase());
    if (normalized.some(code => !IATA_RE.test(code))) {
        throw new ApiError(400, 'Expected three-letter IATA airport codes', '');
    }
    const requested = normalized.length > 0
        ? [...new Set(normalized)]
        : DEFAULT_WATCHED_AIRPORTS;

    const now = Date.now();

    try {
        const airports = MONITORED_AIRPORTS.filter(a => requested.includes(a.iata));
        const summaries: AirportOpsSummary[] = [];

View on GitHub (pinned to e586b8b4b8)