1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
use async_trait::async_trait;
use eyre::{Context, ContextCompat, Result};
use reqwest::Client;
use crate::apis::request::{RequestBuilder, RequestParameter, RequestType};
use crate::apis::{race_table::RaceTable, request::Request, response::Response};
#[async_trait]
pub trait Ergast {
/// Main method to query the Ergast API
/// Use a request build via the api::request::RequestBuilder or create one by hand
async fn query(&self, request: Request) -> Result<RaceTable>;
/// Easy to use method to query the race schedule without further query criteria
///
/// Get the race schedule (including all sub-events) and other related information for either a
/// specific season or the current one.
///
/// For example, to get the race schedule fo the current season
///
/// ```
/// # use eyre::Result;
/// # use ergast_rs::{Ergast, ErgastMock as ErgastClient};
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<()> {
/// let client = ErgastClient::new()?;
/// let race_results = client
/// .schedule(None)
/// .await?;
/// # Ok(())
/// # }
/// ```
async fn schedule(&self, season: Option<u32>) -> Result<RaceTable>;
/// Easy to use method to query only race results without further query criteria
///
/// Get the race results for a specific season (otherwise the current season will be used),
/// and a round (otherwise the last round will be used).
///
/// For example, to get the results of the Sakhir Grand Prix in the 2020 season (when Checo
/// won!)
///
/// ```
/// # use eyre::Result;
/// # use ergast_rs::{Ergast, ErgastMock as ErgastClient};
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<()> {
/// let client = ErgastClient::new()?;
/// let race_results = client
/// .race_results(Some(2020), Some(16))
/// .await?;
/// # Ok(())
/// # }
/// ```
async fn race_results(&self, season: Option<u32>, round: Option<u32>) -> Result<RaceTable>;
/// Easy to use method to query only sprint results without further query criteria
///
/// Get the sprint results for a specific season (otherwise the current season will be used),
/// and a round (otherwise the last round will be used).
///
/// For example, to get the sprint results of the British Grand Prix (10th round) of the
/// 2021 season (when Verstappen won)
///
/// ```
/// # use eyre::Result;
/// # use ergast_rs::{Ergast, ErgastMock as ErgastClient};
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<()> {
/// let client = ErgastClient::new()?;
/// let sprint_result = client
/// .sprint_results(Some(2021), Some(10))
/// .await?;
/// # Ok(())
/// # }
/// ```
async fn sprint_results(&self, season: Option<u32>, round: Option<u32>) -> Result<RaceTable>;
/// Easy to use method to query only qualifying results without further query criteria
///
/// Get the qualifying results for a specific season (otherwise the current season will be used),
/// and a round (otherwise the last round will be used).
///
/// For example, to get the qualifying results of the Saudi Arabian Grand Prix (2nd race) of the
/// 2022 season (when Checo was on pole!)
///
/// ```
/// # use eyre::Result;
/// # use ergast_rs::{Ergast, ErgastMock as ErgastClient};
/// # #[tokio::main(flavor = "current_thread")]
/// # async fn main() -> Result<()> {
/// let client = ErgastClient::new()?;
/// let qualifying_results = client
/// .qualifying_results(Some(2022), Some(2))
/// .await?;
/// # Ok(())
/// # }
/// ```
async fn qualifying_results(
&self,
season: Option<u32>,
round: Option<u32>,
) -> Result<RaceTable>;
}
/// An asynchronous client which is used to fetch Formula 1 schedules and results.
pub struct ErgastClient {
client: Client,
}
#[async_trait]
impl Ergast for ErgastClient {
async fn query(&self, request: Request) -> Result<RaceTable> {
let data = self
.client
.get(request)
.send()
.await
.wrap_err("Failed to make request")?
.json::<Response>()
.await
.wrap_err("Failed to parse the JSON response")?
.data;
let races = data.race_table.wrap_err("didn't find the race table")?;
Ok(races)
}
async fn schedule(&self, season: Option<u32>) -> Result<RaceTable> {
let request = RequestBuilder::new()
.query(RequestType::Schedule)
.add_parameter(if let Some(s) = season {
RequestParameter::Season(s)
} else {
RequestParameter::CurrentSeason
})
.build();
self.query(request).await
}
async fn race_results(&self, season: Option<u32>, round: Option<u32>) -> Result<RaceTable> {
let request = RequestBuilder::new()
.query(RequestType::RaceResult)
.add_parameter(if let Some(s) = season {
RequestParameter::Season(s)
} else {
RequestParameter::CurrentSeason
})
.add_parameter(if let Some(r) = round {
RequestParameter::Round(r)
} else {
RequestParameter::LastRound
})
.build();
self.query(request).await
}
async fn sprint_results(&self, season: Option<u32>, round: Option<u32>) -> Result<RaceTable> {
let request = RequestBuilder::new()
.query(RequestType::SprintResult)
.add_parameter(if let Some(s) = season {
RequestParameter::Season(s)
} else {
RequestParameter::CurrentSeason
})
.add_parameter(if let Some(r) = round {
RequestParameter::Round(r)
} else {
RequestParameter::LastRound
})
.build();
self.query(request).await
}
async fn qualifying_results(
&self,
season: Option<u32>,
round: Option<u32>,
) -> Result<RaceTable> {
let request = RequestBuilder::new()
.query(RequestType::QualifyingResult)
.add_parameter(if let Some(s) = season {
RequestParameter::Season(s)
} else {
RequestParameter::CurrentSeason
})
.add_parameter(if let Some(r) = round {
RequestParameter::Round(r)
} else {
RequestParameter::LastRound
})
.build();
self.query(request).await
}
}
impl ErgastClient {
/// Builds a new client, can fail if the underlying HTTP client fails to build.
#[allow(dead_code)]
pub fn new() -> Result<Self> {
let client = Client::builder()
.build()
.wrap_err("Failed to build client")?;
Ok(Self { client })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn smokecheck_fetch_schedule_without_practice_time() {
let client = ErgastClient::new().expect("failed to build client");
let races = client
.schedule(Some(2021))
.await
.expect("failed to get races");
dbg!(&races);
assert_eq!(races.races.len(), 22);
}
#[tokio::test]
async fn smokecheck_fetch_schedule_with_practice_time() {
let client = ErgastClient::new().expect("failed to build client");
let races = client
.schedule(Some(2022))
.await
.expect("failed to get races");
dbg!(&races);
assert_eq!(races.races.len(), 22);
}
#[tokio::test]
async fn smokecheck_fetch_qualifying_results() {
let client = ErgastClient::new().expect("failed to build client");
let qualifying = client
.qualifying_results(Some(2021), Some(1))
.await
.expect("failed to get qualifying results");
dbg!(&qualifying);
}
#[tokio::test]
async fn smokecheck_fetch_latest_qualifying_results() {
let client = ErgastClient::new().expect("failed to build client");
let qualifying = client
.qualifying_results(None, None)
.await
.expect("failed to get qualifying results");
dbg!(&qualifying);
}
#[tokio::test]
async fn smokecheck_fetch_sprint_results() {
let client = ErgastClient::new().expect("failed to build client");
let qualifying = client
.sprint_results(Some(2021), Some(10))
.await
.expect("failed to get qualifying results");
dbg!(&qualifying);
}
#[tokio::test]
async fn smokecheck_fetch_race_results() {
let client = ErgastClient::new().expect("failed to build client");
let race_results = client
.race_results(Some(2021), Some(1))
.await
.expect("failed to get race results");
dbg!(&race_results);
}
#[tokio::test]
async fn smokecheck_fetch_latest_race_results() {
let client = ErgastClient::new().expect("failed to build client");
let race_results = client
.race_results(None, None)
.await
.expect("failed to get race results");
dbg!(&race_results);
}
}