use std::convert::Infallible;
use enum_as_inner::EnumAsInner;
use ordered_float::OrderedFloat;
use serde::{Deserialize, Deserializer, de::DeserializeOwned};
use serde_with::{DisplayFromStr, serde_as};
use url::Url;
use crate::{
error::{Error, Result},
id::{CircuitID, ConstructorID, DriverID, RoundID, SeasonID, StatusID},
jolpica::time::{
Date, DateTime, Duration, QualifyingTime, RaceTime, Time, deserialize_buggy_race_time, deserialize_duration,
deserialize_optional_time, deserialize_time,
},
};
#[cfg(doc)]
use crate::jolpica::resource::{Filters, Resource};
#[derive(PartialEq, Clone, Debug)]
pub struct Response {
pub xmlns: String,
pub series: String,
pub url: Url,
pub pagination: Pagination,
pub table: Table,
}
impl Response {
pub const fn as_info(&self) -> (&String, &String, &Url) {
(&self.xmlns, &self.series, &self.url)
}
pub fn to_info(&self) -> (String, String, Url) {
(self.xmlns.clone(), self.series.clone(), self.url.clone())
}
pub fn into_table_list<T: TableInnerList>(self) -> Result<Vec<T>> {
T::try_into_inner_from(self.table)
}
pub fn into_single_table_list_element<T: TableInnerList>(self) -> Result<T> {
self.into_table_list().and_then(verify_has_one_element_and_extract)
}
pub fn as_table_list<T: TableInnerList>(&self) -> Result<&Vec<T>> {
T::try_as_inner_from(&self.table)
}
pub fn as_single_table_list_element<T: TableInnerList>(&self) -> Result<&T> {
self.as_table_list()
.map(Vec::as_slice)
.and_then(verify_has_one_element)
.map(|s| s.first().unwrap_or_else(|| unreachable!()))
}
pub fn into_race_schedules(self) -> Result<Vec<Race<Schedule>>> {
self.into_races()?
.into_iter()
.map(|race| race.try_map(|payload| payload.into_schedule().map_err(into)))
.collect()
}
pub fn into_race_schedule(self) -> Result<Race<Schedule>> {
self.into_race_schedules().and_then(verify_has_one_element_and_extract)
}
pub fn into_many_races_with_many_session_results<T: PayloadInnerList>(self) -> Result<Vec<Race<Vec<T>>>> {
self.into_races()?
.into_iter()
.map(|race| race.try_map(|payload| T::try_into_inner_from(payload)))
.collect()
}
pub fn into_one_race_with_many_session_results<T: PayloadInnerList>(self) -> Result<Race<Vec<T>>> {
self.into_many_races_with_many_session_results::<T>()
.and_then(verify_has_one_element_and_extract)
}
pub fn into_many_races_with_one_session_result<T: PayloadInnerList>(self) -> Result<Vec<Race<T>>> {
self.into_many_races_with_many_session_results::<T>()?
.into_iter()
.map(|race| race.try_map(verify_has_one_element_and_extract))
.collect()
}
pub fn into_one_race_with_one_session_result<T: PayloadInnerList>(self) -> Result<Race<T>> {
self.into_many_races_with_one_session_result::<T>()
.and_then(verify_has_one_element_and_extract)
}
pub fn into_driver_laps(self, driver_id: &DriverID) -> Result<Vec<DriverLap>> {
Ok(self)
.and_then(verify_has_one_race_and_extract)?
.payload
.into_laps()?
.into_iter()
.map(|lap| DriverLap::try_from(lap, driver_id))
.collect()
}
pub fn into_lap_timings(self) -> Result<Vec<Timing>> {
Ok(self)
.and_then(verify_has_one_race_and_extract)?
.payload
.into_laps()
.map_err(into)
.and_then(verify_has_one_element_and_extract)
.map(|lap| lap.timings)
}
pub fn into_pit_stops(self) -> Result<Vec<PitStop>> {
Ok(self)
.and_then(verify_has_one_race_and_extract)?
.payload
.into_pit_stops()
.map_err(into)
}
pub fn into_seasons(self) -> Result<Vec<Season>> {
self.into_table_list::<Season>()
}
pub fn into_season(self) -> Result<Season> {
self.into_single_table_list_element::<Season>()
}
pub fn as_seasons(&self) -> Result<&Vec<Season>> {
self.as_table_list::<Season>()
}
pub fn as_season(&self) -> Result<&Season> {
self.as_single_table_list_element::<Season>()
}
pub fn into_drivers(self) -> Result<Vec<Driver>> {
self.into_table_list::<Driver>()
}
pub fn into_driver(self) -> Result<Driver> {
self.into_single_table_list_element::<Driver>()
}
pub fn as_drivers(&self) -> Result<&Vec<Driver>> {
self.as_table_list::<Driver>()
}
pub fn as_driver(&self) -> Result<&Driver> {
self.as_single_table_list_element::<Driver>()
}
pub fn into_constructors(self) -> Result<Vec<Constructor>> {
self.into_table_list::<Constructor>()
}
pub fn into_constructor(self) -> Result<Constructor> {
self.into_single_table_list_element::<Constructor>()
}
pub fn as_constructors(&self) -> Result<&Vec<Constructor>> {
self.as_table_list::<Constructor>()
}
pub fn as_constructor(&self) -> Result<&Constructor> {
self.as_single_table_list_element::<Constructor>()
}
pub fn into_circuits(self) -> Result<Vec<Circuit>> {
self.into_table_list::<Circuit>()
}
pub fn into_circuit(self) -> Result<Circuit> {
self.into_single_table_list_element::<Circuit>()
}
pub fn as_circuits(&self) -> Result<&Vec<Circuit>> {
self.as_table_list::<Circuit>()
}
pub fn as_circuit(&self) -> Result<&Circuit> {
self.as_single_table_list_element::<Circuit>()
}
pub fn into_statuses(self) -> Result<Vec<Status>> {
self.into_table_list::<Status>()
}
pub fn into_status(self) -> Result<Status> {
self.into_single_table_list_element::<Status>()
}
pub fn as_statuses(&self) -> Result<&Vec<Status>> {
self.as_table_list::<Status>()
}
pub fn as_status(&self) -> Result<&Status> {
self.as_single_table_list_element::<Status>()
}
pub fn into_races(self) -> Result<Vec<Race<Payload>>> {
self.into_table_list::<Race<Payload>>()
}
pub fn into_race(self) -> Result<Race<Payload>> {
self.into_single_table_list_element::<Race<Payload>>()
}
pub fn as_races(&self) -> Result<&Vec<Race<Payload>>> {
self.as_table_list::<Race<Payload>>()
}
pub fn as_race(&self) -> Result<&Race<Payload>> {
self.as_single_table_list_element::<Race<Payload>>()
}
}
impl<'de> Deserialize<'de> for Response {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
#[derive(Deserialize)]
struct Proxy {
#[serde(rename = "MRData")]
mr_data: MrData,
}
#[derive(Deserialize)]
struct MrData {
xmlns: String,
series: String,
url: Url,
#[serde(flatten)]
pagination: Pagination,
#[serde(flatten)]
table: Table,
}
let mr_data = Proxy::deserialize(deserializer)?.mr_data;
Ok(Self {
xmlns: mr_data.xmlns,
series: mr_data.series,
url: mr_data.url,
pagination: mr_data.pagination,
table: mr_data.table,
})
}
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
pub struct Pagination {
#[serde_as(as = "DisplayFromStr")]
pub limit: u32,
#[serde_as(as = "DisplayFromStr")]
pub offset: u32,
#[serde_as(as = "DisplayFromStr")]
pub total: u32,
}
impl Pagination {
pub const fn is_last_page(&self) -> bool {
(self.offset + self.limit) >= self.total
}
pub const fn is_single_page(&self) -> bool {
(self.offset == 0) && self.is_last_page()
}
pub const fn next_page(&self) -> Option<Self> {
if self.is_last_page() {
None
} else {
Some(Self {
offset: self.offset + self.limit,
..*self
})
}
}
}
#[derive(Deserialize, EnumAsInner, PartialEq, Clone, Debug)]
pub enum Table {
#[serde(rename = "SeasonTable")]
Seasons {
#[serde(rename = "Seasons")]
seasons: Vec<Season>,
},
#[serde(rename = "DriverTable")]
Drivers {
#[serde(rename = "Drivers")]
drivers: Vec<Driver>,
},
#[serde(rename = "ConstructorTable")]
Constructors {
#[serde(rename = "Constructors")]
constructors: Vec<Constructor>,
},
#[serde(rename = "CircuitTable")]
Circuits {
#[serde(rename = "Circuits")]
circuits: Vec<Circuit>,
},
#[serde(rename = "RaceTable")]
Races {
#[serde(rename = "Races")]
races: Vec<Race>,
},
#[serde(rename = "StatusTable")]
Status {
#[serde(rename = "Status")]
status: Vec<Status>,
},
}
type InnerList<T> = Vec<T>;
pub trait TableInnerList
where
Self: Sized,
{
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>>;
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>>;
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct Season {
#[serde_as(as = "DisplayFromStr")]
pub season: SeasonID,
pub url: Url,
}
impl TableInnerList for Season {
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>> {
table.into_seasons().map_err(into)
}
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>> {
table.as_seasons().ok_or(Error::BadTableVariant)
}
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Driver {
pub driver_id: DriverID,
#[serde_as(as = "Option<DisplayFromStr>")]
pub permanent_number: Option<u32>,
pub code: Option<String>,
pub url: Url,
pub given_name: String,
pub family_name: String,
pub date_of_birth: Date,
pub nationality: String,
}
impl Driver {
pub fn full_name(&self) -> String {
format!("{} {}", self.given_name, self.family_name)
}
}
impl TableInnerList for Driver {
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>> {
table.into_drivers().map_err(into)
}
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>> {
table.as_drivers().ok_or(Error::BadTableVariant)
}
}
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Constructor {
pub constructor_id: ConstructorID,
pub url: Url,
pub name: String,
pub nationality: String,
}
impl TableInnerList for Constructor {
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>> {
table.into_constructors().map_err(into)
}
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>> {
table.as_constructors().ok_or(Error::BadTableVariant)
}
}
#[derive(Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Circuit {
pub circuit_id: CircuitID,
pub url: Url,
pub circuit_name: String,
#[serde(rename = "Location")]
pub location: Location,
}
impl TableInnerList for Circuit {
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>> {
table.into_circuits().map_err(into)
}
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>> {
table.as_circuits().ok_or(Error::BadTableVariant)
}
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Status {
#[serde_as(as = "DisplayFromStr")]
pub status_id: StatusID,
#[serde_as(as = "DisplayFromStr")]
pub count: u32,
pub status: String,
}
impl TableInnerList for Status {
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>> {
table.into_status().map_err(into)
}
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>> {
table.as_status().ok_or(Error::BadTableVariant)
}
}
#[serde_as]
#[derive(Deserialize, Eq, PartialEq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Race<T = Payload> {
#[serde_as(as = "DisplayFromStr")]
pub season: SeasonID,
#[serde_as(as = "DisplayFromStr")]
pub round: RoundID,
pub url: Url,
pub race_name: String,
#[serde(rename = "Circuit")]
pub circuit: Circuit,
pub date: Date,
#[serde(default, deserialize_with = "deserialize_optional_time")]
pub time: Option<Time>,
#[serde(flatten)]
pub payload: T,
}
impl<T> Race<T> {
pub const fn as_info(&self) -> (&SeasonID, &RoundID, &Url, &String, &Circuit, &Date, &Option<Time>) {
(&self.season, &self.round, &self.url, &self.race_name, &self.circuit, &self.date, &self.time)
}
pub fn to_info(&self) -> (SeasonID, RoundID, Url, String, Circuit, Date, Option<Time>) {
(
self.season,
self.round,
self.url.clone(),
self.race_name.clone(),
self.circuit.clone(),
self.date,
self.time,
)
}
pub fn try_map<U, F, E>(self, op: F) -> std::result::Result<Race<U>, E>
where
F: FnOnce(T) -> std::result::Result<U, E>,
E: std::error::Error,
{
Ok(Race::<U> {
season: self.season,
round: self.round,
url: self.url,
race_name: self.race_name,
circuit: self.circuit,
date: self.date,
time: self.time,
payload: op(self.payload)?,
})
}
pub fn map<U, F>(self, op: F) -> Race<U>
where
F: FnOnce(T) -> U,
{
self.try_map(|payload| Ok::<_, Infallible>(op(payload)))
.unwrap_or_else(|_| unreachable!())
}
pub fn from<U>(race: Race<U>, payload: T) -> Self {
race.map(|_| payload)
}
}
impl TableInnerList for Race<Payload> {
fn try_into_inner_from(table: Table) -> Result<InnerList<Self>> {
table.into_races().map_err(into)
}
fn try_as_inner_from(table: &Table) -> Result<&InnerList<Self>> {
table.as_races().ok_or(Error::BadTableVariant)
}
}
#[derive(Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
pub struct Schedule {
#[serde(rename = "FirstPractice")]
pub first_practice: Option<DateTime>,
#[serde(rename = "SecondPractice")]
pub second_practice: Option<DateTime>,
#[serde(rename = "ThirdPractice")]
pub third_practice: Option<DateTime>,
#[serde(rename = "Qualifying")]
pub qualifying: Option<DateTime>,
#[serde(rename = "Sprint")]
pub sprint: Option<DateTime>,
#[serde(rename = "SprintShootout")]
pub sprint_shootout: Option<DateTime>,
#[serde(rename = "SprintQualifying")]
pub sprint_qualifying: Option<DateTime>,
}
impl Race<Schedule> {
pub const fn schedule(&self) -> &Schedule {
&self.payload
}
pub fn into_schedule(self) -> Schedule {
self.payload
}
}
#[derive(EnumAsInner, PartialEq, Clone, Debug)]
pub enum Payload {
QualifyingResults(Vec<QualifyingResult>),
SprintResults(Vec<SprintResult>),
RaceResults(Vec<RaceResult>),
Laps(Vec<Lap>),
PitStops(Vec<PitStop>),
Schedule(Schedule),
}
impl<'de> Deserialize<'de> for Payload {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
fn from_value<'de, T, D>(value: serde_json::Value) -> std::result::Result<T, D::Error>
where
T: DeserializeOwned,
D: Deserializer<'de>,
{
serde_json::from_value(value).map_err(serde::de::Error::custom)
}
#[derive(Deserialize)]
enum Proxy {
QualifyingResults(serde_json::Value),
SprintResults(serde_json::Value),
#[serde(rename = "Results")]
RaceResults(serde_json::Value),
Laps(serde_json::Value),
PitStops(serde_json::Value),
#[serde(untagged)]
Schedule(Schedule),
}
match Proxy::deserialize(deserializer)? {
Proxy::QualifyingResults(value) => from_value::<_, D>(value).map(Self::QualifyingResults),
Proxy::SprintResults(value) => from_value::<_, D>(value).map(Self::SprintResults),
Proxy::RaceResults(value) => from_value::<_, D>(value).map(Self::RaceResults),
Proxy::Laps(value) => from_value::<_, D>(value).map(Self::Laps),
Proxy::PitStops(value) => from_value::<_, D>(value).map(Self::PitStops),
Proxy::Schedule(schedule) => Ok(Self::Schedule(schedule)),
}
}
}
pub trait PayloadInnerList
where
Self: Sized,
{
fn try_into_inner_from(payload: Payload) -> Result<InnerList<Self>>;
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct QualifyingResult {
#[serde_as(as = "DisplayFromStr")]
pub number: u32,
#[serde_as(as = "DisplayFromStr")]
pub position: u32,
#[serde(rename = "Driver")]
pub driver: Driver,
#[serde(rename = "Constructor")]
pub constructor: Constructor,
#[serde(rename = "Q1")]
pub q1: Option<QualifyingTime>,
#[serde(rename = "Q2")]
pub q2: Option<QualifyingTime>,
#[serde(rename = "Q3")]
pub q3: Option<QualifyingTime>,
}
impl Race<Vec<QualifyingResult>> {
pub fn qualifying_results(&self) -> &[QualifyingResult] {
&self.payload
}
pub fn into_qualifying_results(self) -> Vec<QualifyingResult> {
self.payload
}
}
impl Race<QualifyingResult> {
pub const fn qualifying_result(&self) -> &QualifyingResult {
&self.payload
}
pub fn into_qualifying_result(self) -> QualifyingResult {
self.payload
}
}
impl PayloadInnerList for QualifyingResult {
fn try_into_inner_from(payload: Payload) -> Result<InnerList<Self>> {
payload.into_qualifying_results().map_err(into)
}
}
pub type Points = f32;
#[serde_as]
#[derive(Deserialize, PartialEq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct SprintResult {
#[serde_as(as = "DisplayFromStr")]
pub number: u32,
#[serde_as(as = "DisplayFromStr")]
pub position: u32,
pub position_text: Position,
#[serde_as(as = "DisplayFromStr")]
pub points: Points,
#[serde(rename = "Driver")]
pub driver: Driver,
#[serde(rename = "Constructor")]
pub constructor: Constructor,
#[serde_as(as = "DisplayFromStr")]
pub grid: u32,
#[serde_as(as = "DisplayFromStr")]
pub laps: u32,
pub status: String,
#[serde(rename = "Time", default, deserialize_with = "deserialize_buggy_race_time")]
pub time: Option<RaceTime>,
#[serde(rename = "FastestLap")]
pub fastest_lap: Option<FastestLap>,
}
impl Race<Vec<SprintResult>> {
pub fn sprint_results(&self) -> &[SprintResult] {
&self.payload
}
pub fn into_sprint_results(self) -> Vec<SprintResult> {
self.payload
}
}
impl Race<SprintResult> {
pub const fn sprint_result(&self) -> &SprintResult {
&self.payload
}
pub fn into_sprint_result(self) -> SprintResult {
self.payload
}
}
impl PayloadInnerList for SprintResult {
fn try_into_inner_from(payload: Payload) -> Result<InnerList<Self>> {
payload.into_sprint_results().map_err(into)
}
}
#[serde_as]
#[derive(Deserialize, PartialEq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RaceResult {
#[serde(deserialize_with = "deserialize_possible_no_number")]
pub number: u32,
#[serde_as(as = "DisplayFromStr")]
pub position: u32,
pub position_text: Position,
#[serde_as(as = "DisplayFromStr")]
pub points: Points,
#[serde(rename = "Driver")]
pub driver: Driver,
#[serde(rename = "Constructor")]
pub constructor: Constructor,
#[serde_as(as = "DisplayFromStr")]
pub grid: u32,
#[serde_as(as = "DisplayFromStr")]
pub laps: u32,
pub status: String,
#[serde(rename = "Time", default, deserialize_with = "deserialize_buggy_race_time")]
pub time: Option<RaceTime>,
#[serde(rename = "FastestLap")]
pub fastest_lap: Option<FastestLap>,
}
impl RaceResult {
pub const NO_NUMBER: u32 = u32::MAX;
}
impl Race<Vec<RaceResult>> {
pub fn race_results(&self) -> &[RaceResult] {
&self.payload
}
pub fn into_race_results(self) -> Vec<RaceResult> {
self.payload
}
}
impl Race<RaceResult> {
pub const fn race_result(&self) -> &RaceResult {
&self.payload
}
pub fn into_race_result(self) -> RaceResult {
self.payload
}
}
impl PayloadInnerList for RaceResult {
fn try_into_inner_from(payload: Payload) -> Result<InnerList<Self>> {
payload.into_race_results().map_err(into)
}
}
fn deserialize_possible_no_number<'de, D>(deserializer: D) -> std::result::Result<u32, D::Error>
where
D: Deserializer<'de>,
{
String::deserialize(deserializer).and_then(|str| {
if str == "None" {
Ok(RaceResult::NO_NUMBER)
} else {
str.parse::<u32>().map_err(serde::de::Error::custom)
}
})
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Position {
Finished(u32),
Retired,
Disqualified,
Excluded,
Withdrawn,
FailedToQualify,
NotClassified,
}
impl Position {
pub const R: Self = Self::Retired;
pub const D: Self = Self::Disqualified;
pub const E: Self = Self::Excluded;
pub const W: Self = Self::Withdrawn;
pub const F: Self = Self::FailedToQualify;
pub const N: Self = Self::NotClassified;
}
impl<'de> Deserialize<'de> for Position {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
match String::deserialize(deserializer)?.as_str() {
"R" => Ok(Self::R),
"D" => Ok(Self::D),
"E" => Ok(Self::E),
"W" => Ok(Self::W),
"F" => Ok(Self::F),
"N" => Ok(Self::N),
num => Ok(Self::Finished(
num.parse::<u32>()
.map_err(|err| serde::de::Error::custom(err.to_string()))?,
)),
}
}
}
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct DriverLap {
pub number: u32,
pub position: u32,
pub time: Duration,
}
impl DriverLap {
pub fn try_from(lap: Lap, driver_id: &DriverID) -> Result<Self> {
let timing = verify_has_one_element_and_extract(lap.timings)?;
if timing.driver_id != *driver_id {
return Err(Error::UnexpectedData(format!(
"Expected driver_id '{}' but got '{}'",
driver_id, timing.driver_id
)));
}
Ok(Self {
number: lap.number,
position: timing.position,
time: timing.time,
})
}
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct Lap {
#[serde_as(as = "DisplayFromStr")]
pub number: u32,
#[serde(rename = "Timings")]
pub timings: Vec<Timing>,
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Timing {
pub driver_id: DriverID,
#[serde_as(as = "DisplayFromStr")]
pub position: u32,
#[serde(deserialize_with = "deserialize_duration")]
pub time: Duration,
}
#[serde_as]
#[derive(Deserialize, PartialEq, Eq, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct PitStop {
pub driver_id: DriverID,
#[serde_as(as = "DisplayFromStr")]
pub lap: u32,
#[serde_as(as = "DisplayFromStr")]
pub stop: u32,
#[serde(deserialize_with = "deserialize_time")]
pub time: Time,
#[serde(deserialize_with = "deserialize_duration")]
pub duration: Duration,
}
#[serde_as]
#[derive(Deserialize, Hash, Eq, PartialEq, Clone, Debug)]
pub struct Location {
#[serde_as(as = "DisplayFromStr")]
pub lat: OrderedFloat<f64>,
#[serde_as(as = "DisplayFromStr")]
pub long: OrderedFloat<f64>,
pub locality: String,
pub country: String,
}
#[serde_as]
#[derive(Deserialize, PartialEq, Clone, Copy, Debug)]
pub struct FastestLap {
#[serde_as(as = "Option<DisplayFromStr>")]
pub rank: Option<u32>,
#[serde_as(as = "DisplayFromStr")]
pub lap: u32,
#[serde(rename = "Time", deserialize_with = "extract_nested_time")]
pub time: Duration,
#[serde(rename = "AverageSpeed")]
pub average_speed: Option<AverageSpeed>,
}
fn extract_nested_time<'de, D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Duration, D::Error> {
#[derive(Deserialize)]
struct Time {
#[serde(deserialize_with = "deserialize_duration")]
time: Duration,
}
Ok(Time::deserialize(deserializer)?.time)
}
#[serde_as]
#[derive(Deserialize, PartialEq, Clone, Copy, Debug)]
pub struct AverageSpeed {
pub units: SpeedUnits,
#[serde_as(as = "DisplayFromStr")]
pub speed: f32,
}
#[derive(Deserialize, PartialEq, Eq, Clone, Copy, Debug)]
pub enum SpeedUnits {
#[serde(rename = "kph")]
Kph,
}
pub(crate) const fn verify_has_one_element<T>(sequence: &[T]) -> Result<&[T]> {
match sequence.len() {
0 => Err(Error::NotFound),
1 => Ok(sequence),
_ => Err(Error::TooMany),
}
}
pub(crate) fn verify_has_one_element_and_extract<T>(mut sequence: Vec<T>) -> Result<T> {
match sequence.len() {
0 => Err(Error::NotFound),
1 => Ok(sequence.remove(0)),
_ => Err(Error::TooMany),
}
}
pub(crate) fn verify_has_one_race_and_extract(response: Response) -> Result<Race> {
response
.table
.into_races()
.map_err(into)
.and_then(verify_has_one_element_and_extract)
}
fn into<T: Into<U>, U>(t: T) -> U {
t.into()
}
#[cfg(test)]
mod tests {
use std::sync::LazyLock;
use const_format::formatcp;
use crate::jolpica::tests::assets::*;
use crate::tests::asserts::*;
use shadow_asserts::{assert_eq, assert_ne};
use super::*;
#[test]
fn season_table() {
let table: Table = serde_json::from_str(SEASON_TABLE_STR).unwrap();
assert_false!(table.as_seasons().unwrap().is_empty());
assert_eq!(table, *SEASON_TABLE);
}
#[test]
fn driver_table() {
let table: Table = serde_json::from_str(DRIVER_TABLE_STR).unwrap();
assert_false!(table.as_drivers().unwrap().is_empty());
assert_eq!(table, *DRIVER_TABLE);
}
#[test]
fn constructor_table() {
let table: Table = serde_json::from_str(CONSTRUCTOR_TABLE_STR).unwrap();
assert_false!(table.as_constructors().unwrap().is_empty());
assert_eq!(table, *CONSTRUCTOR_TABLE);
}
#[test]
fn circuit_table() {
let table: Table = serde_json::from_str(CIRCUIT_TABLE_STR).unwrap();
assert_false!(table.as_circuits().unwrap().is_empty());
assert_eq!(table, *CIRCUIT_TABLE);
}
#[test]
fn race_table_schedule() {
let table: Table = serde_json::from_str(RACE_TABLE_SCHEDULE_STR).unwrap();
assert_false!(table.as_races().unwrap().is_empty());
assert_eq!(table, *RACE_TABLE_SCHEDULE);
}
#[test]
fn driver_full_name() {
assert_eq!(DRIVER_KIMI.full_name(), "Kimi Räikkönen");
assert_eq!(DRIVER_PEREZ.full_name(), "Sergio Pérez");
assert_eq!(DRIVER_DE_VRIES.full_name(), "Nyck de Vries");
assert_eq!(DRIVER_MAX.full_name(), "Max Verstappen");
assert_eq!(DRIVER_LECLERC.full_name(), "Charles Leclerc");
}
#[test]
fn qualifying_result() {
let from_str = |result_str| serde_json::from_str::<QualifyingResult>(result_str).unwrap();
assert_eq!(from_str(QUALIFYING_RESULT_2003_4_P1_STR), *QUALIFYING_RESULT_2003_4_P1);
assert_eq!(from_str(QUALIFYING_RESULT_2003_4_P2_STR), *QUALIFYING_RESULT_2003_4_P2);
assert_eq!(from_str(QUALIFYING_RESULT_2003_4_P20_STR), *QUALIFYING_RESULT_2003_4_P20);
assert_eq!(from_str(QUALIFYING_RESULT_2023_4_P1_STR), *QUALIFYING_RESULT_2023_4_P1);
assert_eq!(from_str(QUALIFYING_RESULT_2023_4_P2_STR), *QUALIFYING_RESULT_2023_4_P2);
assert_eq!(from_str(QUALIFYING_RESULT_2023_4_P3_STR), *QUALIFYING_RESULT_2023_4_P3);
assert_eq!(from_str(QUALIFYING_RESULT_2023_10_P4_STR), *QUALIFYING_RESULT_2023_10_P4);
assert_eq!(from_str(QUALIFYING_RESULT_2023_12_P2_STR), *QUALIFYING_RESULT_2023_12_P2);
}
#[test]
fn qualifying_results() {
{
let race: Race = serde_json::from_str(RACE_2003_4_QUALIFYING_RESULTS_STR).unwrap();
assert_false!(race.payload.as_qualifying_results().unwrap().is_empty());
assert_eq!(race, *RACE_2003_4_QUALIFYING_RESULTS);
}
{
let race: Race = serde_json::from_str(RACE_2023_4_QUALIFYING_RESULTS_STR).unwrap();
assert_false!(race.payload.as_qualifying_results().unwrap().is_empty());
assert_eq!(race, *RACE_2023_4_QUALIFYING_RESULTS);
}
{
let race: Race = serde_json::from_str(RACE_2023_10_QUALIFYING_RESULTS_STR).unwrap();
assert_false!(race.payload.as_qualifying_results().unwrap().is_empty());
assert_eq!(race, *RACE_2023_10_QUALIFYING_RESULTS);
}
{
let race: Race = serde_json::from_str(RACE_2023_12_QUALIFYING_RESULTS_STR).unwrap();
assert_false!(race.payload.as_qualifying_results().unwrap().is_empty());
assert_eq!(race, *RACE_2023_12_QUALIFYING_RESULTS);
}
}
#[test]
fn sprint_result() {
let from_str = |result_str| serde_json::from_str::<SprintResult>(result_str).unwrap();
assert_eq!(from_str(SPRINT_RESULT_2023_4_P1_STR), *SPRINT_RESULT_2023_4_P1);
}
#[test]
fn sprint_results() {
let race: Race = serde_json::from_str(RACE_2023_4_SPRINT_RESULTS_STR).unwrap();
assert_false!(race.payload.as_sprint_results().unwrap().is_empty());
assert_eq!(race, *RACE_2023_4_SPRINT_RESULTS);
let race: Race = serde_json::from_str(RACE_2024_5_SPRINT_RESULTS_STR).unwrap();
assert_false!(race.payload.as_sprint_results().unwrap().is_empty());
assert_eq!(race, *RACE_2024_5_SPRINT_RESULTS);
}
#[test]
fn race_result() {
let from_str = |result_str| serde_json::from_str::<RaceResult>(result_str).unwrap();
assert_eq!(from_str(RACE_RESULT_1963_10_P23_STR), *RACE_RESULT_1963_10_P23);
assert_eq!(from_str(RACE_RESULT_2003_4_P1_STR), *RACE_RESULT_2003_4_P1);
assert_eq!(from_str(RACE_RESULT_2003_4_P2_STR), *RACE_RESULT_2003_4_P2);
assert_eq!(from_str(RACE_RESULT_2003_4_P19_STR), *RACE_RESULT_2003_4_P19);
assert_eq!(from_str(RACE_RESULT_2021_12_P1_STR), *RACE_RESULT_2021_12_P1);
assert_eq!(from_str(RACE_RESULT_2021_12_P2_STR), *RACE_RESULT_2021_12_P2);
assert_eq!(from_str(RACE_RESULT_2021_12_P3_STR), *RACE_RESULT_2021_12_P3);
assert_eq!(from_str(RACE_RESULT_2021_12_P10_STR), *RACE_RESULT_2021_12_P10);
assert_eq!(from_str(RACE_RESULT_2023_4_P1_STR), *RACE_RESULT_2023_4_P1);
assert_eq!(from_str(RACE_RESULT_2023_4_P2_STR), *RACE_RESULT_2023_4_P2);
assert_eq!(from_str(RACE_RESULT_2023_4_P20_STR), *RACE_RESULT_2023_4_P20);
}
#[test]
fn race_results() {
let race: Race = serde_json::from_str(RACE_1950_5_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_1950_5_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_1963_10_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_1963_10_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_1998_8_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_1998_8_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_2003_4_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_2003_4_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_2020_9_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_2020_9_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_2021_12_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_2021_12_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_2023_3_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_2023_3_RACE_RESULTS);
let race: Race = serde_json::from_str(RACE_2023_4_RACE_RESULTS_STR).unwrap();
assert_false!(race.payload.as_race_results().unwrap().is_empty());
assert_eq!(race, *RACE_2023_4_RACE_RESULTS);
}
#[test]
fn finishing_status() {
let table: Table = serde_json::from_str(STATUS_TABLE_2022_STR).unwrap();
assert_false!(table.as_status().unwrap().is_empty());
assert_eq!(table, *STATUS_TABLE_2022);
}
#[test]
fn timing() {
let from_str = |timing_str| serde_json::from_str::<Timing>(timing_str).unwrap();
assert_eq!(from_str(TIMING_2023_4_L1_P1_STR), *TIMING_2023_4_L1_P1);
assert_eq!(from_str(TIMING_2023_4_L1_P2_STR), *TIMING_2023_4_L1_P2);
assert_eq!(from_str(TIMING_2023_4_L2_P1_STR), *TIMING_2023_4_L2_P1);
assert_eq!(from_str(TIMING_2023_4_L2_P2_STR), *TIMING_2023_4_L2_P2);
}
#[test]
fn lap() {
let from_str = |lap_str| serde_json::from_str::<Lap>(lap_str).unwrap();
assert_eq!(from_str(LAP_2023_4_L1_STR), *LAP_2023_4_L1);
assert_eq!(from_str(LAP_2023_4_L2_STR), *LAP_2023_4_L2);
}
#[test]
fn laps() {
let race: Race = serde_json::from_str(RACE_2023_4_LAPS_STR).unwrap();
let laps = race.payload.as_laps().unwrap();
assert_false!(laps.is_empty());
laps.iter().for_each(|lap| assert_false!(lap.timings.is_empty()));
assert_eq!(race, *RACE_2023_4_LAPS);
}
#[test]
fn pit_stop() {
let from_str = |pit_stop_str| serde_json::from_str::<PitStop>(pit_stop_str).unwrap();
assert_eq!(from_str(PIT_STOP_2023_4_L10_MAX_STR), *PIT_STOP_2023_4_L10_MAX);
assert_eq!(from_str(PIT_STOP_2023_4_L11_LECLERC_STR), *PIT_STOP_2023_4_L11_LECLERC);
}
#[test]
fn pit_stops() {
let race: Race = serde_json::from_str(RACE_2023_4_PIT_STOPS_STR).unwrap();
assert_false!(race.payload.as_pit_stops().unwrap().is_empty());
assert_eq!(race, *RACE_2023_4_PIT_STOPS);
}
#[test]
fn pagination_is_last_page() {
assert_true!(
Pagination {
limit: 30,
offset: 0,
total: 16
}
.is_last_page()
);
assert_true!(
Pagination {
limit: 10,
offset: 5,
total: 15
}
.is_last_page()
);
assert_false!(
Pagination {
limit: 10,
offset: 4,
total: 15
}
.is_last_page()
);
}
#[test]
fn pagination_is_single_page() {
assert_true!(
Pagination {
limit: 30,
offset: 0,
total: 16
}
.is_single_page()
);
assert_false!(
Pagination {
limit: 10,
offset: 5,
total: 15
}
.is_single_page()
)
}
#[test]
fn pagination_next_page() {
assert_eq!(
Pagination {
limit: 10,
offset: 0,
total: 15
}
.next_page()
.unwrap(),
Pagination {
limit: 10,
offset: 10,
total: 15
}
);
assert_true!(
Pagination {
limit: 10,
offset: 10,
total: 15
}
.next_page()
.is_none()
);
}
#[test]
fn pagination_deserialize() {
const REF_PAGINATION: Pagination = Pagination {
limit: 30,
offset: 0,
total: 16,
};
assert_eq!(
serde_json::from_str::<Pagination>(
r#"{
"limit": "30",
"offset": "0",
"total": "16"
}"#
)
.unwrap(),
REF_PAGINATION
);
assert_eq!(
serde_json::from_str::<Response>(
r#"{
"MRData": {
"xmlns": "",
"series": "f1",
"url": "https://api.jolpi.ca/ergast/f1/races.json",
"limit": "30",
"offset": "0",
"total": "16",
"RaceTable": { "Races": [] }
}
}"#
)
.unwrap()
.pagination,
REF_PAGINATION
);
}
fn verify_race_info_compare<T, U, V, F>(lhs: &Race<T>, mut rhs: Race<U>, mut field: F, new_val: V)
where
V: Clone + PartialEq + std::fmt::Debug,
F: FnMut(&mut Race<U>) -> &mut V,
{
let old_val = field(&mut rhs).clone();
assert_ne!(old_val, new_val);
assert_eq!(lhs.as_info(), rhs.as_info());
assert_eq!(lhs.to_info(), rhs.to_info());
field(&mut rhs).clone_from(&new_val);
assert_ne!(lhs.as_info(), rhs.as_info());
assert_ne!(lhs.to_info(), rhs.to_info());
field(&mut rhs).clone_from(&old_val);
assert_eq!(lhs.as_info(), rhs.as_info());
assert_eq!(lhs.to_info(), rhs.to_info());
}
#[test]
fn race_as_to_info() {
let lhs = RACE_2023_4.clone();
assert_eq!(lhs.as_info(), lhs.as_info());
assert_eq!(lhs.as_info(), Race::from(lhs.clone(), true).as_info());
assert_eq!(lhs.to_info(), lhs.to_info());
assert_eq!(lhs.to_info(), Race::from(lhs.clone(), true).to_info());
let rhs = lhs.clone();
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.season, RACE_NONE.season);
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.round, RACE_NONE.round);
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.url, RACE_NONE.url.clone());
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.race_name, RACE_NONE.race_name.clone());
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.circuit, RACE_NONE.circuit.clone());
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.date, RACE_NONE.date);
verify_race_info_compare(&lhs, rhs.clone(), |r| &mut r.time, RACE_NONE.time);
}
#[test]
fn race_info_as_hash_key() {
let mut map = indexmap::IndexMap::new();
assert_true!(map.insert(RACE_2003_4.to_info(), RACE_2003_4.clone()).is_none());
assert_true!(map.contains_key(&RACE_2003_4.to_info()));
assert_false!(map.contains_key(&RACE_2023_4.to_info()));
assert_true!(map.insert(RACE_2023_4.to_info(), RACE_2023_4.clone()).is_none());
assert_true!(map.contains_key(&RACE_2003_4.to_info()));
assert_true!(map.contains_key(&RACE_2023_4.to_info()));
assert_ne!(*RACE_2003_4, *RACE_2023_4);
assert_ne!(&RACE_2003_4.to_info(), &RACE_2023_4.to_info());
assert_eq!(map[&RACE_2003_4.to_info()], *RACE_2003_4);
assert_eq!(map[&RACE_2023_4.to_info()], *RACE_2023_4);
}
#[test]
fn race_try_map() {
let from = Race::from(RACE_2023_4.clone(), true);
let into = from.clone().try_map::<_, _, Infallible>(|_| Ok(String::from("true")));
assert_eq!(into.as_ref().unwrap().as_info(), from.as_info());
assert_eq!(into.unwrap().payload, String::from("true"));
#[derive(Debug)]
struct DummyError;
impl std::fmt::Display for DummyError {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl std::error::Error for DummyError {}
let into = from.clone().try_map::<Infallible, _, _>(|_| Err(DummyError));
assert_true!(into.is_err());
}
#[test]
fn race_map() {
let from = Race::from(RACE_2023_4.clone(), 1);
let into = from.clone().map(|payload_i32| payload_i32.to_string());
assert_eq!(into.as_info(), from.as_info());
assert_eq!(into.payload, String::from("1"));
}
#[test]
fn race_from() {
let from = RACE_2023_4.clone();
let into = Race::from(from.clone(), String::from("some"));
assert_eq!(into.as_info(), from.as_info());
assert_eq!(into.payload, String::from("some"));
}
fn map_race_schedule(race: Race<Payload>) -> Race<Schedule> {
race.map(|payload| payload.into_schedule().unwrap())
}
#[test]
fn payload_deserialize_helpful_errors() {
static GOOD_STR: &str = formatcp!(r#"{{"QualifyingResults": [{QUALIFYING_RESULT_2023_4_P1_STR}]}}"#);
static BAD_STR: &str = formatcp!(r#"{{"QualifyingResults": [{{"key": "data"}}]}}"#);
let p = serde_json::from_str::<Payload>(GOOD_STR);
assert_eq!(p.unwrap().as_qualifying_results().unwrap()[0], *QUALIFYING_RESULT_2023_4_P1);
let p = serde_json::from_str::<Payload>(BAD_STR);
assert_true!(p.unwrap_err().to_string().contains("missing field `number`"));
}
#[test]
fn race_schedule_accessors() {
let reference = RACE_2023_4_SCHEDULE.clone();
let expected = reference.clone().payload.into_schedule().unwrap();
let actual = map_race_schedule(reference.clone());
assert_eq!(actual.schedule(), &expected);
assert_eq!(actual.into_schedule(), expected);
}
fn map_race_multi_results<T: PayloadInnerList>(race: Race<Payload>) -> Race<Vec<T>> {
race.map(|payload| T::try_into_inner_from(payload).unwrap())
}
fn map_race_single_result<T: PayloadInnerList>(race: Race<Payload>) -> Race<T> {
map_race_multi_results(race).map(|payload| payload.into_iter().next().unwrap())
}
#[test]
fn race_qualifying_result_accessors() {
let reference = RACE_2023_4_QUALIFYING_RESULTS.clone();
let expected = reference.clone().payload.into_qualifying_results().unwrap();
let actual = map_race_multi_results(reference.clone());
assert_eq!(actual.qualifying_results(), &expected);
assert_eq!(actual.into_qualifying_results(), expected);
let actual = map_race_single_result(reference.clone());
assert_eq!(actual.qualifying_result(), &expected[0]);
assert_eq!(actual.into_qualifying_result(), expected[0]);
}
#[test]
fn race_sprint_result_accessors() {
let reference = RACE_2023_4_SPRINT_RESULTS.clone();
let expected = reference.clone().payload.into_sprint_results().unwrap();
let actual = map_race_multi_results(reference.clone());
assert_eq!(actual.sprint_results(), &expected);
assert_eq!(actual.into_sprint_results(), expected);
let actual = map_race_single_result(reference.clone());
assert_eq!(actual.sprint_result(), &expected[0]);
assert_eq!(actual.into_sprint_result(), expected[0]);
}
#[test]
fn race_race_result_accessors() {
let reference = RACE_2023_4_RACE_RESULTS.clone();
let expected = reference.clone().payload.into_race_results().unwrap();
let actual = map_race_multi_results(reference.clone());
assert_eq!(actual.race_results(), &expected);
assert_eq!(actual.into_race_results(), expected);
let actual = map_race_single_result(reference.clone());
assert_eq!(actual.race_result(), &expected[0]);
assert_eq!(actual.into_race_result(), expected[0]);
}
#[test]
fn deserialize_possible_no_number() {
#[derive(Deserialize, Debug)]
struct Proxy {
#[serde(deserialize_with = "super::deserialize_possible_no_number")]
number: u32,
}
assert_eq!(serde_json::from_str::<Proxy>(r#"{"number": "10"}"#).unwrap().number, 10);
assert_eq!(serde_json::from_str::<Proxy>(r#"{"number": "None"}"#).unwrap().number, RaceResult::NO_NUMBER);
}
#[test]
fn position() {
assert_eq!(Position::Retired, Position::R);
assert_ne!(Position::Retired, Position::E);
let pos = Position::Finished(10);
assert!(matches!(pos, Position::Finished(_)));
assert_false!(matches!(pos, Position::E));
match pos {
Position::Finished(pos) => assert_eq!(pos, 10),
_ => panic!("Expected Finished variant"),
};
}
#[test]
fn position_deserialize() {
assert!(matches!(serde_json::from_str::<Position>("\"R\"").unwrap(), Position::R));
assert!(matches!(serde_json::from_str::<Position>("\"D\"").unwrap(), Position::D));
assert!(matches!(serde_json::from_str::<Position>("\"E\"").unwrap(), Position::E));
assert!(matches!(serde_json::from_str::<Position>("\"W\"").unwrap(), Position::W));
assert!(matches!(serde_json::from_str::<Position>("\"F\"").unwrap(), Position::F));
assert!(matches!(serde_json::from_str::<Position>("\"N\"").unwrap(), Position::N));
let Position::Finished(pos) = serde_json::from_str("\"10\"").unwrap() else {
panic!("Expected Finished variant")
};
assert_eq!(pos, 10);
assert_true!(serde_json::from_str::<Position>("\"unknown\"").is_err());
}
const RESPONSE_NONE: LazyLock<Response> = LazyLock::new(|| Response {
xmlns: "".into(),
series: "f1".into(),
url: Url::parse("https://api.jolpi.ca/ergast/f1/").unwrap(),
pagination: Pagination {
limit: 30,
offset: 0,
total: 0,
},
table: Table::Seasons { seasons: vec![] },
});
fn make_response_with_table(table: Table) -> Response {
Response {
table,
..RESPONSE_NONE.clone()
}
}
const RESPONSE_SEASONS_NONE: LazyLock<Response> =
LazyLock::new(|| make_response_with_table(Table::Seasons { seasons: vec![] }));
const RESPONSE_SEASONS_ONE: LazyLock<Response> = LazyLock::new(|| {
make_response_with_table(Table::Seasons {
seasons: vec![SEASON_2000.clone()],
})
});
const RESPONSE_SEASONS_TWO: LazyLock<Response> = LazyLock::new(|| {
make_response_with_table(Table::Seasons {
seasons: vec![SEASON_2000.clone(), SEASON_2023.clone()],
})
});
const RESPONSE_DRIVERS_NONE: LazyLock<Response> =
LazyLock::new(|| make_response_with_table(Table::Drivers { drivers: vec![] }));
const RESPONSE_DRIVERS_ONE: LazyLock<Response> = LazyLock::new(|| {
make_response_with_table(Table::Drivers {
drivers: vec![DRIVER_MAX.clone()],
})
});
const RESPONSE_DRIVERS_TWO: LazyLock<Response> = LazyLock::new(|| {
make_response_with_table(Table::Drivers {
drivers: vec![DRIVER_MAX.clone(), DRIVER_LECLERC.clone()],
})
});
fn verify_response_info_compare<V, F>(lhs: &Response, mut rhs: Response, mut field: F, new_val: V)
where
V: Clone + PartialEq + std::fmt::Debug,
F: FnMut(&mut Response) -> &mut V,
{
let old_val = field(&mut rhs).clone();
assert_ne!(old_val, new_val);
assert_eq!(lhs.as_info(), rhs.as_info());
assert_eq!(lhs.to_info(), rhs.to_info());
field(&mut rhs).clone_from(&new_val);
assert_ne!(lhs.as_info(), rhs.as_info());
assert_ne!(lhs.to_info(), rhs.to_info());
field(&mut rhs).clone_from(&old_val);
assert_eq!(lhs.as_info(), rhs.as_info());
assert_eq!(lhs.to_info(), rhs.to_info());
}
#[test]
fn response_as_to_info() {
let lhs = RESPONSE_SEASONS_NONE.clone();
let rhs_diff_pagination = Response {
pagination: Pagination {
limit: 30,
offset: 10,
total: 0,
},
..lhs.clone()
};
let rhs_diff_table = RESPONSE_DRIVERS_NONE.clone();
assert_eq!(lhs.as_info(), lhs.as_info());
assert_eq!(lhs.as_info(), rhs_diff_pagination.as_info());
assert_eq!(lhs.as_info(), rhs_diff_table.as_info());
assert_eq!(lhs.to_info(), lhs.to_info());
assert_eq!(lhs.to_info(), rhs_diff_pagination.to_info());
assert_eq!(lhs.to_info(), rhs_diff_table.to_info());
let rhs = lhs.clone();
verify_response_info_compare(&lhs, rhs.clone(), |r| &mut r.xmlns, "other".into());
verify_response_info_compare(&lhs, rhs.clone(), |r| &mut r.series, "f2".into());
verify_response_info_compare(&lhs, rhs.clone(), |r| &mut r.url, Url::parse("https://example.com").unwrap());
}
#[test]
fn response_into_seasons() {
let response = RESPONSE_SEASONS_TWO.clone();
let seasons = response.into_seasons().unwrap();
assert_eq!(seasons.len(), 2);
assert_eq!(seasons, vec![SEASON_2000.clone(), SEASON_2023.clone()]);
}
#[test]
fn response_into_seasons_error_bad_table_variant() {
assert!(matches!(RESPONSE_DRIVERS_NONE.clone().into_seasons(), Err(Error::BadTableVariant)));
}
#[test]
fn response_into_season() {
assert_eq!(RESPONSE_SEASONS_ONE.clone().into_season().unwrap(), *SEASON_2000);
}
#[test]
fn response_into_season_error_bad_table_variant() {
assert!(matches!(RESPONSE_DRIVERS_NONE.clone().into_season(), Err(Error::BadTableVariant)));
}
#[test]
fn response_into_season_error_not_found() {
assert!(matches!(RESPONSE_SEASONS_NONE.clone().into_season(), Err(Error::NotFound)));
}
#[test]
fn response_into_season_error_too_many() {
assert!(matches!(RESPONSE_SEASONS_TWO.clone().into_season(), Err(Error::TooMany)));
}
#[test]
fn response_as_seasons() {
let response = &*RESPONSE_SEASONS_TWO;
assert_eq!(response.as_seasons().unwrap().len(), 2);
assert_eq!(response.as_seasons().unwrap(), &vec![SEASON_2000.clone(), SEASON_2023.clone()]);
}
#[test]
fn response_as_seasons_error_bad_table_variant() {
assert!(matches!(RESPONSE_DRIVERS_NONE.as_seasons(), Err(Error::BadTableVariant)));
}
#[test]
fn response_as_season() {
let response = &*RESPONSE_SEASONS_ONE;
assert_eq!(response.as_season().unwrap(), &*SEASON_2000);
assert_eq!(response.as_season().unwrap(), &*SEASON_2000);
}
#[test]
fn response_as_season_error_bad_table_variant() {
assert!(matches!(RESPONSE_DRIVERS_NONE.as_season(), Err(Error::BadTableVariant)));
}
#[test]
fn response_as_season_error_not_found() {
assert!(matches!(RESPONSE_SEASONS_NONE.as_season(), Err(Error::NotFound)));
}
#[test]
fn response_as_season_error_too_many() {
assert!(matches!(RESPONSE_SEASONS_TWO.as_season(), Err(Error::TooMany)));
}
#[test]
fn response_into_drivers() {
let response = RESPONSE_DRIVERS_TWO.clone();
let drivers = response.into_drivers().unwrap();
assert_eq!(drivers.len(), 2);
assert_eq!(drivers, vec![DRIVER_MAX.clone(), DRIVER_LECLERC.clone()]);
}
#[test]
fn response_into_drivers_error_bad_table_variant() {
assert!(matches!(RESPONSE_NONE.clone().into_drivers(), Err(Error::BadTableVariant)));
}
#[test]
fn response_into_driver() {
assert_eq!(RESPONSE_DRIVERS_ONE.clone().into_driver().unwrap(), *DRIVER_MAX);
}
#[test]
fn response_into_driver_error_bad_table_variant() {
assert!(matches!(RESPONSE_NONE.clone().into_driver(), Err(Error::BadTableVariant)));
}
#[test]
fn response_into_driver_error_not_found() {
assert!(matches!(RESPONSE_DRIVERS_NONE.clone().into_driver(), Err(Error::NotFound)));
}
#[test]
fn response_into_driver_error_too_many() {
assert!(matches!(RESPONSE_DRIVERS_TWO.clone().into_driver(), Err(Error::TooMany)));
}
#[test]
fn response_as_drivers() {
let response = &*RESPONSE_DRIVERS_TWO;
assert_eq!(response.as_drivers().unwrap().len(), 2);
assert_eq!(response.as_drivers().unwrap(), &vec![DRIVER_MAX.clone(), DRIVER_LECLERC.clone()]);
}
#[test]
fn response_as_drivers_error_bad_table_variant() {
assert!(matches!(RESPONSE_NONE.as_drivers(), Err(Error::BadTableVariant)));
}
#[test]
fn response_as_driver() {
let response = &*RESPONSE_DRIVERS_ONE;
assert_eq!(response.as_driver().unwrap(), &*DRIVER_MAX);
assert_eq!(response.as_driver().unwrap(), &*DRIVER_MAX);
}
#[test]
fn response_as_driver_error_bad_table_variant() {
assert!(matches!(RESPONSE_NONE.as_driver(), Err(Error::BadTableVariant)));
}
#[test]
fn response_as_driver_error_not_found() {
assert!(matches!(RESPONSE_DRIVERS_NONE.as_driver(), Err(Error::NotFound)));
}
#[test]
fn response_as_driver_error_too_many() {
assert!(matches!(RESPONSE_DRIVERS_TWO.as_driver(), Err(Error::TooMany)));
}
}