ourairports/
lib.rs

1//! Rust interface for handling [OurAirports data](https://ourairports.com/data/).
2//!
3//! # Examples
4//! Retrieving airport data
5//! ```
6//! use ourairports::airports::*;
7//!
8//! fn main() -> Result<(), Box<dyn std::error::Error>> {
9//!     let airports = get_airports_csv()?;
10//!
11//!     // London Heathrow Airport (ICAO: EGLL, IATA: LHR)
12//!     let heathrow_airport = airports.get(&2434).unwrap();
13//!     assert_eq!(2434, heathrow_airport.id());
14//!     assert_eq!("EGLL", heathrow_airport.ident());
15//!     assert_eq!("LHR", heathrow_airport.iata_code());
16//!     assert_eq!(&AirportType::LargeAirport, heathrow_airport.airport_type());
17//!
18//!    Ok(())
19//! }
20//! ```
21//!
22//! # Credits
23//! The descriptions for many of the fields and enum variants is adapted from the OurAirports
24//! [data dictionary](https://ourairports.com/help/data-dictionary.html) and
25//! [map legend](https://ourairports.com/help/#legend).
26
27
28use log::debug;
29use reqwest::blocking::Client;
30use serde::de::{self, Unexpected};
31use serde::{Deserialize, Deserializer, Serialize};
32
33pub mod airport_frequencies;
34pub mod airports;
35pub mod countries;
36pub mod navaids;
37pub mod regions;
38pub mod runways;
39
40/// Type of all ID fields.
41pub type Id = u64;
42
43/// Error type for errors in fetching OurAirports data (e.g. [`airports::get_airports_csv()`])
44#[derive(thiserror::Error, Debug)]
45pub enum FetchError {
46    #[error("Network error: {0}")]
47    NetworkError(#[from] reqwest::Error),
48    #[error("Error in deserializing: {0}")]
49    DeserializeError(#[from] csv::Error),
50}
51
52/// List of allowed continent values.
53#[derive(Deserialize, Serialize, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
54pub enum Continent {
55    #[serde(rename = "AF")]
56    Africa,
57    #[serde(rename = "AN")]
58    Antarctica,
59    #[serde(rename = "AS")]
60    Asia,
61    #[serde(rename = "EU")]
62    Europe,
63    #[serde(rename = "NA")]
64    NorthAmerica,
65    #[serde(rename = "OC")]
66    Oceania,
67    #[serde(rename = "SA")]
68    SouthAmerica,
69}
70
71/// Trait for converting OurAirports data into JSON string.
72pub trait ToJsonString {
73    /// Serialize an OurAirports data to string of JSON.
74    fn to_json_string(&self) -> serde_json::Result<String>
75    where
76        Self: serde::Serialize,
77    {
78        serde_json::to_string(&self)
79    }
80
81    /// Serialize an OurAirports data to string of JSON with pretty printing.
82    fn to_json_string_pretty(&self) -> serde_json::Result<String>
83    where
84        Self: serde::Serialize,
85    {
86        serde_json::to_string_pretty(&self)
87    }
88}
89
90/// Converts a string to a boolean based on "yes" and "no"
91fn bool_from_str<'de, D>(deserializer: D) -> Result<bool, D::Error>
92where
93    D: Deserializer<'de>,
94{
95    match String::deserialize(deserializer)?.to_lowercase().as_str() {
96        "yes" | "1" => Ok(true),
97        "no" | "0" => Ok(false),
98        other => Err(de::Error::invalid_value(
99            Unexpected::Str(other),
100            &"Value must be yes and no or 1 and 0",
101        )),
102    }
103}
104
105/// Transforms a comma-separated string to a vector.
106fn vec_string_from_string<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
107where
108    D: Deserializer<'de>,
109{
110    let keywords = String::deserialize(deserializer)?;
111    match keywords.len() {
112        0 => Ok(vec![]),
113        _ => Ok(keywords.split(',').map(|s| s.trim().to_string()).collect()),
114    }
115}
116
117fn web_request_blocking(url: &str) -> Result<String, reqwest::Error> {
118    debug!("requesting data from {}", url);
119    let client = Client::builder()
120        .timeout(None)
121        .build()?;
122    client.get(url).send()?.text()
123}
124