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
- Reduce the airports input to at most 20 IATA codes per request.
- Pass airports as an array of short strings rather than one concatenated long string.
- Validate/slice the list client-side before constructing ListAirportDelaysRequest-style airport-ops requests.
- 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
- Always slice or chunk airport lists to <= 20 before calls.
- Keep codes as arrays, not concatenated strings.
- Share a single client-side validation constant (20) with the server limit.
- Add unit tests for boundary sizes 20 and 21.
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
- Unsupported airport delay parameter
- Aircraft identifier is too long
- disruptionPct must be an integer from 0 to 100 for a…
- Dynamic ApiError(400, message) via local invalid() helper…
- Dynamic ApiError(400, message) via local invalid() helper…
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)