pub use olympian::{DataCache, Timeseries, Timestamp};
use async_trait::async_trait;
use chronoutil::RelativeDuration;
use std::collections::HashMap;
use thiserror::Error;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum Error {
#[error("series id `{0}` could not be parsed")]
InvalidSeriesId(String),
#[error("data source `{0}` not registered")]
InvalidDataSource(String),
#[error(
"extra_spec `{extra_spec:?}` could not be parsed by data source {data_source}: {source}"
)]
InvalidExtraSpec {
data_source: &'static str,
extra_spec: Option<String>,
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[error("io error: {0}")]
Io(#[from] std::io::Error),
#[error("this data source does not offer series data: {0}")]
UnimplementedSeries(String),
#[error("this data source does not offer spatial data: {0}")]
UnimplementedSpatial(String),
#[error("tokio task failure")]
Join(#[from] tokio::task::JoinError),
#[error(transparent)]
Other(Box<dyn std::error::Error + Send + Sync + 'static>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timerange {
pub start: Timestamp,
pub end: Timestamp,
}
pub struct TimeSpec {
pub timerange: Timerange,
pub time_resolution: RelativeDuration,
}
impl TimeSpec {
pub fn new(start: Timestamp, end: Timestamp, time_resolution: RelativeDuration) -> Self {
TimeSpec {
timerange: Timerange { start, end },
time_resolution,
}
}
pub fn new_time_resolution_string(
start: Timestamp,
end: Timestamp,
time_resolution: &str,
) -> Result<Self, String> {
Ok(TimeSpec {
timerange: Timerange { start, end },
time_resolution: RelativeDuration::parse_from_iso8601(time_resolution)
.map_err(|e| e.to_string())?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GeoPoint {
pub lat: f64,
pub lon: f64,
}
pub type Polygon = Vec<GeoPoint>;
pub enum SpaceSpec {
One(String),
Polygon(Polygon),
All,
}
#[async_trait]
pub trait DataConnector: Sync + std::fmt::Debug {
async fn fetch_data(
&self,
space_spec: &SpaceSpec,
time_spec: &TimeSpec,
num_leading_points: u8,
num_trailing_points: u8,
extra_spec: Option<&str>,
) -> Result<DataCache, Error>;
}
#[derive(Debug)]
pub struct DataSwitch {
sources: HashMap<String, Box<dyn DataConnector + Send>>,
}
impl DataSwitch {
pub fn new(sources: HashMap<String, Box<dyn DataConnector + Send>>) -> Self {
Self { sources }
}
pub(crate) async fn fetch_data(
&self,
data_source_id: &str,
space_spec: &SpaceSpec,
time_spec: &TimeSpec,
num_leading_points: u8,
num_trailing_points: u8,
extra_spec: Option<&str>,
) -> Result<DataCache, Error> {
let data_source = self
.sources
.get(data_source_id)
.ok_or_else(|| Error::InvalidDataSource(data_source_id.to_string()))?;
data_source
.fetch_data(
space_spec,
time_spec,
num_leading_points,
num_trailing_points,
extra_spec,
)
.await
}
}