use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::{FlightRadarError, Result};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Flight {
#[serde(default)]
#[serde(alias = "flight_id", alias = "fr24_id")]
pub id: Option<String>,
#[serde(default)]
pub callsign: Option<String>,
#[serde(default)]
#[serde(alias = "lat")]
pub latitude: Option<f64>,
#[serde(default)]
#[serde(alias = "lon", alias = "lng")]
pub longitude: Option<f64>,
#[serde(default)]
pub airline_icao: Option<String>,
#[serde(default)]
pub origin_airport_iata: Option<String>,
#[serde(default)]
pub destination_airport_iata: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl Flight {
pub fn get_altitude(&self) -> Option<u32> {
self.extra
.get("altitude")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
}
pub fn get_heading(&self) -> Option<f64> {
self.extra.get("heading").and_then(|v| v.as_f64())
}
pub fn get_ground_speed(&self) -> Option<f64> {
self.extra.get("ground_speed").and_then(|v| v.as_f64())
}
pub fn get_vertical_speed(&self) -> Option<f64> {
self.extra.get("vertical_speed").and_then(|v| v.as_f64())
}
pub fn get_aircraft_type(&self) -> Option<String> {
self.extra
.get("aircraft_type")
.and_then(|v| v.as_str())
.map(String::from)
}
pub fn get_registration(&self) -> Option<String> {
self.extra
.get("registration")
.and_then(|v| v.as_str())
.map(String::from)
}
fn haversine_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
const EARTH_RADIUS_KM: f64 = 6371.0;
let lat1_rad = lat1.to_radians();
let lat2_rad = lat2.to_radians();
let delta_lat = (lat2 - lat1).to_radians();
let delta_lon = (lon2 - lon1).to_radians();
let a = (delta_lat / 2.0).sin().powi(2)
+ lat1_rad.cos() * lat2_rad.cos() * (delta_lon / 2.0).sin().powi(2);
let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
EARTH_RADIUS_KM * c
}
pub fn get_distance_from(&self, latitude: f64, longitude: f64) -> Option<f64> {
let (my_lat, my_lon) = (self.latitude?, self.longitude?);
Some(Self::haversine_distance(
my_lat, my_lon, latitude, longitude,
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Airport {
#[serde(default)]
pub iata: Option<String>,
#[serde(default)]
pub icao: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub latitude: Option<f64>,
#[serde(default)]
pub longitude: Option<f64>,
#[serde(default)]
pub country: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl Airport {
pub fn get_timezone(&self) -> Option<String> {
self.extra
.get("timezone")
.and_then(|v| v.as_str())
.map(String::from)
}
pub fn get_elevation(&self) -> Option<u32> {
self.extra
.get("elevation")
.and_then(|v| v.as_u64())
.map(|v| v as u32)
}
pub fn get_runways(&self) -> Option<String> {
self.extra
.get("runways")
.and_then(|v| v.as_str())
.map(String::from)
}
pub fn get_city(&self) -> Option<String> {
self.extra
.get("city")
.and_then(|v| v.as_str())
.map(String::from)
}
pub fn get_distance_from(&self, latitude: f64, longitude: f64) -> Option<f64> {
let (my_lat, my_lon) = (self.latitude?, self.longitude?);
Some(Flight::haversine_distance(
my_lat, my_lon, latitude, longitude,
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Airline {
#[serde(default)]
pub iata: Option<String>,
#[serde(default)]
pub icao: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub country: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SearchResult {
#[serde(flatten)]
pub data: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FlightsResponse {
#[serde(default)]
pub flights: Vec<Flight>,
#[serde(default)]
pub aircraft: Vec<Flight>,
#[serde(default)]
pub data: Vec<Flight>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl FlightsResponse {
pub fn items(&self) -> &[Flight] {
if !self.flights.is_empty() {
return &self.flights;
}
if !self.aircraft.is_empty() {
return &self.aircraft;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_flights_key = value.get("flights").is_some()
|| value.get("aircraft").is_some()
|| value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_flights_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_flights_key = candidate.get("flights").is_some()
|| candidate.get("aircraft").is_some()
|| candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_flights_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"flight list response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SearchResponse {
#[serde(default)]
pub results: Vec<SearchResult>,
#[serde(default)]
pub items: Vec<SearchResult>,
#[serde(default)]
pub data: Vec<SearchResult>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl SearchResponse {
pub fn items(&self) -> &[SearchResult] {
if !self.results.is_empty() {
return &self.results;
}
if !self.items.is_empty() {
return &self.items;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_search_key = value.get("results").is_some()
|| value.get("items").is_some()
|| value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_search_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_search_key = candidate.get("results").is_some()
|| candidate.get("items").is_some()
|| candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_search_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"search response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AirportResponse {
#[serde(default)]
pub airport: Option<Airport>,
#[serde(default)]
pub data: Option<Airport>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AirportsResponse {
#[serde(default)]
pub airports: Vec<Airport>,
#[serde(default)]
pub data: Vec<Airport>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl AirportsResponse {
pub fn items(&self) -> &[Airport] {
if !self.airports.is_empty() {
return &self.airports;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_airports_key = value.get("airports").is_some() || value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_airports_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_airports_key =
candidate.get("airports").is_some() || candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_airports_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"airports response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AirlinesResponse {
#[serde(default)]
pub airlines: Vec<Airline>,
#[serde(default)]
pub data: Vec<Airline>,
#[serde(default)]
pub rows: Vec<Airline>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl AirlinesResponse {
pub fn items(&self) -> &[Airline] {
if !self.airlines.is_empty() {
return &self.airlines;
}
if !self.rows.is_empty() {
return &self.rows;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_airlines_key = value.get("airlines").is_some()
|| value.get("rows").is_some()
|| value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_airlines_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_airlines_key = candidate.get("airlines").is_some()
|| candidate.get("rows").is_some()
|| candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_airlines_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"airlines response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FlightDetails {
#[serde(default)]
pub flight_id: Option<String>,
#[serde(default)]
pub status: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FlightDetailsResponse {
#[serde(default)]
pub flight: Option<FlightDetails>,
#[serde(default)]
pub details: Option<FlightDetails>,
#[serde(default)]
pub data: Option<FlightDetails>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl FlightDetailsResponse {
pub fn details(&self) -> Option<&FlightDetails> {
self.flight
.as_ref()
.or(self.details.as_ref())
.or(self.data.as_ref())
}
pub fn from_value(value: Value) -> Result<Self> {
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& parsed.details().is_some()
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let parsed: Self = serde_json::from_value(candidate.clone())?;
if parsed.details().is_some() {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"flight details response does not include details data".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AuthResponse {
#[serde(default)]
pub success: Option<bool>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub message: Option<String>,
#[serde(default)]
pub token: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl AuthResponse {
pub fn is_success(&self) -> bool {
if let Some(success) = self.success {
return success;
}
if let Some(status) = &self.status {
let normalized = status.trim().to_ascii_lowercase();
return normalized == "ok" || normalized == "success";
}
self.extra
.get("result")
.and_then(Value::as_bool)
.unwrap_or(true)
}
pub fn from_value(value: Value) -> Result<Self> {
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (parsed.success.is_some()
|| parsed.status.is_some()
|| parsed.message.is_some()
|| parsed.token.is_some())
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let parsed: Self = serde_json::from_value(candidate.clone())?;
if parsed.success.is_some()
|| parsed.status.is_some()
|| parsed.message.is_some()
|| parsed.token.is_some()
{
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"auth response does not match expected schema".to_owned(),
))
}
}
impl AirportResponse {
pub fn airport(&self) -> Option<&Airport> {
self.airport.as_ref().or(self.data.as_ref())
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_airport_key = value.get("airport").is_some() || value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (parsed.airport().is_some() || root_has_airport_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_airport_key =
candidate.get("airport").is_some() || candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if parsed.airport().is_some() || nested_has_airport_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"airport response does not include airport data".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AirportDetailsResponse {
#[serde(default)]
pub airport: Option<Airport>,
#[serde(default)]
pub flights: Vec<Flight>,
#[serde(default)]
pub details: Option<Value>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl AirportDetailsResponse {
pub fn from_value(value: Value) -> Result<Self> {
let root_has_details_key = value.get("airport").is_some()
|| value.get("flights").is_some()
|| value.get("details").is_some()
|| value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (parsed.airport.is_some() || !parsed.flights.is_empty() || root_has_details_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_details_key = candidate.get("airport").is_some()
|| candidate.get("flights").is_some()
|| candidate.get("details").is_some()
|| candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if parsed.airport.is_some() || !parsed.flights.is_empty() || nested_has_details_key
{
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"airport details response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AirportDisruption {
#[serde(default)]
pub title: Option<String>,
#[serde(default)]
pub description: Option<String>,
#[serde(default)]
pub level: Option<String>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AirportDisruptionsResponse {
#[serde(default)]
pub disruptions: Vec<AirportDisruption>,
#[serde(default)]
pub data: Vec<AirportDisruption>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl AirportDisruptionsResponse {
pub fn items(&self) -> &[AirportDisruption] {
if !self.disruptions.is_empty() {
return &self.disruptions;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_key = value.get("disruptions").is_some() || value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_key =
candidate.get("disruptions").is_some() || candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"airport disruptions response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MostTrackedItem {
#[serde(default)]
pub flight_id: Option<String>,
#[serde(default)]
pub callsign: Option<String>,
#[serde(default)]
pub watchers: Option<u64>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MostTrackedResponse {
#[serde(default)]
pub flights: Vec<MostTrackedItem>,
#[serde(default)]
pub data: Vec<MostTrackedItem>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl MostTrackedResponse {
pub fn items(&self) -> &[MostTrackedItem] {
if !self.flights.is_empty() {
return &self.flights;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_key = value.get("flights").is_some() || value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_key =
candidate.get("flights").is_some() || candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"most tracked response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Zone {
#[serde(default)]
pub tl_y: Option<f64>,
#[serde(default)]
pub tl_x: Option<f64>,
#[serde(default)]
pub br_y: Option<f64>,
#[serde(default)]
pub br_x: Option<f64>,
#[serde(default)]
pub subzones: HashMap<String, Zone>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ZonesResponse {
#[serde(default)]
pub zones: HashMap<String, Zone>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl ZonesResponse {
pub fn from_value(value: Value) -> Result<Self> {
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.zones.is_empty() || parsed.extra.contains_key("zones"))
{
return Ok(parsed);
}
for pointer in [
"/result/response",
"/result/data",
"/response",
"/data",
"/zones",
] {
if let Some(candidate) = value.pointer(pointer) {
if let Ok(parsed) = serde_json::from_value::<Self>(candidate.clone())
&& (!parsed.zones.is_empty() || parsed.extra.contains_key("zones"))
{
return Ok(parsed);
}
if let Ok(zones) =
serde_json::from_value::<HashMap<String, Zone>>(candidate.clone())
&& !zones.is_empty()
{
return Ok(Self {
zones,
extra: HashMap::new(),
});
}
}
}
if let Ok(zones) = serde_json::from_value::<HashMap<String, Zone>>(value)
&& !zones.is_empty()
{
return Ok(Self {
zones,
extra: HashMap::new(),
});
}
Err(FlightRadarError::InvalidPayload(
"zones response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VolcanicEruptionEvent {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub latitude: Option<f64>,
#[serde(default)]
pub longitude: Option<f64>,
#[serde(default)]
pub altitude: Option<f64>,
#[serde(default)]
pub status: Option<String>,
#[serde(default)]
pub timestamp: Option<i64>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct VolcanicEruptionsResponse {
#[serde(default)]
pub events: Vec<VolcanicEruptionEvent>,
#[serde(default)]
pub data: Vec<VolcanicEruptionEvent>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl VolcanicEruptionsResponse {
pub fn items(&self) -> &[VolcanicEruptionEvent] {
if !self.events.is_empty() {
return &self.events;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_key = value.get("events").is_some() || value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_key =
candidate.get("events").is_some() || candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"volcanic eruptions response does not match expected schema".to_owned(),
))
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Bookmark {
#[serde(default)]
pub id: Option<String>,
#[serde(default)]
pub flight_id: Option<String>,
#[serde(default)]
pub callsign: Option<String>,
#[serde(default)]
pub aircraft_type: Option<String>,
#[serde(default)]
pub airline: Option<String>,
#[serde(default)]
pub created_at: Option<i64>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BookmarksResponse {
#[serde(default)]
pub bookmarks: Vec<Bookmark>,
#[serde(default)]
pub data: Vec<Bookmark>,
#[serde(default)]
pub items: Vec<Bookmark>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
impl BookmarksResponse {
pub fn items(&self) -> &[Bookmark] {
if !self.bookmarks.is_empty() {
return &self.bookmarks;
}
if !self.items.is_empty() {
return &self.items;
}
&self.data
}
pub fn from_value(value: Value) -> Result<Self> {
let root_has_key = value.get("bookmarks").is_some()
|| value.get("items").is_some()
|| value.get("data").is_some();
if let Ok(parsed) = serde_json::from_value::<Self>(value.clone())
&& (!parsed.items().is_empty() || root_has_key)
{
return Ok(parsed);
}
for pointer in ["/result/response", "/result/data", "/response", "/data"] {
if let Some(candidate) = value.pointer(pointer) {
let nested_has_key = candidate.get("bookmarks").is_some()
|| candidate.get("items").is_some()
|| candidate.get("data").is_some();
let parsed: Self = serde_json::from_value(candidate.clone())?;
if !parsed.items().is_empty() || nested_has_key {
return Ok(parsed);
}
}
}
Err(FlightRadarError::InvalidPayload(
"bookmarks response does not match expected schema".to_owned(),
))
}
}