Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Safer types for responses with potentially empty data objects. #150

Merged
merged 2 commits into from
Jul 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/brave-geese-develop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@dgac/nmb2b-client': minor
---

Improve type safety of B2B replies when objects are potentially empty.

See https://github.com/DGAC/nmb2b-client-js/issues/149
16 changes: 9 additions & 7 deletions src/Airspace/queryCompleteAIXMDatasets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { instrument } from '../utils/instrumentation';
import { injectSendTime, responseStatusHandler } from '../utils/internals';
import { prepareSerializer } from '../utils/transformers';
import type { AirspaceClient } from './';
import type { AiracIdentifier, AIXMFile } from './types';
import type { CollapseEmptyObjectsToNull } from '../utils/types';

import type {
DateYearMonthDay,
Expand All @@ -14,11 +16,13 @@ export interface CompleteAIXMDatasetRequest {
queryCriteria: CompleteDatasetQueryCriteria;
}

export type CompleteAIXMDatasetReply = Reply & {
data: {
datasetSummaries: CompleteDatasetSummary[];
};
};
export type CompleteAIXMDatasetReply = CollapseEmptyObjectsToNull<
Reply & {
data: {
datasetSummaries: CompleteDatasetSummary[];
};
}
>;

type Values = CompleteAIXMDatasetRequest;
type Result = CompleteAIXMDatasetReply;
Expand Down Expand Up @@ -54,8 +58,6 @@ export default function prepareQueryCompleteAIXMDatasets(
);
}

import type { AiracIdentifier, AIXMFile } from './types';

interface CompleteDatasetSummary {
updateId: string;
publicationDate: DateYearMonthDay;
Expand Down
18 changes: 10 additions & 8 deletions src/Airspace/retrieveAUP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import { injectSendTime, responseStatusHandler } from '../utils/internals';
import type { SoapOptions } from '../soap';
import { prepareSerializer } from '../utils/transformers';
import { instrument } from '../utils/instrumentation';
import type { AUPId, AUP } from './types';
import type { Reply } from '../Common/types';
import type { CollapseEmptyObjectsToNull } from '../utils/types';

type Values = AUPRetrievalRequest;
type Result = AUPRetrievalReply;
Expand Down Expand Up @@ -36,16 +39,15 @@ export default function prepareRetrieveAUP(client: AirspaceClient): Resolver {
);
}

import type { AUPId, AUP } from './types';
import type { Reply } from '../Common/types';

export interface AUPRetrievalRequest {
aupId: AUPId;
returnComputed?: boolean;
}

export interface AUPRetrievalReply extends Reply {
data: {
aup: AUP;
};
}
export type AUPRetrievalReply = CollapseEmptyObjectsToNull<
Reply & {
data: {
aup: AUP;
};
}
>;
28 changes: 15 additions & 13 deletions src/Airspace/retrieveAUPChain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { instrument } from '../utils/instrumentation';
import { injectSendTime, responseStatusHandler } from '../utils/internals';
import { prepareSerializer } from '../utils/transformers';
import type { AirspaceClient } from './';
import type {
AirNavigationUnitId,
DateYearMonthDay,
Reply,
} from '../Common/types';

import type { AUPChain } from './types';
import type { CollapseEmptyObjectsToNull } from '../utils/types';

type Values = AUPChainRetrievalRequest;
type Result = AUPChainRetrievalReply;
Expand Down Expand Up @@ -38,21 +46,15 @@ export default function prepareRetrieveAUPChain(
);
}

import type {
AirNavigationUnitId,
DateYearMonthDay,
Reply,
} from '../Common/types';

import type { AUPChain } from './types';

export interface AUPChainRetrievalRequest {
chainDate: DateYearMonthDay;
amcIds?: AirNavigationUnitId[];
}

export type AUPChainRetrievalReply = Reply & {
data: {
chains: AUPChain[];
};
};
export type AUPChainRetrievalReply = CollapseEmptyObjectsToNull<
Reply & {
data: {
chains: AUPChain[];
};
}
>;
26 changes: 14 additions & 12 deletions src/Airspace/retrieveEAUPChain.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import type { AirspaceClient } from './';
import { injectSendTime, responseStatusHandler } from '../utils/internals';
import type { SoapOptions } from '../soap';
import { prepareSerializer } from '../utils/transformers';
import { instrument } from '../utils/instrumentation';
import { injectSendTime, responseStatusHandler } from '../utils/internals';
import { prepareSerializer } from '../utils/transformers';
import type { AirspaceClient } from './';
import type { DateYearMonthDay, Reply } from '../Common/types';

import type { CollapseEmptyObjectsToNull } from '../utils/types';
import type { EAUPChain } from './types';

type Values = EAUPChainRetrievalRequest;
type Result = EAUPChainRetrievalReply;
Expand Down Expand Up @@ -38,16 +42,14 @@ export default function prepareRetrieveEAUPChain(
);
}

import type { DateYearMonthDay, Reply } from '../Common/types';

import type { EAUPChain } from './types';

export interface EAUPChainRetrievalRequest {
chainDate: DateYearMonthDay;
}

export type EAUPChainRetrievalReply = Reply & {
data: {
chain: EAUPChain;
};
};
export type EAUPChainRetrievalReply = CollapseEmptyObjectsToNull<
Reply & {
data: {
chain: EAUPChain;
};
}
>;
6 changes: 3 additions & 3 deletions src/Common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export type ReplyStatus =
| 'CONFLICTING_UPDATE'
| 'INVALID_DATASET';

