use std::fmt;
use std::fmt::{Display, Formatter};
const DEFAULT_SCHEMA: &str = "http";
const DEFAULT_LIMIT: u32 = 30;
const DEFAULT_OFFSET: u32 = 0;
const CURRENT_SEASON: &str = "current";
const FIRST_ROUND: &str = "first";
const LAST_ROUND: &str = "last";
pub type Request = String;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum RequestType {
Seasons,
Circuit,
Schedule,
Constructors,
Drivers,
QualifyingResult,
SprintResult,
RaceResult,
DriverStanding,
ConstructorStanding,
FinishingStatus,
LapTimes,
PitStops,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum RequestParameter {
Season(u32),
CurrentSeason,
Round(u32),
LastRound,
FirstRound,
Id(String),
Circuit(String),
Driver(String),
DriverStanding(u32),
Constructor(String),
ConstructorStanding(u32),
FinishingPosition(u32),
FinishingStatus(String),
Grid(u32),
RaceResult(u32),
SprintResult(u32),
FastestLap(Option<u32>),
Lap(u32),
PitStop(u32),
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct RequestBuilder {
protocol: String,
selection: Option<RequestType>,
season: String,
round: Option<String>,
id: Option<String>,
criteria: Vec<RequestParameter>,
limit: u32,
offset: u32,
}
impl Display for RequestType {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let str = match self {
RequestType::Seasons => "seasons",
RequestType::Circuit => "circuits",
RequestType::Schedule => "races",
RequestType::Constructors => "constructors",
RequestType::Drivers => "drivers",
RequestType::QualifyingResult => "qualifying",
RequestType::SprintResult => "sprint",
RequestType::RaceResult => "results",
RequestType::DriverStanding => "driverStandings",
RequestType::ConstructorStanding => "constructorStandings",
RequestType::FinishingStatus => "status",
RequestType::LapTimes => "laps",
RequestType::PitStops => "pitstops",
};
write!(f, "{}", str)
}
}
impl Display for RequestParameter {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
RequestParameter::Season(s) => write!(f, "{}", s),
RequestParameter::CurrentSeason => write!(f, "{}", CURRENT_SEASON),
RequestParameter::Round(r) => write!(f, "{}", r),
RequestParameter::LastRound => write!(f, "{}", LAST_ROUND),
RequestParameter::FirstRound => write!(f, "{}", FIRST_ROUND),
RequestParameter::Id(id) => write!(f, "{}", id),
RequestParameter::Circuit(c) => write!(f, "circuits/{}", c),
RequestParameter::Driver(d) => write!(f, "drivers/{}", d),
RequestParameter::DriverStanding(s) => write!(f, "driverStandings/{}", s),
RequestParameter::Constructor(c) => write!(f, "constructors/{}", c),
RequestParameter::ConstructorStanding(s) => write!(f, "constructorStandings/{}", s),
RequestParameter::FinishingPosition(p) => write!(f, "{}", p),
RequestParameter::FinishingStatus(s) => write!(f, "status/{}", s),
RequestParameter::Grid(g) => write!(f, "grid/{}", g),
RequestParameter::RaceResult(r) => write!(f, "results/{}", r),
RequestParameter::SprintResult(s) => write!(f, "sprints/{}", s),
RequestParameter::FastestLap(l) => {
let r = if let Some(rank) = l { rank } else { &1u32 };
write!(f, "fastest/{}", r)
}
RequestParameter::Lap(l) => write!(f, "laps/{}", l),
RequestParameter::PitStop(p) => write!(f, "{}", p),
}
}
}
impl Default for RequestBuilder {
fn default() -> Self {
Self::new()
}
}
impl RequestBuilder {
pub fn new() -> Self {
RequestBuilder {
protocol: String::from(DEFAULT_SCHEMA),
selection: None,
season: String::from(CURRENT_SEASON),
round: None,
id: None,
criteria: Vec::new(),
limit: DEFAULT_LIMIT,
offset: DEFAULT_OFFSET,
}
}
pub fn query(mut self, query: RequestType) -> RequestBuilder {
if (query == RequestType::PitStops || query == RequestType::LapTimes)
&& self.round.is_none()
{
self.round = Some(LAST_ROUND.to_string())
}
self.selection = Some(query);
self
}
pub fn add_parameter(mut self, param: RequestParameter) -> RequestBuilder {
match ¶m {
RequestParameter::Season(s) => {
if *s == 0 {
self.season = String::from(CURRENT_SEASON);
} else {
self.season = s.to_string();
}
}
RequestParameter::CurrentSeason => {
self.season = String::from(CURRENT_SEASON);
}
RequestParameter::Round(r) => {
if *r == 0 {
self.round = Some(String::from(LAST_ROUND));
} else {
self.round = Some(r.to_string());
}
}
RequestParameter::FirstRound => self.round = Some(String::from(FIRST_ROUND)),
RequestParameter::LastRound => self.round = Some(String::from(LAST_ROUND)),
RequestParameter::Lap(l) => {
if self.selection == Some(RequestType::LapTimes) {
self.id = Some(l.to_string())
} else {
self.criteria.push(param);
}
}
RequestParameter::FinishingStatus(s) => {
if self.selection == Some(RequestType::FinishingStatus) {
self.id = Some(s.to_owned())
} else {
self.criteria.push(param);
}
}
RequestParameter::PitStop(p) => {
if self.selection == Some(RequestType::PitStops) {
self.id = Some(p.to_string())
} else {
self.criteria.push(param);
}
}
_ => self.criteria.push(param),
}
self
}
pub fn add_parameters(mut self, params: Vec<RequestParameter>) -> RequestBuilder {
for param in params {
self = self.add_parameter(param);
}
self
}
pub fn protocol(mut self, protocol: String) -> RequestBuilder {
self.protocol = protocol;
self
}
pub fn limit(mut self, limit: u32) -> RequestBuilder {
if limit > 1000 {
self.limit = 1000;
return self;
}
self.limit = limit;
self
}
pub fn offset(mut self, offset: u32) -> RequestBuilder {
self.offset = offset;
self
}
pub fn build(self) -> Request {
format!("{protocol}://ergast.com/api/f1/{season}{round}{criteria}{select}{id}.json?{limit},{offset}",
protocol = self.protocol,
season = self.season,
round = if let Some(round) = &self.round { format!("/{}", round) } else { "".to_string() },
criteria = self.build_criteria(),
select = if let Some(selection) = &self.selection { format!("/{}", selection) } else { "".to_string() },
id = if let Some(id) = &self.id { format!("/{}", id) } else { "".to_string() },
limit = self.limit,
offset = self.offset
)
}
fn build_criteria(&self) -> String {
if self.criteria.is_empty() {
return String::new();
}
let mut str = String::new();
for param in &self.criteria {
str.push('/');
str.push_str(param.to_string().as_str());
}
str
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_minimal() {
let expected = format!(
"http://ergast.com/api/f1/current.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new();
assert_eq!(expected, request.build());
}
#[test]
fn test_selection() {
let expected = format!(
"http://ergast.com/api/f1/current/results.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new().query(RequestType::RaceResult);
assert_eq!(expected, request.build());
}
#[test]
fn test_one_criteria_no_selection() {
let expected = format!(
"http://ergast.com/api/f1/current/drivers/verstappen.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new()
.add_parameter(RequestParameter::Driver(String::from("verstappen")));
assert_eq!(expected, request.build());
}
#[test]
fn test_multiple_criteria_no_selection() {
let expected = format!(
"http://ergast.com/api/f1/current/drivers/max_verstappen/grid/1.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new()
.add_parameter(RequestParameter::Driver(String::from("max_verstappen")))
.add_parameter(RequestParameter::Grid(1));
assert_eq!(expected, request.build());
}
#[test]
fn test_one_criteria_with_season() {
let expected = format!(
"http://ergast.com/api/f1/2022/drivers/max_verstappen.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new()
.add_parameter(RequestParameter::Season(2022))
.add_parameter(RequestParameter::Driver(String::from("max_verstappen")));
assert_eq!(expected, request.build());
}
#[test]
fn test_one_criteria_with_round() {
let expected = format!(
"http://ergast.com/api/f1/current/20/drivers/max_verstappen.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new()
.add_parameter(RequestParameter::Round(20))
.add_parameter(RequestParameter::Driver(String::from("max_verstappen")));
assert_eq!(expected, request.build());
}
#[test]
fn test_pit_selection_with_id() {
let expected = format!(
"http://ergast.com/api/f1/current/last/pitstops/2.json?{},{}",
DEFAULT_LIMIT, DEFAULT_OFFSET
);
let request = RequestBuilder::new()
.query(RequestType::PitStops)
.add_parameter(RequestParameter::PitStop(2));
assert_eq!(expected, request.build());
}
}