flybywiresim / discord-bot-utils

Utilities Discord Bot created for the FlyByWire Discord server.
https://flybywiresim.com/
GNU Affero General Public License v3.0
5 stars 7 forks source link

feat: generic wrapper for JSON APIs #70

Open ExampleWasTaken opened 4 months ago

ExampleWasTaken commented 4 months ago

Overview

Using 3rd-party APIs in statically typed languages poses a challenge as you need some way to validate the type of the returned data to avoid type errors at runtime. So far, the codebase used TypeScript's any type on responses from 3rd-party APIs to access values on the returned data. As any basically means opting out of all type checking, it's best practice to avoid its usage wherever possible. Therefore, the upcoming lint overhaul will disallow any completely.

Refactoring all currently used API implementations to avoid using any is too complex to be included in the lint overhaul. This PR is a mix of feat and refactor: It introduces a new generic API wrapper and migrates all existing 3rd-party APIs to use it.

Description

The API wrapper uses the 3rd-party library Zod to validate the returned data against statically declared schemas. It leverages TypeScript's ability to implicitly infer types thereby enabling type checking on the returned data. Code completion on the returned data is another QoL feature added by this PR/library.

Basic usage

Details

Using the wrapper is as simple as declaring a Zod schema for the returned data: ```ts import { z } from 'zod'; export const PersonSchema = z.object({ name: z.string(), age: z.number(), }); export type Person = z.infer; ``` Then using the `fetchForeignAPI(request, schema)` method: ```ts import { Request } from 'node-fetch'; try { const data = await fetchForeignAPI(new Request('https://api.example.com/'), PersonSchema); data.[name, age] // code completion and type checking } catch (e) { if (e instanceof ZodError) { // full access to the error - note that fetchForeignAPI() already logs the error to the console as well as the endpoint from which the data was retrieved } } ``` For simplicity you can also just pass a `URL` object or `string` to `fetchForeignAPI()`.

Testing

A list of all modified commands can be found below. While testing make sure to use improbable inputs as well as more typical inputs. E.g. requesting station/taf/metar information from small airports, airports with almost no traffic etc. Consider any case where the command doesn't succeed as 'failed'. The goal is to get correct typing, not limit the data we can show/use.

Affected Commands

| Command | Status | |--------|--------| | `/vatsim data ` | ✅ | | `/vatsim events` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/8ea11a59-0112-4497-9df7-12b88f11d88b) | | `/station` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/edb7c93d-7697-44a2-86d0-5c397c83dcdc) | | `/taf` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/0f23ffad-5655-4518-b69e-9a966cc03157) | | `/metar` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/486e5d98-04dd-401a-bf1a-98b290c0fae3) | | `/live-flights` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/8cacfcf4-140a-45c3-b464-3fe69cb59cbf) | | `/simbrief-data retrieve` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/68e5b82f-d7e5-4ac2-a650-e275c16ddb9c) | | `/wolframalpha` | ![image](https://github.com/flybywiresim/discord-bot-utils/assets/58574351/865c1bd8-a27c-4f70-bc41-3aff7cd95c68) |

Additional Information

The /taf embed has been slightly modified to be more consistent. Instead of decoding the first part of the TAF only, it now only provides the raw string and links to our docs and https://e6bx.com/weather/{ICAO}/?showDecoded=1&focuspoint=tafdecoder which decodes the TAF for the queried airport.

Discord Username

examplewastaken

pdellaert commented 4 months ago

I like this in general, but needs a deeper review than I can do right now :)