koala73/worldmonitor · warning · ApiError
Dynamic ApiError(400, message) via local invalid() helper (e
Error message
Dynamic ApiError(400, message) via local invalid() helper (e.g. 'Flight-search field is too long', 'Expected three-letter airport codes')
What it means
searchGoogleFlights uses the same local invalid() helper as the dates endpoint: any request string field longer than its cap throws ApiError(400, 'Flight-search field is too long'), and format checks can throw 'Expected three-letter airport codes'. It rejects malformed input before any upstream Google Flights call.
Solutions
- Trim and send 3-letter IATA codes for origin/destination
- Enforce a 16-character max on all string fields before the call
- Catch ApiError, show its message, and prompt the user to correct the airport fields
Example fix
// before
await searchGoogleFlights(ctx, { origin: userInput.origin, destination: 'LHR' });
// after
const origin = userInput.origin.trim().toUpperCase().slice(0, 3);
await searchGoogleFlights(ctx, { origin, destination: 'LHR' }); Defensive patterns
Strategy: validation
Validate before calling
const ok = [req.origin, req.destination].every(v => typeof v === 'string' && /^[A-Za-z]{3}$/.test(v.trim()));
if (!ok) throw new Error('origin and destination must be three-letter IATA codes'); Type guard
const isShortCode = (v: unknown): v is string => typeof v === 'string' && v.trim().length <= 16 && v.trim().length > 0;
Try / catch
try {
return await searchGoogleFlights(ctx, req);
} catch (e) {
if (e instanceof ApiError && e.status === 400) {
console.warn('flight search rejected:', e.message);
return null;
}
throw e;
} Prevention
- Bind search inputs to a coded airport list, never free text
- Normalize (trim/uppercase) before sending
- Mirror the server's 16-char bound in your form validation
When it happens
Trigger: Calling search-google-flights with req.origin/req.destination over 16 characters, or non-IATA values failing the three-letter airport check; overly long optional fields also routed through bounded().
Common situations: Autocomplete fallback text submitted instead of a selected airport code; whitespace-padded or concatenated strings from form fields; UI allowing free-form origin/destination input.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Dynamic ApiError(400, message) via local invalid() helper (e
- Could not resolve ${JSON.stringify(echoCountryInput(raw))} t
- provide exactly one of country_code, preset, or members.
- Aircraft identifier is too long
- Expected a six-character hexadecimal ICAO address
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/16e975c527b94c28.
Report an issue: GitHub.
Appendix: source
Thrown at server/worldmonitor/aviation/v1/search-google-flights.ts:21
SearchGoogleFlightsRequest,
SearchGoogleFlightsResponse,
} from '../../../../src/generated/server/worldmonitor/aviation/v1/service_server';
import { ApiError } from '../../../../src/generated/server/worldmonitor/aviation/v1/service_server';
import { IATA_RE } from './_shared';
// @ts-expect-error — JS module, no declaration file
import { sha256Hex } from '../../../../api/_crypto.js';
import { getRelayBaseUrl, getRelayHeaders } from '../../../_shared/relay';
import { parseStringArray } from '../../../_shared/parse-string-array';
import { normalizePassengerCount } from '../../../_shared/passenger-count';
import { cachedFetchJson } from '../../../_shared/redis';
const CACHE_TTL = 600;
export async function searchGoogleFlights(
_ctx: ServerContext,
req: SearchGoogleFlightsRequest,
): Promise<SearchGoogleFlightsResponse> {
const invalid = (message: string): never => { throw new ApiError(400, message, ''); };
const bounded = (value: string | undefined, max: number): string => {
if ((value?.length ?? 0) > max) invalid('Flight-search field is too long');
return (value ?? '').trim();
};
const origin = bounded(req.origin, 16).toUpperCase();
const destination = bounded(req.destination, 16).toUpperCase();
if (!IATA_RE.test(origin) || !IATA_RE.test(destination)) invalid('Expected three-letter airport codes');
const parseDate = (value: string): number => {
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) invalid('Expected YYYY-MM-DD dates');
const time = Date.parse(value + 'T00:00:00Z');
if (!Number.isFinite(time) || new Date(time).toISOString().slice(0, 10) !== value) invalid('Invalid calendar date');
return time;
};
const departureDate = bounded(req.departureDate, 10);
const returnDate = bounded(req.returnDate, 10);
const departureTime = parseDate(departureDate);
if (returnDate && parseDate(returnDate) < departureTime) invalid('Return date must not precede departure date');
const cabinClass = bounded(req.cabinClass, 32).toUpperCase() || 'ECONOMY';View on GitHub (pinned to 7d06c8633d)