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 Deserialize<'a> + CTAResponse>( &mut self, endpoint: &str, req: T, ) -> Result { let url = self.url.join(endpoint)?; let resp: Response = 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 { 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 { self.make_request("ttarrivals.aspx", eta_request).await } pub async fn fetch_train_schedule(&mut self, run_number: u32) -> Result { 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(); } }