export interface Reply {
export type Reply = {
requestReceptionTime?: DateTimeSecond;
requestId?: string;
sendTime?: DateTimeSecond;
Expand All @@ -140,11 +140,11 @@ export interface Reply {
reason?: string;
}

export interface Request {
export type Request = {
endUserId?: string;
onBehalfOfUnit?: AirNavigationUnitId;
sendTime: DateTimeSecond;
}
};

export type ServiceGroup =
| 'AIRSPACE'
Expand Down
140 changes: 82 additions & 58 deletions src/Flight/queryFlightPlans.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,70 +59,94 @@ describe('queryFlightPlans', async () => {
}
});

test.runIf(shouldUseRealB2BConnection)('query known flight', async () => {
try {
if (!knownFlight || !('flight' in knownFlight)) {
return;
}
test.runIf(shouldUseRealB2BConnection)('query empty callsign', async () => {
const invalidCallsign = 'ABCDE';

const res = await Flight.queryFlightPlans({
aircraftId: invalidCallsign,
nonICAOAerodromeOfDeparture: false,
airFiled: false,
nonICAOAerodromeOfDestination: false,
estimatedOffBlockTime: {
wef: sub(new Date(), {
minutes: 30,
}),
unt: add(new Date(), {
minutes: 30,
}),
},
});

assert(knownFlight.flight.flightId.keys, 'Invalid flight');

const res = await Flight.queryFlightPlans({
aircraftId: knownFlight.flight.flightId.keys.aircraftId,
nonICAOAerodromeOfDeparture: false,
airFiled: false,
nonICAOAerodromeOfDestination: false,
estimatedOffBlockTime: {
wef: sub(knownFlight.flight.flightId.keys.estimatedOffBlockTime, {
minutes: 30,
}),
unt: add(knownFlight.flight.flightId.keys.estimatedOffBlockTime, {
minutes: 30,
}),
},
});

const { data } = res;

if (!data.summaries || data.summaries.length === 0) {
console.error(
'Query did not return any flight plan, this should never happen.',
);
return;
}
expect(res.data).toBe(null);
});

test.runIf(shouldUseRealB2BConnection && false)(
'query known flight',
async () => {
try {
if (!knownFlight || !('flight' in knownFlight)) {
return;
}

for (const f of data.summaries) {
if (!('lastValidFlightPlan' in f || 'currentInvalid' in f)) {
throw new Error(
'queryFlightPlans: either lastValidFlightPlan or currentInvalid should exist',
assert(knownFlight.flight.flightId.keys, 'Invalid flight');

const res = await Flight.queryFlightPlans({
aircraftId: knownFlight.flight.flightId.keys.aircraftId,
nonICAOAerodromeOfDeparture: false,
airFiled: false,
nonICAOAerodromeOfDestination: false,
estimatedOffBlockTime: {
wef: sub(knownFlight.flight.flightId.keys.estimatedOffBlockTime, {
minutes: 30,
}),
unt: add(knownFlight.flight.flightId.keys.estimatedOffBlockTime, {
minutes: 30,
}),
},
});

const { data } = res;

if (!data?.summaries || data.summaries.length === 0) {
console.error(
'Query did not return any flight plan, this should never happen.',
);
return;
}

if ('lastValidFlightPlan' in f) {
expect(f.lastValidFlightPlan).toMatchObject({
id: {
id: expect.any(String),
keys: {
aircraftId: expect.any(String),
aerodromeOfDeparture: expect.any(String),
aerodromeOfDestination: expect.any(String),
estimatedOffBlockTime: expect.any(Date),
for (const f of data.summaries) {
if (!('lastValidFlightPlan' in f || 'currentInvalid' in f)) {
throw new Error(
'queryFlightPlans: either lastValidFlightPlan or currentInvalid should exist',
);
}

if ('lastValidFlightPlan' in f) {
expect(f.lastValidFlightPlan).toMatchObject({
id: {
id: expect.any(String),
keys: {
aircraftId: expect.any(String),
aerodromeOfDeparture: expect.any(String),
aerodromeOfDestination: expect.any(String),
estimatedOffBlockTime: expect.any(Date),
},
},
},
status: expect.any(String),
});
} else if ('currentInvalid' in f) {
console.warn(
'Query returned a flight with a currentInvalid property',
);
status: expect.any(String),
});
} else if ('currentInvalid' in f) {
console.warn(
'Query returned a flight with a currentInvalid property',
);
}
}
} catch (err) {
if (err instanceof NMB2BError) {
console.log(inspect(err, { depth: 4 }));
}
}
} catch (err) {
if (err instanceof NMB2BError) {
console.log(inspect(err, { depth: 4 }));
}

throw err;
}
});
throw err;
}
},
);
});
4 changes: 2 additions & 2 deletions src/Flight/retrieveFlight.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('retrieveFlight', async () => {
requestedFlightFields: ['ftfmPointProfile'],
});

assert(res.data.flight?.ftfmPointProfile);
assert(res.data?.flight?.ftfmPointProfile);

res.data.flight.ftfmPointProfile.forEach((item) => {
expect(item).toEqual(
Expand Down Expand Up @@ -127,7 +127,7 @@ describe('retrieveFlight', async () => {
requestedFlightFields: ['aircraftType', 'delay'],
});

const flight = res.data.flight;
const flight = res.data?.flight;
expect(flight).toBeDefined();
expect(flight?.flightId.id).toEqual(
expect.stringMatching(/^A(A|T)[0-9]{8}$/),
Expand Down
Loading