use serde::Deserialize;
use std::sync::Arc;
pub mod errors;
use errors::Error;
#[derive(Debug, Deserialize)]
struct InnerOpenSkyStates {
pub time: u64,
pub states: Vec<InnerStateVector>,
}
#[derive(Debug, Deserialize)]
struct ShortInnerOpenSkyStates {
pub time: u64,
pub states: Vec<ShortInnerStateVector>,
}
#[derive(Debug)]
pub struct OpenSkyStates {
pub time: u64,
pub states: Vec<StateVector>,
}
impl OpenSkyStates {
fn from_inner(inner: InnerOpenSkyStates) -> Self {
let mut states = Vec::with_capacity(inner.states.len());
for inner in inner.states {
states.push(StateVector::from_inner(inner));
}
Self {
time: inner.time,
states,
}
}
fn from_short_inner(inner: ShortInnerOpenSkyStates) -> Self {
let mut states = Vec::with_capacity(inner.states.len());
for inner in inner.states {
states.push(StateVector::from_short_inner(inner));
}
Self {
time: inner.time,
states,
}
}
}
#[derive(Debug, Deserialize)]
struct InnerStateVector(
String,
Option<String>,
String,
Option<u64>,
u64,
Option<f32>,
Option<f32>,
Option<f32>,
bool,
Option<f32>,
Option<f32>,
Option<f32>,
Option<Vec<u64>>,
Option<f32>,
Option<String>,
bool,
u8,
u32,
);
#[derive(Debug, Deserialize)]
struct ShortInnerStateVector(
String,
Option<String>,
String,
Option<u64>,
u64,
Option<f32>,
Option<f32>,
Option<f32>,
bool,
Option<f32>,
Option<f32>,
Option<f32>,
Option<Vec<u64>>,
Option<f32>,
Option<String>,
bool,
u8,
);
#[derive(Debug)]
pub struct StateVector {
pub icao24: String,
pub callsign: Option<String>,
pub origin_country: String,
pub time_position: Option<u64>,
pub last_contact: u64,
pub longitude: Option<f32>,
pub latitude: Option<f32>,
pub baro_altitude: Option<f32>,
pub on_ground: bool,
pub velocity: Option<f32>,
pub true_track: Option<f32>,
pub vertical_rate: Option<f32>,
pub sensors: Option<Vec<u64>>,
pub geo_altitude: Option<f32>,
pub squawk: Option<String>,
pub spi: bool,
pub position_source: u8,
pub undocumented: Option<u32>,
}
impl StateVector {
fn from_inner(isv: InnerStateVector) -> Self {
Self {
icao24: isv.0,
callsign: isv.1,
origin_country: isv.2,
time_position: isv.3,
last_contact: isv.4,
longitude: isv.5,
latitude: isv.6,
baro_altitude: isv.7,
on_ground: isv.8,
velocity: isv.9,
true_track: isv.10,
vertical_rate: isv.11,
sensors: isv.12,
geo_altitude: isv.13,
squawk: isv.14,
spi: isv.15,
position_source: isv.16,
undocumented: Some(isv.17),
}
}
fn from_short_inner(isv: ShortInnerStateVector) -> Self {
Self {
icao24: isv.0,
callsign: isv.1,
origin_country: isv.2,
time_position: isv.3,
last_contact: isv.4,
longitude: isv.5,
latitude: isv.6,
baro_altitude: isv.7,
on_ground: isv.8,
velocity: isv.9,
true_track: isv.10,
vertical_rate: isv.11,
sensors: isv.12,
geo_altitude: isv.13,
squawk: isv.14,
spi: isv.15,
position_source: isv.16,
undocumented: None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct BoundingBox {
pub lat_min: f32,
pub lat_max: f32,
pub long_min: f32,
pub long_max: f32,
}
impl BoundingBox {
pub fn new(lat_min: f32, lat_max: f32, long_min: f32, long_max: f32) -> Self {
Self {
lat_min,
lat_max,
long_min,
long_max,
}
}
}
#[derive(Debug, Clone)]
pub struct StateRequest {
login: Option<Arc<(String, String)>>,
bbox: Option<BoundingBox>,
time: Option<u64>,
icao24_addresses: Vec<String>,
serials: Vec<u64>,
}
#[derive(Debug, Clone)]
struct FlightsRequest {
login: Option<Arc<(String, String)>>,
begin: u64,
end: u64,
icao24_address: Option<String>,
}
#[derive(Debug, Clone)]
struct ArrivalsRequest {}
impl StateRequest {
pub async fn send(&self) -> Result<OpenSkyStates, Error> {
let login_part = if let Some(login) = &self.login {
format!("{}:{}@", login.0, login.1)
} else {
String::new()
};
let mut args = String::new();
if let Some(time) = self.time {
if args.is_empty() {
args.push('?');
}
args.push_str(&format!("time={}", time));
}
if let Some(bbox) = self.bbox {
if args.is_empty() {
args.push('?');
} else {
args.push('&');
}
args.push_str(&format!(
"lamin={}&lomin={}&lamax={}&lomax={}",
bbox.lat_min, bbox.long_min, bbox.lat_max, bbox.long_max
));
}
if !self.icao24_addresses.is_empty() {
if args.is_empty() {
args.push('?');
} else {
args.push('&');
}
if let Some(first) = self.icao24_addresses.get(0) {
args.push_str(&format!("icao24={}", first));
}
for icao24 in self.icao24_addresses.iter().skip(1) {
args.push_str(&format!("&icao24={}", icao24));
}
}
let endpoint = if !self.serials.is_empty() {
if args.is_empty() {
args.push('?');
} else {
args.push('&');
}
if let Some(first) = self.serials.get(0) {
args.push_str(&format!("serials={}", first));
}
for serial in self.serials.iter().skip(1) {
args.push_str(&format!("&serials={}", serial));
}
"own"
} else {
"all"
};
let url = format!(
"https://{}opensky-network.org/api/states/{}{}",
login_part, endpoint, args
);
let res = reqwest::get(url).await?;
match res.status() {
reqwest::StatusCode::OK => {
let bytes = res.bytes().await?.to_vec();
Ok(if self.time.is_some() {
let short_inner_states: ShortInnerOpenSkyStates =
serde_json::from_slice(&bytes)?;
OpenSkyStates::from_short_inner(short_inner_states)
} else {
if let Ok(inner_states) = serde_json::from_slice(&bytes) {
OpenSkyStates::from_inner(inner_states)
} else {
let short_inner_states: ShortInnerOpenSkyStates =
serde_json::from_slice(&bytes)?;
OpenSkyStates::from_short_inner(short_inner_states)
}
})
}
status => Err(Error::Http(status)),
}
}
}
pub struct StateRequestBuilder {
inner: StateRequest,
}
impl StateRequestBuilder {
fn new(login: Option<Arc<(String, String)>>) -> Self {
Self {
inner: StateRequest {
login,
bbox: None,
time: None,
icao24_addresses: Vec::new(),
serials: Vec::new(),
},
}
}
pub fn with_bbox(mut self, bbox: BoundingBox) -> Self {
self.inner.bbox = Some(bbox);
self
}
pub fn at_time(mut self, timestamp: u64) -> Self {
self.inner.time = Some(timestamp);
self
}
pub fn with_icao24(mut self, address: String) -> Self {
self.inner.icao24_addresses.push(address);
self
}
pub fn with_serial(mut self, serial: u64) -> Self {
self.inner.serials.push(serial);
self
}
pub fn consume(self) -> StateRequest {
self.inner
}
pub fn finish(&self) -> StateRequest {
self.inner.clone()
}
pub async fn send(self) -> Result<OpenSkyStates, Error> {
self.inner.send().await
}
}
pub struct OpenSkyApi {
login: Option<Arc<(String, String)>>,
}
impl OpenSkyApi {
pub fn new() -> Self {
Self { login: None }
}
pub fn with_login(username: String, password: String) -> Self {
Self {
login: Some(Arc::new((username, password))),
}
}
pub fn get_states(&self) -> StateRequestBuilder {
StateRequestBuilder::new(self.login.clone())
}
}
impl From<StateRequestBuilder> for StateRequest {
fn from(srb: StateRequestBuilder) -> Self {
srb.consume()
}
}