septa_api/client.rs
1use crate::{errors, requests, responses};
2use serde::de::DeserializeOwned;
3
4const BASE_API_URL: &str = "https://www3.septa.org/api";
5
6pub type Result<T> = std::result::Result<T, errors::Error>;
7
8#[derive(Debug, Clone)]
9pub struct Client {
10 base_url: String,
11}
12
13impl Default for Client {
14 fn default() -> Self {
15 Self::new()
16 }
17}
18
19impl Client {
20 pub fn new() -> Self {
21 Self {
22 base_url: BASE_API_URL.to_string(),
23 }
24 }
25
26 pub fn with_base_url(base_url: &str) -> Self {
27 Self {
28 base_url: base_url.to_string(),
29 }
30 }
31
32 async fn get<R: DeserializeOwned>(&self, endpoint: &str) -> Result<R> {
33 let url = format!("{}{}", self.base_url, endpoint);
34
35 let response = reqwest::Client::new()
36 .get(url)
37 .send()
38 .await?
39 .json::<responses::ApiResponse<R>>()
40 .await?;
41
42 match response {
43 responses::ApiResponse::Error(error) => Err(errors::Error::ApiErrorResponse(error)),
44 responses::ApiResponse::Response(response) => Ok(response),
45 }
46 }
47
48 async fn get_request<T: requests::Request, R: DeserializeOwned>(
49 &self,
50 endpoint: &str,
51 request: T,
52 ) -> Result<R> {
53 let url = format!("{}{}", self.base_url, endpoint);
54
55 let response = reqwest::Client::new()
56 .get(url)
57 .query(&request.into_params())
58 .send()
59 .await?
60 .json::<responses::ApiResponse<R>>()
61 .await?;
62
63 match response {
64 responses::ApiResponse::Error(error) => Err(errors::Error::ApiErrorResponse(error)),
65 responses::ApiResponse::Response(response) => Ok(response),
66 }
67 }
68
69 /// Returns a list of regional rail trains to arrive at a given station
70 ///
71 /// This function calls into the `/Arrivals/index.php` endpoint.
72 ///
73 /// The returned list is split into northbound and southbound trains. The definition of "Northbound" and "Southbound"
74 /// is defined by SEPTA as the following:
75 /// The direction are obviously not geographical references, but rather a reference to the old Reading and
76 /// Pennsy Railroads. The key to understanding the direction is by using Suburban Station as a starting
77 /// point: Any trains that move eastbound towards Market East are all considered Northbound; trains going
78 /// from suburban to 30th St are all Southbound. The path field describes more accurately the path of travel
79 /// along various branches.
80 ///
81 /// # Arguments
82 ///
83 /// * `request` - A struct containing the request parameters
84 ///
85 /// # Example
86 ///
87 /// ```
88 /// use septa_api::Client;
89 /// use septa_api::requests::ArrivalsRequest;
90 /// use septa_api::types::RegionalRailStop;
91 ///
92 /// #[tokio::main]
93 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
94 /// let client = Client::new();
95 /// let request = ArrivalsRequest {
96 /// station: RegionalRailStop::SuburbanStation,
97 /// results: Some(5),
98 /// direction: None
99 /// };
100 /// let response = client.arrivals(request).await?;
101 ///
102 /// // Loop through the northbound arrivals
103 /// for train in response.northbound {
104 /// println!("Train {} is currently {} on the {} line", train.train_id, train.status, train.line.to_string());
105 /// }
106 ///
107 /// // Loop through the southbound arrivals
108 /// for train in response.southbound {
109 /// println!("Train {} is currently {} on the {} line", train.train_id, train.status, train.line.to_string());
110 /// }
111 ///
112 /// Ok(())
113 /// }
114 /// ```
115 pub async fn arrivals(
116 &self,
117 request: requests::ArrivalsRequest,
118 ) -> Result<responses::ArrivalsResponse> {
119 self.get_request("/Arrivals/index.php", request).await
120 }
121
122 /// Returns a list of all active regional rail trains
123 ///
124 /// This function calls into the `/TrainView/index.php` endpoint.
125 ///
126 /// # Example
127 ///
128 /// ```
129 /// use septa_api::Client;
130 ///
131 /// #[tokio::main]
132 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
133 /// let client = Client::new();
134 /// let response = client.train_view().await?;
135 ///
136 /// for train in response {
137 /// println!("Train {} is currently {} mins late on the {} line", train.train_number, train.late, train.line.to_string());
138 /// }
139 ///
140 /// Ok(())
141 /// }
142 pub async fn train_view(&self) -> Result<responses::TrainResponse> {
143 self.get("/TrainView/index.php").await
144 }
145
146 /// Returns departure and arrival times between two different stations
147 ///
148 /// This function calls into the `/NextToArrive/index.php` endpoint.
149 ///
150 /// # Arguments
151 ///
152 /// * `request` - A struct containing the request parameters
153 ///
154 /// # Example
155 ///
156 /// ```
157 /// use septa_api::Client;
158 /// use septa_api::requests::NextToArriveRequest;
159 /// use septa_api::types::RegionalRailStop;
160 ///
161 /// #[tokio::main]
162 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
163 /// let client = Client::new();
164 /// let request = NextToArriveRequest {
165 /// starting_station: RegionalRailStop::SuburbanStation,
166 /// ending_station: RegionalRailStop::Downingtown,
167 /// results: None,
168 /// };
169 /// let response = client.next_to_arrive(request).await?;
170 ///
171 /// for next_to_arrive in response {
172 /// println!("Train {} is scheduled to arrive {}", next_to_arrive.orig_train, next_to_arrive.orig_departure_time);
173 /// }
174 ///
175 /// Ok(())
176 /// }
177 pub async fn next_to_arrive(
178 &self,
179 request: requests::NextToArriveRequest,
180 ) -> Result<responses::NextToArriveResponse> {
181 self.get_request("/NextToArrive/index.php", request).await
182 }
183
184 /// Returns the schedule for a train by the train's number
185 ///
186 /// This function calls into the `/RRSchedules/index.php` endpoint.
187 ///
188 /// # Arguments
189 ///
190 /// * `request` - A struct containing the request parameters
191 ///
192 /// # Example
193 ///
194 /// ```
195 /// use septa_api::Client;
196 /// use septa_api::requests::RailScheduleRequest;
197 ///
198 /// #[tokio::main]
199 /// async fn main() -> Result<(), Box<dyn std::error::Error>> {
200 /// let client = Client::new();
201 /// let request = RailScheduleRequest {
202 /// train_number: "514".to_string()
203 /// };
204 /// let response = client.rail_schedule(request).await?;
205 ///
206 /// for schedule in response {
207 /// println!("The train is scheduled to arrive at {} at {}", schedule.station.to_string(), schedule.scheduled_time);
208 /// }
209 ///
210 /// Ok(())
211 /// }
212 pub async fn rail_schedule(
213 &self,
214 request: requests::RailScheduleRequest,
215 ) -> Result<responses::RailScheduleResponse> {
216 self.get_request("/RRSchedules/index.php", request).await
217 }
218}