121 lines
3.0 KiB
Rust
121 lines
3.0 KiB
Rust
use crate::models::eta::{EtaRequest, EtaResp};
|
|
use crate::models::follow::{FollowRequest, FollowResp};
|
|
use crate::models::route::{RouteRequest, RouteResp};
|
|
use crate::models::{CTAResponse, Response};
|
|
use reqwest::{Client, Url};
|
|
use serde::{Deserialize, Serialize};
|
|
use thiserror::Error;
|
|
|
|
pub mod models;
|
|
|
|
const CTA_API_URL: &str = "http://lapi.transitchicago.com/api/1.0/";
|
|
|
|
#[derive(Error, Debug)]
|
|
pub enum Error {
|
|
#[error("Failed to deserialize response from API: {0}")]
|
|
JsonParseFailure(#[from] serde_json::Error),
|
|
#[error("Reqwest failure: {0}")]
|
|
ReqwestFailure(#[from] reqwest::Error),
|
|
#[error("URL parse failure: {0}")]
|
|
UrlParseError(#[from] url::ParseError),
|
|
#[error("Got error number '{0}': {1}")]
|
|
CtaError(String, String),
|
|
}
|
|
|
|
pub struct CTAClient {
|
|
key: String,
|
|
client: Client,
|
|
url: Url,
|
|
}
|
|
|
|
impl CTAClient {
|
|
pub fn new(key: String) -> Self {
|
|
Self {
|
|
key,
|
|
client: Default::default(),
|
|
url: Url::parse(CTA_API_URL).unwrap(),
|
|
}
|
|
}
|
|
|
|
pub async fn make_request<T: Serialize, V: for<'a> Deserialize<'a> + CTAResponse>(
|
|
&mut self,
|
|
endpoint: &str,
|
|
req: T,
|
|
) -> Result<V, Error> {
|
|
let url = self.url.join(endpoint)?;
|
|
|
|
let resp: Response<V> = self
|
|
.client
|
|
.get(url)
|
|
.query(&req)
|
|
.query(&[("outputType", "JSON")])
|
|
.query(&[("key", &self.key)])
|
|
.send()
|
|
.await?
|
|
.json()
|
|
.await?;
|
|
|
|
resp.ctatt.check_error()?;
|
|
|
|
Ok(resp.ctatt)
|
|
}
|
|
|
|
pub async fn fetch_route(&mut self, route: &str) -> Result<RouteResp, Error> {
|
|
let req = RouteRequest {
|
|
rt: route.to_string(),
|
|
};
|
|
|
|
self.make_request("ttpositions.aspx", req).await
|
|
}
|
|
|
|
pub async fn fetch_eta(&mut self, eta_request: EtaRequest) -> Result<EtaResp, Error> {
|
|
self.make_request("ttarrivals.aspx", eta_request).await
|
|
}
|
|
|
|
pub async fn fetch_train_schedule(&mut self, run_number: u32) -> Result<FollowResp, Error> {
|
|
self.make_request("ttfollow.aspx", FollowRequest { run_number })
|
|
.await
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::CTAClient;
|
|
use crate::models::eta::EtaRequest;
|
|
|
|
pub fn client() -> CTAClient {
|
|
let token = std::env::var("CTA_TOKEN").expect("Missing CTA_TOKEN");
|
|
|
|
CTAClient::new(token)
|
|
}
|
|
|
|
#[tokio::test]
|
|
pub async fn test_fetch_eta() {
|
|
let mut client = client();
|
|
|
|
let _eta = client
|
|
.fetch_eta(EtaRequest {
|
|
mapid: None,
|
|
stpid: Some(30111),
|
|
max: Some(1),
|
|
rt: None,
|
|
})
|
|
.await
|
|
.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
pub async fn test_fetch_route() {
|
|
let mut client = client();
|
|
|
|
let _eta = client.fetch_route("blue").await.unwrap();
|
|
}
|
|
|
|
#[tokio::test]
|
|
pub async fn test_fetch_train_schedule() {
|
|
let mut client = client();
|
|
|
|
let _train = client.fetch_train_schedule(109).await.unwrap();
|
|
}
|
|
}
|