Skip to content

Commit 540557a

Browse files
migrate taf cmd to zod
1 parent 31c2ea3 commit 540557a

File tree

6 files changed

+46
-38
lines changed

6 files changed

+46
-38
lines changed

src/commands/utils/taf.ts

+28-36
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ApplicationCommandOptionType, ApplicationCommandType, Colors } from 'discord.js';
2-
import fetch from 'node-fetch';
3-
import { constantsConfig, slashCommand, slashCommandStructure, makeEmbed, makeLines, Logger } from '../../lib';
2+
import { Request } from 'node-fetch';
3+
import { ZodError } from 'zod';
4+
import { Logger, TAF, TafSchema, fetchData, makeEmbed, makeLines, slashCommand, slashCommandStructure } from '../../lib';
45

56
const data = slashCommandStructure({
67
name: 'taf',
@@ -22,6 +23,12 @@ const noQueryEmbed = makeEmbed({
2223
color: Colors.Red,
2324
});
2425

26+
const errorEmbed = (error: string) => makeEmbed({
27+
title: 'TAF Error',
28+
description: error,
29+
color: Colors.Red,
30+
});
31+
2532
export default slashCommand(data, async ({ interaction }) => {
2633
await interaction.deferReply();
2734

@@ -42,47 +49,32 @@ export default slashCommand(data, async ({ interaction }) => {
4249
return interaction.editReply({ embeds: [noQueryEmbed] });
4350
}
4451

52+
let taf: TAF;
4553
try {
46-
const tafReport: any = await fetch(`https://avwx.rest/api/taf/${icao}`, {
54+
taf = await fetchData<TAF>(new Request(`https://avwx.rest/api/taf/${icao}`, {
4755
method: 'GET',
4856
headers: { Authorization: tafToken },
49-
}).then((res) => res.json());
50-
51-
if (tafReport.error) {
52-
const invalidEmbed = makeEmbed({
53-
title: `TAF Error | ${icao.toUpperCase()}`,
54-
description: tafReport.error,
55-
color: Colors.Red,
56-
});
57-
return interaction.editReply({ embeds: [invalidEmbed] });
57+
}), TafSchema);
58+
} catch (e) {
59+
if (e instanceof ZodError) {
60+
return interaction.editReply({ embeds: [errorEmbed('The API returned unknown data.')] });
5861
}
59-
const getClouds = (clouds: any) => {
60-
const retClouds = [];
61-
for (const cloud of clouds) {
62-
retClouds.push(cloud.repr);
63-
}
64-
return retClouds.join(', ');
65-
};
66-
const tafEmbed = makeEmbed({
67-
title: `TAF Report | ${tafReport.station}`,
68-
description: makeLines([
69-
'**Raw Report**',
70-
tafReport.raw,
62+
return interaction.editReply({ embeds: [errorEmbed(`An error occurred while fetching the latest TAF for ${icao.toUpperCase()}.`)] });
63+
}
7164

72-
'',
73-
'**Basic Report:**',
74-
`**Time Forecasted:** ${tafReport.time.dt}`,
75-
`**Forecast Start Time:** ${tafReport.start_time.dt}`,
76-
`**Forecast End Time:** ${tafReport.end_time.dt}`,
77-
`**Visibility:** ${tafReport.forecast[0].visibility.repr} ${Number.isNaN(+tafReport.forecast[0].visibility.repr) ? '' : tafReport.units.visibility}`,
78-
`**Wind:** ${tafReport.forecast[0].wind_direction.repr}${tafReport.forecast[0].wind_direction.repr === 'VRB' ? '' : constantsConfig.units.DEGREES} at ${tafReport.forecast[0].wind_speed.repr} ${tafReport.units.wind_speed}`,
79-
`**Clouds:** ${getClouds(tafReport.forecast[0].clouds)}`,
80-
`**Flight Rules:** ${tafReport.forecast[0].flight_rules}`,
81-
]),
65+
try {
66+
const tafEmbed = makeEmbed({
67+
title: `TAF Report | ${taf.station}`,
68+
description: makeLines(['**Raw Report**', ...taf.forecast.map((forecast, i) => {
69+
if (i === 0) {
70+
return `${taf.station} ${forecast.raw}`;
71+
}
72+
return forecast.raw;
73+
})]),
8274
fields: [
8375
{
84-
name: 'Unsure of how to read the raw report?',
85-
value: 'Please refer to our guide [here.](https://docs.flybywiresim.com/pilots-corner/airliner-flying-guide/weather/#taf-example-decoded)',
76+
name: 'Unsure of how to read the report?',
77+
value: `Please refer to our guide [here](https://docs.flybywiresim.com/pilots-corner/airliner-flying-guide/weather/#taf-example-decoded) or see above report decoded [here](https://e6bx.com/weather/${taf.station}/?showDecoded=1&focuspoint=tafdecoder).`,
8678
inline: false,
8779
},
8880
],

src/lib/apis/fetchData.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fetch, { Request } from 'node-fetch';
22
import { ZodSchema } from 'zod';
3+
import { Logger } from '../logger';
34

45
/**
56
* Fetch data from any API endpoint that returns JSON formatted data.
@@ -26,6 +27,8 @@ export const fetchData = async <ReturnType = unknown>(request: Request, zodSchem
2627
const result = zodSchema.safeParse(data);
2728

2829
if (!result.success) {
30+
Logger.error('[zod] Data validation failed:');
31+
result.error.issues.forEach((issue) => Logger.error(`Code: ${issue.code}, Path: ${issue.path.join('.')}, Message: ${issue.message}`));
2932
throw result.error;
3033
}
3134

src/lib/apis/zodSchemas/tafSchemas.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { z } from 'zod';
2+
3+
export const ForecastSchema = z.object({ raw: z.string() });
4+
5+
export const TafSchema = z.object({
6+
raw: z.string(),
7+
station: z.string(),
8+
forecast: z.array(ForecastSchema),
9+
});
10+
11+
export type Forecast = z.infer<typeof ForecastSchema>;
12+
export type TAF = z.infer<typeof TafSchema>;

src/lib/index.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ export * from './schedulerJobs/postBirthdays';
2424

2525
// API Wrapper
2626
export * from './apis/fetchData';
27-
export * from './apis/zodSchemas/vatsimEvents';
28-
export * from './apis/zodSchemas/vatsimData';
27+
export * from './apis/zodSchemas/vatsimEventsSchemas';
28+
export * from './apis/zodSchemas/vatsimDataSchemas';
29+
export * from './apis/zodSchemas/tafSchemas';

0 commit comments

Comments
 (0)