initial commit

This commit is contained in:
Joey Hines 2025-04-20 10:52:00 -06:00
commit 9f9900ca6b
Signed by: joeyahines
GPG Key ID: 38BA6F25C94C9382
7 changed files with 1741 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1603
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

13
Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "cta-api"
version = "0.1.0"
edition = "2024"
description = "CTA API Client"
authors = ["Joey Hines joey@ahines.net"]
[dependencies]
reqwest = { version = "0.12.15", features = ["json"] }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
thiserror = "2.0.12"
url = "2.5.4"

54
src/lib.rs Normal file
View File

@ -0,0 +1,54 @@
use crate::models::Response;
use crate::models::route::{RouteRequest, RouteResp};
use reqwest::{Client, Url};
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),
}
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 fetch_route(&mut self, route: &str) -> Result<RouteResp, Error> {
let req = RouteRequest {
rt: route.to_string(),
key: self.key.clone(),
};
let url = self.url.join("ttpositions.aspx")?;
let resp: Response<RouteResp> = self
.client
.get(url)
.query(&req)
.query(&[("outputType", "JSON")])
.send()
.await?
.json()
.await?;
Ok(resp.ctatt)
}
}

18
src/models/mod.rs Normal file
View File

@ -0,0 +1,18 @@
pub mod route;
pub mod train;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Ctatt {
pub tmst: String,
#[serde(rename = "errCd")]
pub err_cd: String,
#[serde(rename = "errNm")]
pub err_nm: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct Response<T> {
pub ctatt: T,
}

23
src/models/route.rs Normal file
View File

@ -0,0 +1,23 @@
use crate::models::Ctatt;
use crate::models::train::Train;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Debug)]
pub struct RouteRequest {
pub rt: String,
pub key: String,
}
#[derive(Deserialize, Debug)]
pub struct Route {
#[serde(rename = "@name")]
pub name: String,
pub train: Vec<Train>,
}
#[derive(Deserialize, Debug)]
pub struct RouteResp {
#[serde(flatten)]
pub header: Ctatt,
pub route: Vec<Route>,
}

29
src/models/train.rs Normal file
View File

@ -0,0 +1,29 @@
use serde::Deserialize;
#[derive(Debug, Deserialize)]
pub struct Train {
pub rn: String,
#[serde(rename = "destSt")]
pub dest_st: String,
#[serde(rename = "destNm")]
pub dest_nm: String,
#[serde(rename = "trDr")]
pub tr_dr: String,
#[serde(rename = "nextStaId")]
pub next_sta_id: String,
#[serde(rename = "nextStpId")]
pub next_stp_id: String,
#[serde(rename = "nextStaNm")]
pub next_sta_nm: String,
pub prdt: String,
#[serde(rename = "arrT")]
pub arrt: String,
#[serde(rename = "isApp")]
pub is_app: String,
#[serde(rename = "isDly")]
pub is_dly: String,
pub flags: Option<String>,
pub lat: String,
pub lon: String,
pub heading: String,
}