Skip to main content

flightradar24_api/
client.rs

1use crate::error::FlightRadarError;
2use reqwest::blocking::Client;
3use serde::{Deserialize, Serialize};
4
5/// Main structure for storing API internal data
6pub struct FlightRadarClient {
7    client: Client,
8    base_url: String,
9    api_key: String,
10}
11
12impl FlightRadarClient {
13    /// Creates a new instance of the client.
14    /// # Arguments
15    ///   * `api_key` - Your Flightradar24 API key.
16    pub fn new(api_key: String) -> Self {
17        FlightRadarClient {
18            client: Client::new(),
19            // This is an example base URL. Adjust as needed.
20            base_url: "https://fr24api.flightradar24.com/api/".to_string(),
21            api_key,
22        }
23    }
24
25    pub fn update_base_url(&mut self, base_url: String) {
26        self.base_url = base_url;
27    }
28
29    /// Build parameters string for query URL
30    /// # Arguments
31    ///   * `params` - Structure of all possible parameters
32    /// # Returns
33    ///   `String` of params on success or a `FlightRadarError` on failure.
34    fn build_query_params(params: &FullLiveFlightQuery) -> Result<String, FlightRadarError> {
35        let mut url: String = String::new();
36
37        if let Some(bounds) = &params.bounds {
38            let bounds_str = format!(
39                "?bounds={},{},{},{}",
40                bounds.north, bounds.south, bounds.west, bounds.east
41            );
42            url.push_str(&bounds_str);
43        }
44        if let Some(flights) = &params.flights {
45            url.push_str("&flights=");
46            for flight in flights {
47                if flight.chars().all(char::is_alphanumeric) && flight.len() > 2 {
48                    url.push_str(&flight.to_string());
49                    url.push(',');
50                } else {
51                    return Err(FlightRadarError::Parameter(format!("Flight #: {}", flight)));
52                }
53            }
54            url.pop();
55        }
56        if let Some(callsigns) = &params.callsigns {
57            url.push_str("&callsigns=");
58            for callsign in callsigns {
59                if callsign.chars().all(char::is_alphanumeric)
60                    && callsign.len() > 2
61                    && callsign.len() <= 8
62                {
63                    url.push_str(&callsign.to_string());
64                    url.push(',');
65                } else {
66                    return Err(FlightRadarError::Parameter(format!(
67                        "Callsign: {}",
68                        callsign
69                    )));
70                }
71            }
72            url.pop();
73        }
74        if let Some(registrations) = &params.registrations {
75            url.push_str("&registrations=");
76            for registration in registrations {
77                if registration
78                    .chars()
79                    .all(|c| c.is_alphanumeric() || c == '-')
80                    && registration.len() > 1
81                    && registration.len() <= 12
82                {
83                    url.push_str(&registration.to_string());
84                    url.push(',');
85                } else {
86                    return Err(FlightRadarError::Parameter(format!(
87                        "Registration #: {}",
88                        registration
89                    )));
90                }
91            }
92            url.pop();
93        }
94        if let Some(painted_as) = &params.painted_as {
95            url.push_str("&painted_as=");
96            for painted in painted_as {
97                if painted.chars().all(char::is_alphabetic) && painted.len() == 3 {
98                    url.push_str(&painted.to_string());
99                    url.push(',');
100                } else {
101                    return Err(FlightRadarError::Parameter(format!(
102                        "Painted As: {}",
103                        painted
104                    )));
105                }
106            }
107            url.pop();
108        }
109        if let Some(operating_as) = &params.operating_as {
110            url.push_str("&operating_as=");
111            for operating in operating_as {
112                if operating.chars().all(char::is_alphabetic) && operating.len() == 3 {
113                    url.push_str(&operating.to_string());
114                    url.push(',');
115                } else {
116                    return Err(FlightRadarError::Parameter(format!(
117                        "Operating As: {}",
118                        operating
119                    )));
120                }
121            }
122            url.pop();
123        }
124        if let Some(airports) = &params.airports {
125            url.push_str("&airports=");
126            for airport in airports {
127                if airport
128                    .chars()
129                    .all(|c| (c.is_alphabetic() && (c != 'i' || c != 'q' || c != 'x')) || c == ':')
130                {
131                    url.push_str(&airport.to_string());
132                    url.push(',');
133                } else {
134                    return Err(FlightRadarError::Parameter(format!("Airport: {}", airport)));
135                }
136            }
137            url.pop();
138        }
139        if let Some(routes) = &params.routes {
140            url.push_str("&routes=");
141            for route in routes {
142                if route
143                    .chars()
144                    .all(|c| (c.is_alphabetic() && (c != 'i' || c != 'q' || c != 'x')) || c == '-')
145                {
146                    url.push_str(&route.to_string());
147                    url.push(',');
148                } else {
149                    return Err(FlightRadarError::Parameter(format!("Route: {}", route)));
150                }
151            }
152            url.pop();
153        }
154        if let Some(aircraft) = &params.aircraft {
155            url.push_str("&aircraft=");
156            for aircraft_iter in aircraft {
157                if aircraft_iter
158                    .chars()
159                    .all(|c| c.is_alphanumeric() || c == '*')
160                {
161                    if aircraft_iter.chars().filter(|c| c == &'*').count() == 1 {
162                        url.push_str(&aircraft_iter.to_string());
163                        url.push(',');
164                    }
165                } else {
166                    return Err(FlightRadarError::Parameter(format!(
167                        "Aircraft: {}",
168                        aircraft_iter
169                    )));
170                }
171            }
172            url.pop();
173        }
174        if let Some(altitude_ranges) = &params.altitude_ranges {
175            url.push_str("&altitude_ranges=");
176            for altitude_range in altitude_ranges {
177                url.push_str(&altitude_range.min.to_string());
178                url.push('-');
179                url.push_str(&altitude_range.max.to_string());
180                url.push(',');
181            }
182            url.pop();
183        }
184        if let Some(squawks) = &params.squawks {
185            url.push_str("&squawks=");
186            for squawk in squawks {
187                if squawk <= &7777 {
188                    url.push_str(&squawk.to_string());
189                    url.push(',');
190                } else {
191                    return Err(FlightRadarError::Parameter(format!("Squawk: {}", squawk)));
192                }
193            }
194            url.pop();
195        }
196        if let Some(categories) = &params.categories {
197            url.push_str("&categories=");
198            for category in categories {
199                if "PCMJTHBGDVON".contains(*category) {
200                    url.push(*category);
201                    url.push(',');
202                } else {
203                    return Err(FlightRadarError::Parameter(format!(
204                        "Category: {}",
205                        category
206                    )));
207                }
208            }
209            url.pop();
210        }
211        if let Some(data_sources) = &params.data_sources {
212            let valid_data_sources = [
213                "ADSB".to_string(),
214                "MLAT".to_string(),
215                "ESTIMATED".to_string(),
216            ];
217            url.push_str("&data_sources=");
218            for data_source in data_sources {
219                if valid_data_sources.contains(data_source) {
220                    url.push_str(data_source);
221                    url.push(',');
222                } else {
223                    return Err(FlightRadarError::Parameter(format!(
224                        "Data Source: {}",
225                        data_source
226                    )));
227                }
228            }
229            url.pop();
230        }
231        if let Some(airspaces) = &params.airspaces {
232            //TODO: This doesn't matter for Historic endpoints
233            url.push_str("&airspaces=");
234            for airspace in airspaces {
235                if airspace.chars().all(char::is_alphabetic) {
236                    url.push_str(&airspace.to_string());
237                    url.push(',');
238                } else {
239                    return Err(FlightRadarError::Parameter(format!(
240                        "Airspace: {}",
241                        airspace
242                    )));
243                }
244            }
245            url.pop();
246        }
247        if let Some(gspeed) = &params.gspeed {
248            match gspeed {
249                ApiRangeEnum::U32(val) => {
250                    // Can't be greater than 5000
251                    if val > &5000 {
252                        return Err(FlightRadarError::Parameter(format!("GSpeed: {}", val)));
253                    }
254                    url.push_str("&gspeed=");
255                    url.push_str(&val.to_string());
256                }
257                ApiRangeEnum::ApiRange(gspeed) => {
258                    // Can't be greater than 5000
259                    if gspeed.max > 5000 {
260                        return Err(FlightRadarError::Parameter(format!(
261                            "GSpeed: {}",
262                            gspeed.max
263                        )));
264                    }
265                    url.push_str("&gspeed=");
266                    url.push_str(&gspeed.min.to_string());
267                    url.push('-');
268                    url.push_str(&gspeed.max.to_string());
269                }
270            }
271        };
272        if let Some(limit) = &params.limit {
273            url.push_str("&limit=");
274            url.push_str(&limit.to_string());
275        }
276
277        Ok(url)
278    }
279
280    /// Check to ensure one API query is provided for endpoint
281    /// # Arguments
282    ///   * `query_in` - FullLiveFlightQuery to check
283    /// # Returns
284    ///   A `bool` based on check
285    pub fn check_live_parameters(query_in: &FullLiveFlightQuery) -> bool {
286        if query_in.aircraft.is_some()
287            || query_in.airports.is_some()
288            || query_in.airspaces.is_some()
289            || query_in.altitude_ranges.is_some()
290            || query_in.bounds.is_some()
291            || query_in.callsigns.is_some()
292            || query_in.categories.is_some()
293            || query_in.data_sources.is_some()
294            || query_in.flights.is_some()
295            || query_in.gspeed.is_some()
296            || query_in.operating_as.is_some()
297            || query_in.painted_as.is_some()
298            || query_in.registrations.is_some()
299            || query_in.routes.is_some()
300            || query_in.squawks.is_some()
301        {
302            return true;
303        };
304
305        // Needed parameter not found
306        false
307    }
308
309    pub fn check_historic_parameters(query_in: &FullLiveFlightQuery) -> bool {
310        if query_in.aircraft.is_some()
311            || query_in.airports.is_some()
312            || query_in.altitude_ranges.is_some()
313            || query_in.bounds.is_some()
314            || query_in.callsigns.is_some()
315            || query_in.categories.is_some()
316            || query_in.data_sources.is_some()
317            || query_in.flights.is_some()
318            || query_in.gspeed.is_some()
319            || query_in.operating_as.is_some()
320            || query_in.painted_as.is_some()
321            || query_in.registrations.is_some()
322            || query_in.routes.is_some()
323            || query_in.squawks.is_some()
324        {
325            return true;
326        };
327
328        // Needed parameter not found
329        false
330    }
331
332    /// Issue the GET command to API Endpoint
333    /// # Arguments
334    ///   * `url` - API URL to send GET request to
335    /// # Returns
336    ///   A `String` on success and `FlightRadarError` on failure.
337    pub fn query_endpoint(&self, url: String) -> Result<String, FlightRadarError> {
338        let response = self
339            .client
340            .get(&url)
341            .header("Accept-Version", "v1")
342            .bearer_auth(&self.api_key)
343            .send()?; // synchronous send
344        let response_text = response.text()?; // synchronous text retrieval
345        Ok(response_text)
346    }
347
348    /// Fetches airline information by ICAO.
349    /// # Arguments
350    ///   * `icao` - The identifier for the airline.
351    /// # Returns
352    ///   A `Airline` struct on success or a `FlightRadarError` on failure.
353    pub fn get_airline_by_icao(&self, icao: &str) -> Result<Airline, FlightRadarError> {
354        // Make URL and GET
355        let url = format!("{}static/airlines/{}/light", self.base_url, icao);
356        let text = match self.query_endpoint(url) {
357            Ok(data) => data,
358            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
359        };
360
361        // Parse
362        let airline: Airline = serde_json::from_str(&text)
363            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
364
365        Ok(airline)
366    }
367
368    /// Fetches airport information by code.
369    /// # Arguments
370    ///   * `code` - The identifier for the airport.
371    /// # Returns
372    ///   A `Airport` struct on success or a `FlightRadarError` on failure.
373    pub fn get_airport_by_code(&self, code: &str) -> Result<Airport, FlightRadarError> {
374        // Make URL and GET
375        let url = format!("{}static/airports/{}/full", self.base_url, code);
376        let text = match self.query_endpoint(url) {
377            Ok(data) => data,
378            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
379        };
380
381        // Parse
382        let airport: Airport = serde_json::from_str(&text)
383            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
384
385        Ok(airport)
386    }
387
388    /// Fetches airport information by code.
389    /// # Arguments
390    ///   * `code` - The identifier for the airport.
391    /// # Returns
392    ///   A `Airport` struct on success or a `FlightRadarError` on failure.
393    pub fn get_airport_lite_by_code(&self, code: &str) -> Result<AirportLite, FlightRadarError> {
394        // Make URL and GET
395        let url = format!("{}static/airports/{}/light", self.base_url, code);
396        let text = match self.query_endpoint(url) {
397            Ok(data) => data,
398            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
399        };
400
401        // Parse
402        let airport: AirportLite = serde_json::from_str(&text)
403            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
404
405        Ok(airport)
406    }
407
408    /// Fetches live flight information by location (or other parameters).
409    /// # Arguments
410    ///   * `other_queries` - Optional parameters to narrow down data
411    /// # Returns
412    ///   A `FullLiveFlightResponse` struct on success or a `FlightRadarError` on failure.
413    pub fn get_live_flight(
414        &self,
415        other_queries: Option<&FullLiveFlightQuery>,
416    ) -> Result<FullLiveFlightResponse, FlightRadarError> {
417        // Make Required URL
418        let defualt_query_in = &FullLiveFlightQuery::default();
419        let other_query_in = match other_queries {
420            Some(data) => data,
421            _ => defualt_query_in,
422        };
423
424        // If parameters not included, bailout
425        if !Self::check_live_parameters(other_query_in) {
426            return Err(FlightRadarError::Parameter(
427                "Missing One Of Required Parameters".to_string(),
428            ));
429        }
430
431        let params_back = Self::build_query_params(other_query_in)?;
432        let endpoint = format!("{}live/flight-positions/full{}", self.base_url, params_back);
433
434        //println!("{}", endpoint);
435
436        // GET
437        let text = match self.query_endpoint(endpoint) {
438            Ok(data) => data,
439            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
440        };
441        //println!("{:?}", text);
442
443        // Parse
444        let live_data: FullLiveFlightResponse = serde_json::from_str(&text)
445            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
446
447        Ok(live_data)
448    }
449
450    /// Fetches light live flight information by location (or other parameters).
451    /// # Arguments
452    ///   * `other_queries` - Optional parameters to narrow down data
453    /// # Returns
454    ///   A `LightLiveFlightResponse` struct on success or a `FlightRadarError` on failure.
455    pub fn get_live_flight_light(
456        &self,
457        other_queries: Option<&FullLiveFlightQuery>,
458    ) -> Result<LightLiveFlightResponse, FlightRadarError> {
459        // Make Required URL
460        let defualt_query_in = &FullLiveFlightQuery::default();
461        let other_query_in = match other_queries {
462            Some(data) => data,
463            _ => defualt_query_in,
464        };
465
466        // If parameters not included, bailout
467        if !Self::check_live_parameters(other_query_in) {
468            return Err(FlightRadarError::Parameter(
469                "Missing One Of Required Parameters".to_string(),
470            ));
471        }
472
473        let params_back = Self::build_query_params(other_query_in)?;
474        let endpoint = format!("{}live/flight-positions/full{}", self.base_url, params_back);
475
476        //println!("{}", endpoint);
477
478        // GET
479        let text = match self.query_endpoint(endpoint) {
480            Ok(data) => data,
481            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
482        };
483        //println!("{:?}", text);
484
485        // Parse
486        let live_data: LightLiveFlightResponse = serde_json::from_str(&text)
487            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
488
489        Ok(live_data)
490    }
491
492    /// Fetches historic flight information by timestamp (or other parameters).
493    /// # Arguments
494    ///   * `timestamp` - Timestamp to gather information from
495    ///   * `other_queries` - Optional parameters to narrow down data
496    /// # Returns
497    ///   A `FullLiveFlightResponse` struct on success or a `FlightRadarError` on failure.
498    pub fn get_historic_flight(
499        &self,
500        timestamp: &u64,
501        other_queries: Option<&FullLiveFlightQuery>,
502    ) -> Result<FullLiveFlightResponse, FlightRadarError> {
503        // Check Timestamp
504        const MIN_TIMESTAMP: u64 = 1462924800;
505        if timestamp < &MIN_TIMESTAMP {
506            return Err(FlightRadarError::Parameter(format!(
507                "Invalid Timestamp: {}",
508                timestamp
509            )));
510        };
511
512        // If parameters not included, bailout
513        let stuff = match other_queries {
514            Some(stuff) => stuff,
515            None => &FullLiveFlightQuery::default(),
516        };
517
518        if !Self::check_historic_parameters(stuff) {
519            return Err(FlightRadarError::Parameter(
520                "Missing One Of Required Parameters".to_string(),
521            ));
522        }
523
524        // Build optional params
525        let params_back = if let Some(other_queries) = other_queries {
526            match Self::build_query_params(other_queries) {
527                Ok(param_str) => param_str[1..param_str.len()].to_string(),
528                Err(_) => String::from(""),
529            }
530        } else {
531            String::from("")
532        };
533
534        let endpoint = format!(
535            "{}historic/flight-positions/full?timestamp={}&{}",
536            self.base_url, timestamp, params_back
537        );
538
539        // GET
540        let text = match self.query_endpoint(endpoint) {
541            Ok(data) => data,
542            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
543        };
544        //println!("{:?}", text);
545
546        // Parse
547        let historic_data: FullLiveFlightResponse = serde_json::from_str(&text)
548            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
549
550        Ok(historic_data)
551    }
552
553    /// Fetches light historic flight information by timestamp (or other parameters).
554    /// # Arguments
555    ///   * `timestamp` - Timestamp to gather information from
556    ///   * `other_queries` - Optional parameters to narrow down data
557    /// # Returns
558    ///   A `LightLiveFlightResponse` struct on success or a `FlightRadarError` on failure.
559    pub fn get_historic_flight_light(
560        &self,
561        timestamp: &u64,
562        other_queries: Option<&FullLiveFlightQuery>,
563    ) -> Result<LightLiveFlightResponse, FlightRadarError> {
564        // Check Timestamp
565        const MIN_TIMESTAMP: u64 = 1462924800;
566        if timestamp < &MIN_TIMESTAMP {
567            return Err(FlightRadarError::Parameter(format!(
568                "Invalid Timestamp: {}",
569                timestamp
570            )));
571        };
572
573        // If parameters not included, bailout
574        let stuff = match other_queries {
575            Some(stuff) => stuff,
576            None => &FullLiveFlightQuery::default(),
577        };
578
579        if !Self::check_historic_parameters(stuff) {
580            return Err(FlightRadarError::Parameter(
581                "Missing One Of Required Parameters".to_string(),
582            ));
583        }
584
585        // Build optional params
586        let params_back = if let Some(other_queries) = other_queries {
587            match Self::build_query_params(other_queries) {
588                Ok(param_str) => param_str[1..param_str.len()].to_string(),
589                Err(_) => String::from(""),
590            }
591        } else {
592            String::from("")
593        };
594
595        let endpoint = format!(
596            "{}historic/flight-positions/light?timestamp={}&{}",
597            self.base_url, timestamp, params_back
598        );
599
600        // GET
601        let text = match self.query_endpoint(endpoint) {
602            Ok(data) => data,
603            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
604        };
605        //println!("{:?}", text);
606
607        // Parse
608        let historic_data: LightLiveFlightResponse = serde_json::from_str(&text)
609            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
610
611        Ok(historic_data)
612    }
613
614    /// Fetches flight information by flight ID.
615    /// # Arguments
616    ///   * `flight_id` - The identifier for the flight.
617    /// # Returns
618    ///   A `Flight` struct on success or a `FlightRadarError` on failure.
619    pub fn get_flight_tracks_by_id(
620        &self,
621        flight_id: &str,
622    ) -> Result<Vec<Flight>, FlightRadarError> {
623        // If value isn't valid hexadecimal, exit function and raise error
624        if u64::from_str_radix(flight_id, 16).is_err() {
625            return Err(FlightRadarError::Parameter(format!(
626                "Flight ID Not Hexadecimal: {}",
627                flight_id
628            )));
629        }
630
631        // Make URL and GET
632        let url = format!("{}flight-tracks?flight_id={}", self.base_url, flight_id);
633        let text = match self.query_endpoint(url) {
634            Ok(data) => data,
635            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
636        };
637        //println!("{:?}", text);
638
639        // Parse
640        let flights: Vec<Flight> = serde_json::from_str(&text)
641            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
642
643        Ok(flights)
644    }
645
646    /// Fetches API usage details over period
647    /// # Arguments
648    ///   * `period` - Backwards time to gather usage (Allowed: 24h | 7d | 30d | 1y)
649    /// # Returns
650    ///   A `ApiUsageResponse` struct on success or a `FlightRadarError` on failure.
651    pub fn get_api_usage(&self, period: &str) -> Result<ApiUsageResponse, FlightRadarError> {
652        // If value isn't valid, exit function and raise error
653        (match period {
654            "24h" | "7d" | "30d" | "1y" => Ok(()),
655            _ => Err(FlightRadarError::Parameter(format!(
656                "Period: {}. Should be: 24h|7d|30d|1y",
657                period
658            ))),
659        })?;
660
661        // Make URL and GET
662        let url = format!("{}usage?period={}", self.base_url, period);
663        let text = match self.query_endpoint(url) {
664            Ok(data) => data,
665            Err(_) => return Err(FlightRadarError::General("GET Request Failed".to_string())),
666        };
667        //println!("{:?}", text);
668
669        // Parse
670        let usage: ApiUsageResponse = serde_json::from_str(&text)
671            .map_err(|e| FlightRadarError::Parsing(format!("{}\nResponse: {}", e, text)))?;
672
673        Ok(usage)
674    }
675}
676
677/// Individual Tracks for flight-tracks endpoint
678#[derive(Debug, Deserialize, Default)]
679pub struct Track {
680    pub timestamp: String,
681    pub lat: f64,
682    pub lon: f64,
683    pub alt: u32,
684    pub gspeed: u32,
685    pub vspeed: u32,
686    pub track: u32,
687    pub squawk: String,
688    pub callsign: String,
689    pub source: String,
690}
691
692/// Wrapper struct for flight-tracks endpoint
693#[derive(Debug, Deserialize, Default)]
694pub struct Flight {
695    #[serde(rename = "fr24_id")]
696    pub id: String,
697    pub tracks: Vec<Track>,
698}
699
700/// Wrapper struct of usage endpoint
701#[derive(Debug, Deserialize, Default)]
702pub struct ApiUsageResponse {
703    pub data: Vec<ApiEndpointUsage>,
704}
705
706/// Individual endpoint usage data
707#[derive(Debug, Deserialize, Default)]
708pub struct ApiEndpointUsage {
709    pub endpoint: String,
710    pub metadata: String,
711    pub request_count: u32,
712    pub results: u32,
713    pub credits: u32,
714}
715
716/// Basic Airline stucture
717#[derive(Debug, Deserialize, Default)]
718pub struct Airline {
719    pub name: String,
720    pub iata: Option<String>,
721    pub icao: String,
722}
723
724/// Result airport data from airport/full endpoint
725#[derive(Debug, Deserialize, Default)]
726pub struct Airport {
727    pub name: String,
728    pub iata: String,
729    pub icao: String,
730    pub lon: f64,
731    pub lat: f64,
732    pub elevation: i32,
733    pub country: Country,
734    pub city: String,
735    pub state: Option<String>,
736    pub timezone: Timezone,
737}
738
739/// Nested Struct for Country Data
740#[derive(Debug, Deserialize, Default)]
741pub struct Country {
742    pub code: String,
743    pub name: String,
744}
745
746/// Nested Struct for Timezone Data
747#[derive(Debug, Deserialize, Default)]
748pub struct Timezone {
749    pub name: String,
750    pub offset: i32,
751}
752
753/// Result airport data from airport/light endpoint
754#[derive(Debug, Deserialize, Default)]
755pub struct AirportLite {
756    pub name: String,
757    pub iata: String,
758    pub icao: String,
759}
760
761/// Represents a query for flight positions.
762#[derive(Debug, Deserialize, Default)]
763pub struct FullLiveFlightQuery {
764    pub bounds: Option<Bounds>,
765    pub flights: Option<Vec<String>>,
766    pub callsigns: Option<Vec<String>>,
767    pub registrations: Option<Vec<String>>,
768    pub painted_as: Option<Vec<String>>,
769    pub operating_as: Option<Vec<String>>,
770    pub airports: Option<Vec<String>>,
771    pub routes: Option<Vec<String>>,
772    pub aircraft: Option<Vec<String>>,
773    pub altitude_ranges: Option<Vec<ApiRange>>,
774    pub squawks: Option<Vec<u16>>,
775    pub categories: Option<Vec<char>>,
776    pub data_sources: Option<Vec<String>>,
777    pub airspaces: Option<Vec<String>>,
778    pub gspeed: Option<ApiRangeEnum>,
779    pub limit: Option<u32>,
780}
781
782/// Represents a geographic bounding box.
783#[derive(Debug, Deserialize, Default, Serialize)]
784pub struct Bounds {
785    pub north: f64,
786    pub south: f64,
787    pub west: f64,
788    pub east: f64,
789}
790
791/// Represents a numeric range with a minimum and maximum.
792#[derive(Debug, Deserialize, Default)]
793pub struct ApiRange {
794    pub min: u32,
795    pub max: u32,
796}
797
798/// Allow range to be specific number or range
799#[derive(Debug, Deserialize)]
800pub enum ApiRangeEnum {
801    U32(u32),
802    ApiRange(ApiRange),
803}
804
805/// Wrapper struct for flight-positions endpoint
806#[derive(Deserialize, Debug, Default)]
807pub struct FullLiveFlightResponse {
808    pub data: Vec<FullLiveFlightData>,
809}
810
811/// Data for each flight returned from flight-positions endpoint
812#[derive(Deserialize, Debug, Default)]
813pub struct FullLiveFlightData {
814    pub fr24_id: String,
815    pub flight: String,
816    pub callsign: String,
817    pub lat: f64,
818    pub lon: f64,
819    pub track: u32,
820    pub alt: u32,
821    pub gspeed: u32,
822    pub vspeed: i32, // Plane can be descending
823    pub squawk: String,
824    pub timestamp: String,
825    pub source: String,
826    pub hex: String,
827    // `type` is a reserved keyword in Rust so we rename it to `type_field`
828    #[serde(rename = "type")]
829    pub type_field: String,
830    pub reg: String,
831    pub painted_as: String,
832    pub operating_as: String,
833    pub orig_iata: String,
834    pub orig_icao: String,
835    pub dest_iata: String,
836    pub dest_icao: String,
837    pub eta: String,
838}
839
840/// Data for light flight responses
841#[derive(Deserialize, Debug, Default)]
842pub struct LightLiveFlightResponse {
843    pub data: Vec<LightLiveFlightData>,
844}
845
846/// Data for each flight returned from flight-positions endpoint
847#[derive(Deserialize, Debug, Default)]
848pub struct LightLiveFlightData {
849    pub fr24_id: String,
850    pub hex: String,
851    pub callsign: String,
852    pub lat: f64,
853    pub lon: f64,
854    pub track: u32,
855    pub alt: u32,
856    pub gspeed: u32,
857    pub vspeed: i32, // Plane can be descending
858    pub squawk: String,
859    pub timestamp: String,
860    pub source: String,
861}