Extend detailsRequestParam codec for area:schedule (one-way + round-trip + connections)
This commit is contained in:
@@ -160,3 +160,352 @@ describe("detailsRequestParam — roundtrip", () => {
|
|||||||
expect(decoded).toEqual(input);
|
expect(decoded).toEqual(input);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Schedule area
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
describe("detailsRequestParam — schedule area — build", () => {
|
||||||
|
it("4.1.2 Table 5 row 11: encodes one-way schedule route (no time, no connections)", () => {
|
||||||
|
const r: DetailsRequest = {
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
};
|
||||||
|
expect(buildDetailsRequestParam(r)).toBe("schedule-route-NBC-KHV-20220307-20220313");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encodes one-way with time range", () => {
|
||||||
|
expect(
|
||||||
|
buildDetailsRequestParam({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
}),
|
||||||
|
).toBe("schedule-route-NBC-KHV-20220307-20220313-06002200");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encodes one-way with connections C0 (direct-only)", () => {
|
||||||
|
expect(
|
||||||
|
buildDetailsRequestParam({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
connections: 0,
|
||||||
|
}),
|
||||||
|
).toBe("schedule-route-NBC-KHV-20220307-20220313-C0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encodes one-way with time + connections", () => {
|
||||||
|
expect(
|
||||||
|
buildDetailsRequestParam({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
connections: 0,
|
||||||
|
}),
|
||||||
|
).toBe("schedule-route-NBC-KHV-20220307-20220313-06002200-C0");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encodes round-trip without time or connections", () => {
|
||||||
|
expect(
|
||||||
|
buildDetailsRequestParam({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
returnTrip: {
|
||||||
|
departure: "KHV",
|
||||||
|
arrival: "NBC",
|
||||||
|
dateFrom: "20220320",
|
||||||
|
dateTo: "20220326",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe("schedule-route-NBC-KHV-20220307-20220313-KHV-NBC-20220320-20220326");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encodes round-trip with time + connections on both legs", () => {
|
||||||
|
expect(
|
||||||
|
buildDetailsRequestParam({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
connections: 0,
|
||||||
|
returnTrip: {
|
||||||
|
departure: "KHV",
|
||||||
|
arrival: "NBC",
|
||||||
|
dateFrom: "20220320",
|
||||||
|
dateTo: "20220326",
|
||||||
|
timeFrom: "0800",
|
||||||
|
timeTo: "1800",
|
||||||
|
connections: 1,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe(
|
||||||
|
"schedule-route-NBC-KHV-20220307-20220313-06002200-C0-KHV-NBC-20220320-20220326-08001800-C1",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("encodes round-trip with time on outbound only", () => {
|
||||||
|
expect(
|
||||||
|
buildDetailsRequestParam({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "SVO",
|
||||||
|
arrival: "LED",
|
||||||
|
dateFrom: "20220401",
|
||||||
|
dateTo: "20220407",
|
||||||
|
timeFrom: "1000",
|
||||||
|
timeTo: "2000",
|
||||||
|
returnTrip: {
|
||||||
|
departure: "LED",
|
||||||
|
arrival: "SVO",
|
||||||
|
dateFrom: "20220408",
|
||||||
|
dateTo: "20220414",
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).toBe("schedule-route-SVO-LED-20220401-20220407-10002000-LED-SVO-20220408-20220414");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("detailsRequestParam — schedule area — parse", () => {
|
||||||
|
it("returns null for schedule-flight (not a known schedule kind)", () => {
|
||||||
|
expect(parseDetailsRequestParam("schedule-flight-SU1234-20260515")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses one-way without time or connections", () => {
|
||||||
|
expect(parseDetailsRequestParam("schedule-route-NBC-KHV-20220307-20220313")).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses one-way with time range", () => {
|
||||||
|
expect(parseDetailsRequestParam("schedule-route-NBC-KHV-20220307-20220313-06002200")).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses one-way with connections (C0)", () => {
|
||||||
|
expect(parseDetailsRequestParam("schedule-route-NBC-KHV-20220307-20220313-C0")).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
connections: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses one-way with Cyrillic С0 (Angular spec compatibility)", () => {
|
||||||
|
// Cyrillic С (U+0421) should be accepted the same as Latin C
|
||||||
|
expect(parseDetailsRequestParam("schedule-route-NBC-KHV-20220307-20220313-С0")).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
connections: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses one-way with time + connections", () => {
|
||||||
|
expect(parseDetailsRequestParam("schedule-route-NBC-KHV-20220307-20220313-06002200-C0")).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
connections: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses round-trip without time or connections", () => {
|
||||||
|
expect(
|
||||||
|
parseDetailsRequestParam("schedule-route-NBC-KHV-20220307-20220313-KHV-NBC-20220320-20220326"),
|
||||||
|
).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
returnTrip: {
|
||||||
|
departure: "KHV",
|
||||||
|
arrival: "NBC",
|
||||||
|
dateFrom: "20220320",
|
||||||
|
dateTo: "20220326",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses round-trip with time + connections on both legs", () => {
|
||||||
|
expect(
|
||||||
|
parseDetailsRequestParam(
|
||||||
|
"schedule-route-NBC-KHV-20220307-20220313-06002200-C0-KHV-NBC-20220320-20220326-08001800-C1",
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
connections: 0,
|
||||||
|
returnTrip: {
|
||||||
|
departure: "KHV",
|
||||||
|
arrival: "NBC",
|
||||||
|
dateFrom: "20220320",
|
||||||
|
dateTo: "20220326",
|
||||||
|
timeFrom: "0800",
|
||||||
|
timeTo: "1800",
|
||||||
|
connections: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("parses round-trip with time on outbound only", () => {
|
||||||
|
expect(
|
||||||
|
parseDetailsRequestParam(
|
||||||
|
"schedule-route-SVO-LED-20220401-20220407-10002000-LED-SVO-20220408-20220414",
|
||||||
|
),
|
||||||
|
).toEqual({
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "SVO",
|
||||||
|
arrival: "LED",
|
||||||
|
dateFrom: "20220401",
|
||||||
|
dateTo: "20220407",
|
||||||
|
timeFrom: "1000",
|
||||||
|
timeTo: "2000",
|
||||||
|
returnTrip: {
|
||||||
|
departure: "LED",
|
||||||
|
arrival: "SVO",
|
||||||
|
dateFrom: "20220408",
|
||||||
|
dateTo: "20220414",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("detailsRequestParam — schedule area — roundtrip property", () => {
|
||||||
|
it.each<DetailsRequest>([
|
||||||
|
{
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
connections: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
connections: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
returnTrip: {
|
||||||
|
departure: "KHV",
|
||||||
|
arrival: "NBC",
|
||||||
|
dateFrom: "20220320",
|
||||||
|
dateTo: "20220326",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: "NBC",
|
||||||
|
arrival: "KHV",
|
||||||
|
dateFrom: "20220307",
|
||||||
|
dateTo: "20220313",
|
||||||
|
timeFrom: "0600",
|
||||||
|
timeTo: "2200",
|
||||||
|
connections: 0,
|
||||||
|
returnTrip: {
|
||||||
|
departure: "KHV",
|
||||||
|
arrival: "NBC",
|
||||||
|
dateFrom: "20220320",
|
||||||
|
dateTo: "20220326",
|
||||||
|
timeFrom: "0800",
|
||||||
|
timeTo: "1800",
|
||||||
|
connections: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])("round-trips %o", (input) => {
|
||||||
|
const encoded = buildDetailsRequestParam(input);
|
||||||
|
const decoded = parseDetailsRequestParam(encoded);
|
||||||
|
expect(decoded).toEqual(input);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
/**
|
/**
|
||||||
* Codec for the `?request=...` query parameter on Online-Board flight-details URLs.
|
* Codec for the `?request=...` query parameter on flight-details URLs.
|
||||||
* Per TZ §4.1.2 Table 5 row 6. The param encodes the parent search context so
|
|
||||||
* the mini-list can rebuild the list on refresh / deep-link entry.
|
|
||||||
*
|
*
|
||||||
* Forms (canonical):
|
* Online-Board forms per TZ §4.1.2 Table 5 row 6:
|
||||||
* onlineboard-flight-{flightNumber}-{yyyyMMdd}
|
* onlineboard-flight-{flightNumber}-{yyyyMMdd}
|
||||||
* onlineboard-departure-{iata}-{yyyyMMdd}[-{HHmmHHmm}]
|
* onlineboard-departure-{iata}-{yyyyMMdd}[-{HHmmHHmm}]
|
||||||
* onlineboard-arrival-{iata}-{yyyyMMdd}[-{HHmmHHmm}]
|
* onlineboard-arrival-{iata}-{yyyyMMdd}[-{HHmmHHmm}]
|
||||||
* onlineboard-route-{depIata}-{arrIata}-{yyyyMMdd}[-{HHmmHHmm}]
|
* onlineboard-route-{depIata}-{arrIata}-{yyyyMMdd}[-{HHmmHHmm}]
|
||||||
|
*
|
||||||
|
* Schedule forms per TZ §4.1.2 Table 5 row 11:
|
||||||
|
* schedule-route-{dep}-{arr}-{dateFrom}-{dateTo}[-{HHmmHHmm}][-C{n}]
|
||||||
|
* schedule-route-{dep}-{arr}-{dateFrom}-{dateTo}[-{HHmmHHmm}][-C{n}]-{arr}-{dep}-{returnDateFrom}-{returnDateTo}[-{HHmmHHmm}][-C{n}]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type DetailsRequest =
|
export type DetailsRequest =
|
||||||
@@ -28,9 +30,30 @@ export type DetailsRequest =
|
|||||||
date: string;
|
date: string;
|
||||||
timeFrom?: string;
|
timeFrom?: string;
|
||||||
timeTo?: string;
|
timeTo?: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
area: "schedule";
|
||||||
|
kind: "route";
|
||||||
|
departure: string;
|
||||||
|
arrival: string;
|
||||||
|
dateFrom: string; // yyyyMMdd (outbound dateFrom)
|
||||||
|
dateTo: string; // yyyyMMdd (outbound dateTo)
|
||||||
|
timeFrom?: string; // HHmm
|
||||||
|
timeTo?: string; // HHmm
|
||||||
|
connections?: number; // 0 = C0 (direct-only), 1 = C1 (one connection)
|
||||||
|
returnTrip?: {
|
||||||
|
departure: string;
|
||||||
|
arrival: string;
|
||||||
|
dateFrom: string;
|
||||||
|
dateTo: string;
|
||||||
|
timeFrom?: string;
|
||||||
|
timeTo?: string;
|
||||||
|
connections?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const AREA = "onlineboard";
|
const ONLINEBOARD_AREA = "onlineboard";
|
||||||
|
const SCHEDULE_AREA = "schedule";
|
||||||
|
|
||||||
function isDate(s: string | undefined): s is string {
|
function isDate(s: string | undefined): s is string {
|
||||||
return typeof s === "string" && /^\d{8}$/.test(s);
|
return typeof s === "string" && /^\d{8}$/.test(s);
|
||||||
@@ -40,7 +63,99 @@ function isTimeRange(s: string | undefined): s is string {
|
|||||||
return typeof s === "string" && /^\d{8}$/.test(s);
|
return typeof s === "string" && /^\d{8}$/.test(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isConnectionToken(s: string | undefined): boolean {
|
||||||
|
// Latin C or Cyrillic С (U+0421)
|
||||||
|
return typeof s === "string" && (s.startsWith("C") || s.startsWith("С")) && s.length >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseConnectionToken(s: string): number | undefined {
|
||||||
|
const numStr = s.slice(1);
|
||||||
|
const num = Number(numStr);
|
||||||
|
return Number.isNaN(num) ? undefined : num;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Schedule leg serialization helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface ScheduleLeg {
|
||||||
|
departure: string;
|
||||||
|
arrival: string;
|
||||||
|
dateFrom: string;
|
||||||
|
dateTo: string;
|
||||||
|
timeFrom?: string;
|
||||||
|
timeTo?: string;
|
||||||
|
connections?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildScheduleLegTokens(leg: ScheduleLeg): string[] {
|
||||||
|
const tokens: string[] = [leg.departure, leg.arrival, leg.dateFrom, leg.dateTo];
|
||||||
|
if (leg.timeFrom && leg.timeTo) {
|
||||||
|
tokens.push(`${leg.timeFrom}${leg.timeTo}`);
|
||||||
|
}
|
||||||
|
if (leg.connections !== undefined) {
|
||||||
|
tokens.push(`C${leg.connections}`);
|
||||||
|
}
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to consume one schedule leg from `parts` starting at `offset`.
|
||||||
|
* A leg starts with: {iata}-{iata}-{yyyyMMdd}-{yyyyMMdd} then optional time/conn tokens.
|
||||||
|
* Returns the parsed leg and the index of the next unused part, or null if invalid.
|
||||||
|
*/
|
||||||
|
function parseScheduleLeg(
|
||||||
|
parts: string[],
|
||||||
|
offset: number,
|
||||||
|
): { leg: ScheduleLeg; nextOffset: number } | null {
|
||||||
|
// Minimum: dep, arr, dateFrom, dateTo = 4 tokens
|
||||||
|
if (offset + 3 >= parts.length) return null;
|
||||||
|
|
||||||
|
const departure = parts[offset];
|
||||||
|
const arrival = parts[offset + 1];
|
||||||
|
const dateFrom = parts[offset + 2];
|
||||||
|
const dateTo = parts[offset + 3];
|
||||||
|
|
||||||
|
if (!departure || !arrival || !isDate(dateFrom) || !isDate(dateTo)) return null;
|
||||||
|
|
||||||
|
let nextOffset = offset + 4;
|
||||||
|
const leg: ScheduleLeg = { departure, arrival, dateFrom, dateTo };
|
||||||
|
|
||||||
|
// Consume optional timeRange
|
||||||
|
const maybeTime = parts[nextOffset];
|
||||||
|
if (maybeTime && isTimeRange(maybeTime) && !isConnectionToken(maybeTime)) {
|
||||||
|
leg.timeFrom = maybeTime.slice(0, 4);
|
||||||
|
leg.timeTo = maybeTime.slice(4, 8);
|
||||||
|
nextOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume optional connections token
|
||||||
|
const maybeConn = parts[nextOffset];
|
||||||
|
if (maybeConn && isConnectionToken(maybeConn)) {
|
||||||
|
const conn = parseConnectionToken(maybeConn);
|
||||||
|
if (conn !== undefined) {
|
||||||
|
leg.connections = conn;
|
||||||
|
}
|
||||||
|
nextOffset++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { leg, nextOffset };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Public API
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export function buildDetailsRequestParam(r: DetailsRequest): string {
|
export function buildDetailsRequestParam(r: DetailsRequest): string {
|
||||||
|
if (r.area === SCHEDULE_AREA) {
|
||||||
|
const outboundTokens = buildScheduleLegTokens(r);
|
||||||
|
const allTokens = [SCHEDULE_AREA, "route", ...outboundTokens];
|
||||||
|
if (r.returnTrip) {
|
||||||
|
allTokens.push(...buildScheduleLegTokens(r.returnTrip));
|
||||||
|
}
|
||||||
|
return allTokens.join("-");
|
||||||
|
}
|
||||||
|
|
||||||
switch (r.kind) {
|
switch (r.kind) {
|
||||||
case "flight":
|
case "flight":
|
||||||
return `${r.area}-flight-${r.flightNumber}-${r.date}`;
|
return `${r.area}-flight-${r.flightNumber}-${r.date}`;
|
||||||
@@ -60,7 +175,75 @@ export function parseDetailsRequestParam(raw: string): DetailsRequest | null {
|
|||||||
if (!raw) return null;
|
if (!raw) return null;
|
||||||
const parts = raw.split("-");
|
const parts = raw.split("-");
|
||||||
if (parts.length < 4) return null;
|
if (parts.length < 4) return null;
|
||||||
if (parts[0] !== AREA) return null;
|
|
||||||
|
const area = parts[0];
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Schedule area
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
if (area === SCHEDULE_AREA) {
|
||||||
|
const kind = parts[1];
|
||||||
|
if (kind !== "route") return null;
|
||||||
|
|
||||||
|
// parts[2..] are leg tokens starting from index 2
|
||||||
|
const outboundResult = parseScheduleLeg(parts, 2);
|
||||||
|
if (!outboundResult) return null;
|
||||||
|
|
||||||
|
const { leg: outbound, nextOffset } = outboundResult;
|
||||||
|
|
||||||
|
// Detect whether a second leg follows. A second leg starts with two IATA
|
||||||
|
// codes followed by two dates. Check that parts[nextOffset] and
|
||||||
|
// parts[nextOffset+1] are 3-letter IATA-ish codes and parts[nextOffset+2]
|
||||||
|
// and [nextOffset+3] are dates. This disambiguates a return trip from
|
||||||
|
// trailing garbage.
|
||||||
|
let returnTrip: ScheduleLeg | undefined;
|
||||||
|
if (nextOffset < parts.length) {
|
||||||
|
const maybeSecond = parseScheduleLeg(parts, nextOffset);
|
||||||
|
if (maybeSecond) {
|
||||||
|
returnTrip = maybeSecond.leg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: DetailsRequest = {
|
||||||
|
area: "schedule",
|
||||||
|
kind: "route",
|
||||||
|
departure: outbound.departure,
|
||||||
|
arrival: outbound.arrival,
|
||||||
|
dateFrom: outbound.dateFrom,
|
||||||
|
dateTo: outbound.dateTo,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (outbound.timeFrom !== undefined && outbound.timeTo !== undefined) {
|
||||||
|
result.timeFrom = outbound.timeFrom;
|
||||||
|
result.timeTo = outbound.timeTo;
|
||||||
|
}
|
||||||
|
if (outbound.connections !== undefined) {
|
||||||
|
result.connections = outbound.connections;
|
||||||
|
}
|
||||||
|
if (returnTrip) {
|
||||||
|
const rt: NonNullable<typeof result.returnTrip> = {
|
||||||
|
departure: returnTrip.departure,
|
||||||
|
arrival: returnTrip.arrival,
|
||||||
|
dateFrom: returnTrip.dateFrom,
|
||||||
|
dateTo: returnTrip.dateTo,
|
||||||
|
};
|
||||||
|
if (returnTrip.timeFrom !== undefined && returnTrip.timeTo !== undefined) {
|
||||||
|
rt.timeFrom = returnTrip.timeFrom;
|
||||||
|
rt.timeTo = returnTrip.timeTo;
|
||||||
|
}
|
||||||
|
if (returnTrip.connections !== undefined) {
|
||||||
|
rt.connections = returnTrip.connections;
|
||||||
|
}
|
||||||
|
result.returnTrip = rt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Online-Board area
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
if (area !== ONLINEBOARD_AREA) return null;
|
||||||
|
|
||||||
const kind = parts[1];
|
const kind = parts[1];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user