#![cfg(not(target_arch = "wasm32"))]
use {
crate::*,
anyhow::Result,
hmac::{Hmac, Mac},
serde::de::DeserializeOwned,
sha1::Sha1,
};
pub const API_URL: &str = "https://timetableapi.ptv.vic.gov.au";
type PtvHmac = Hmac<Sha1>;
pub struct Client {
devid: String,
key: String,
}
impl Client {
pub fn new(devid: String, key: String) -> Client {
Client { devid, key }
}
pub async fn rq<T: DeserializeOwned>(&self, path: String) -> Result<T> {
let path = format!(
"/{path}{}devid={}",
{
if !path.contains('?') {
"?"
} else if path.ends_with('?') {
""
} else {
"&"
}
},
self.devid
);
let mut hasher: PtvHmac = Hmac::new_from_slice(self.key.as_bytes()).unwrap();
hasher.update(path.as_bytes());
let hash = hex::encode(hasher.finalize().into_bytes());
let url = format!("{API_URL}{}&signature={}", path, hash);
if std::env::var("DEBUG").is_ok() {
println!("Requesting: {}", url);
}
let res = reqwest::get(&url).await?;
if !res.status().is_success() {
let status = res.status();
if let Ok(ApiError { message, .. }) = res.json().await {
return Err(anyhow::anyhow!("Request failed: {} - {}", status, message));
}
return Err(anyhow::anyhow!("Request failed: {}", status));
}
Ok(res.json().await?)
}
pub async fn departures_stop(
&self,
route_type: RouteType,
stop_id: StopId,
options: DeparturesStopOpts,
) -> Result<DeparturesResponse> {
self.rq(format!(
"v3/departures/route_type/{}/stop/{}?{}",
route_type,
stop_id,
to_query(options)
))
.await
}
pub async fn departures_stop_route(
&self,
route_type: RouteType,
route_id: RouteId,
stop_id: StopId,
options: DeparturesStopRouteOpts,
) -> Result<DeparturesResponse> {
self.rq(format!(
"v3/departures/route_type/{}/stop/{}/route/{}?{}",
route_type,
route_id,
stop_id,
to_query(options)
))
.await
}
pub async fn directions_id(&self, direction_id: DirectionId) -> Result<DirectionsResponse> {
self.rq(format!("v3/directions/{}", direction_id)).await
}
pub async fn directions_route(&self, route_id: RouteId) -> Result<DirectionsResponse> {
self.rq(format!("v3/directions/route/{}", route_id)).await
}
pub async fn directions_id_route(
&self,
direction_id: DirectionId,
route_type: RouteType,
) -> Result<DirectionsResponse> {
self.rq(format!(
"v3/directions/{}/route_type/{}",
direction_id, route_type
))
.await
}
pub async fn disruptions(&self, options: DisruptionsOpts) -> Result<DisruptionsResponse> {
self.rq(format!("v3/disruptions?{}", to_query(options)))
.await
}
pub async fn disruptions_route(
&self,
route_id: RouteId,
options: DisruptionsSpecificOpts,
) -> Result<DisruptionsResponse> {
self.rq(format!(
"v3/disruptions/route/{}?{}",
route_id,
to_query(options)
))
.await
}
pub async fn disruptions_route_stop(
&self,
route_id: RouteId,
stop_id: StopId,
options: DisruptionsSpecificOpts,
) -> Result<DisruptionsResponse> {
self.rq(format!(
"v3/disruptions/route/{}/stop/{}?{}",
route_id,
stop_id,
to_query(options)
))
.await
}
pub async fn disruptions_stop(
&self,
stop_id: StopId,
options: DisruptionsSpecificOpts,
) -> Result<DisruptionsResponse> {
self.rq(format!(
"v3/disruptions/stop/{}?{}",
stop_id,
to_query(options)
))
.await
}
pub async fn disruptions_id(&self, disruption_id: DisruptionId) -> Result<Disruption> {
self.rq(format!("v3/disruptions/{}", disruption_id)).await
}
pub async fn fare_estimate(
&self,
min_zone: u8,
max_zone: u8,
options: FareEstimateOpts,
) -> Result<FareEstimateResponse> {
self.rq(format!(
"v3/fare_estimate/min_zone/{}/max_zone/{}?{}",
min_zone,
max_zone,
to_query(options)
))
.await
}
pub async fn outlets(&self, options: OutletsOpts) -> Result<OutletsResponse> {
self.rq(format!("v3/outlets?{}", to_query(options))).await
}
pub async fn outlets_lat_long(
&self,
latitude: f64,
longitude: f64,
options: OutletsLatLongOpts,
) -> Result<OutletsResponse> {
self.rq(format!(
"v3/outlets/location/{}/{}?{}",
latitude,
longitude,
to_query(options)
))
.await
}
pub async fn patterns_run_route(
&self,
run_ref: &str,
route_type: RouteType,
options: PatternsRunRouteOpts,
) -> Result<PatternResponse> {
self.rq(format!(
"v3/pattern/run/{}/route_type/{}?{}",
run_ref,
route_type,
to_query(options)
))
.await
}
pub async fn routes(&self, options: RouteOpts) -> Result<RoutesResponse> {
self.rq(format!("v3/routes?{}", to_query(options))).await
}
pub async fn routes_id(
&self,
route_id: RouteId,
options: RouteIdOpts,
) -> Result<RoutesIdResponse> {
self.rq(format!("v3/routes/{}?{}", route_id, to_query(options)))
.await
}
pub async fn runs_ref(&self, run_ref: &str, options: RunsRefOpts) -> Result<RunsResponse> {
self.rq(format!("v3/runs/{}?{}", run_ref, to_query(options)))
.await
}
pub async fn runs_id(&self, run_id: RouteId, options: RunsIdOpts) -> Result<RunsResponse> {
self.rq(format!("v3/runs/route/{}?{}", run_id, to_query(options)))
.await
}
pub async fn runs_ref_type(
&self,
run_ref: &str,
route_type: RouteType,
options: RunsRefOpts,
) -> Result<RunsResponse> {
self.rq(format!(
"v3/runs/{}/route_type/{}?{}",
run_ref,
route_type,
to_query(options)
))
.await
}
pub async fn runs_id_type(
&self,
run_id: RunId,
route_type: RouteType,
options: RunsIdOpts,
) -> Result<RunsResponse> {
self.rq(format!(
"v3/runs/route/{}/route_type/{}?{}",
run_id,
route_type,
to_query(options)
))
.await
}
pub async fn search(&self, search_term: &str, options: SearchOpts) -> Result<SearchResponse> {
self.rq(format!(
"v3/search/{}?{}",
url_escape::encode_path(&clean(search_term.to_owned())).into_owned(),
to_query(options)
))
.await
}
pub async fn stops_id_route_type(
&self,
stop_id: StopId,
route_type: RouteType,
options: StopsIdRouteTypeOpts,
) -> Result<StopResponse> {
self.rq(format!(
"v3/stops/{}/route_type/{}?{}",
stop_id,
route_type,
to_query(options)
))
.await
}
}