1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
//! Wrapper for the VATSIM APIs to get live data from the servers.
//!
//! These functions are [async], as they deal with HTTP requests. You'll need
//! to use an async runtime like [tokio] to run them.
//!
//! See the [struct] docs for usage information.
//!
//! [async]: https://doc.rust-lang.org/std/keyword.async.html
//! [tokio]: https://docs.rs/tokio/latest/tokio/
//! [struct]: Vatsim
//!
//! # Example
//!
//! ```rust,no_run
//! use vatsim_utils::live_api::Vatsim;
//!
//! # async fn _do() {
//! let api = Vatsim::new().await.unwrap();
//! // use `api` ...
//! # }
//! ```
use crate::{
errors::VatsimUtilError,
models::{Status, StatusData, TransceiverResponseEntry, V3ResponseData},
};
use log::debug;
use rand::seq::SliceRandom;
use reqwest::{Client, ClientBuilder};
/// Initial VATSIM API requests are made to this endpoint.
const STATUS_URL: &str = "https://status.vatsim.net/status.json";
/// Struct containing access to the VATSIM live APIs - those
/// listed on the [VATSIM Developer Info wiki page].
///
/// [VATSIM Developer Info wiki page]: https://github.com/vatsimnetwork/developer-info/wiki/Data-Feeds
#[derive(Debug)]
pub struct Vatsim {
client: Client,
v3_url: String,
transceivers_url: String,
}
impl Vatsim {
/// Create a new API struct instance.
///
/// Internally, this function also makes the API call to the status
/// endpoint to get the endpoint to make later API calls, which
/// is why this function is also `async`.
///
/// # Example
///
/// ```rust,no_run
/// use vatsim_utils::live_api::Vatsim;
///
/// # async fn _do() {
/// let api = Vatsim::new().await.unwrap();
/// # }
/// ```
///
/// # Errors
///
/// This function can fail if the HTTP requests to the VATSIM API status
/// endpoint fail, as this endpoint is required in order to get and
/// store URLs to later query for getting data.
///
/// # Panics
///
/// Will panic if the HTTP user agent cannot be constructed, which
/// should never happen.
pub async fn new() -> Result<Self, VatsimUtilError> {
debug!("Creating VATSIM struct instance");
let client = ClientBuilder::new()
.user_agent("github.com/celeo/vatsim_utils")
.build()
.expect("Invalid HTTP Agent");
let (v3_url, transceivers_url) = Vatsim::get_endpoint_urls(&client).await?;
Ok(Self {
client,
v3_url,
transceivers_url,
})
}
/// Get the V3 and transceivers URLs by querying the status endpoint.
async fn get_endpoint_urls(client: &Client) -> Result<(String, String), VatsimUtilError> {
debug!("Getting V3 url from status page");
let response = client.get(STATUS_URL).send().await?;
if !response.status().is_success() {
return Err(VatsimUtilError::InvalidStatusCode(
response.status().as_u16(),
));
}
let data: StatusData = (response.json::<Status>().await?).data;
let v3_url = data
.v3
.choose(&mut rand::thread_rng())
.expect("No VATSIM V3 API URLs returned")
.clone();
let transceivers_url = data
.transceivers
.choose(&mut rand::thread_rng())
.expect("No VATSIM transceivers API URLs returned")
.clone();
debug!("V3 URL: {v3_url}, transceiver URL: {transceivers_url}");
Ok((v3_url, transceivers_url))
}
/// Query the stored V3 endpoint.
///
/// This function sorts the pilots and controllers by their
/// callsigns, alphabetically, before returning.
///
/// # Example
///
/// ```rust,no_run
/// use vatsim_utils::live_api::Vatsim;
///
/// # async fn _do() {
/// let api = Vatsim::new().await.unwrap();
/// let data = api.get_v3_data().await.unwrap();
/// // use data ...
/// # }
/// ```
///
/// # Errors
///
/// This function can fail if the HTTP request fails or if the returned
/// data does not match the schemas of the models passed to the
/// deserializer.
///
/// # Panics
///
/// Could panic if the callsign `String`s fail `partial_cmp`.
pub async fn get_v3_data(&self) -> Result<V3ResponseData, VatsimUtilError> {
debug!("Getting current V3 data");
let response = self.client.get(&self.v3_url).send().await?;
if !response.status().is_success() {
return Err(VatsimUtilError::InvalidStatusCode(
response.status().as_u16(),
));
}
let mut data: V3ResponseData = response.json().await?;
data.pilots
.sort_by(|a, b| a.callsign.partial_cmp(&b.callsign).unwrap());
data.controllers
.sort_by(|a, b| a.callsign.partial_cmp(&b.callsign).unwrap());
Ok(data)
}
/// Get pilot transceiver frequency data.
///
/// # Example
///
/// ```rust,no_run
/// use vatsim_utils::live_api::Vatsim;
///
/// # async fn _do() {
/// let api = Vatsim::new().await.unwrap();
/// let data = api.get_transceivers_data().await.unwrap();
/// // use data ...
/// # }
/// ```
///
/// # Errors
///
/// This function can fail if the HTTP request fails or if the returned
/// data does not match the schemas of the models passed to the
/// deserializer.
pub async fn get_transceivers_data(
&self,
) -> Result<Vec<TransceiverResponseEntry>, VatsimUtilError> {
debug!("Getting current transceivers data");
let response = self.client.get(&self.transceivers_url).send().await?;
if !response.status().is_success() {
return Err(VatsimUtilError::InvalidStatusCode(
response.status().as_u16(),
));
}
let data = response.json().await?;
Ok(data)
}
}