use std::collections::HashMap;
use serde::{Deserialize, Deserializer, Serialize};
use chrono::{DateTime, NaiveDate, TimeZone, Utc};
pub use reqwest;
const API_INDEX_URL: &str = "https://components.ifsc-climbing.org/results-api.php?api=index";
const API_CALENDAR_URL: &str = "https://components.ifsc-climbing.org/results-api.php?api=season_leagues_calendar&league=%LEAGUE_ID%";
#[derive(Deserialize)]
struct IfscLeagueRaw {
id: i32,
name: String
}
#[derive(Deserialize)]
struct IfscSeasonRaw {
id: i32,
name: String,
leagues: Vec<IfscLeagueRaw>
}
#[derive(Deserialize)]
struct IfscCurrentSeasonRaw {
id: i32
}
#[derive(Deserialize)]
struct IfscIndexRaw {
current: IfscCurrentSeasonRaw,
seasons: Vec<IfscSeasonRaw>
}
#[derive(Serialize)]
pub struct IfscLeague {
pub id: i32,
pub parent_id: i32,
pub name: String
}
#[derive(Serialize)]
pub struct IfscSeason {
pub id: i32,
pub name: String,
pub leagues: Vec<i32>
}
#[derive(Serialize)]
pub struct IfscIndex {
pub current_season_id: i32,
pub seasons: HashMap<i32, IfscSeason>,
pub leagues: HashMap<i32, IfscLeague>
}
impl IfscIndex {
pub async fn retrieve() -> Result<IfscIndex, reqwest::Error> {
let parsed = reqwest::get(API_INDEX_URL)
.await?
.error_for_status()?
.json::<IfscIndexRaw>()
.await?;
Ok(Self::from_raw(&parsed))
}
fn from_raw(raw: &IfscIndexRaw) -> IfscIndex {
let mut seasons = HashMap::new();
let mut leagues = HashMap::new();
for season in &raw.seasons {
let mut season_leagues_ids = Vec::new();
for league in &season.leagues {
leagues.insert(league.id, IfscLeague {
id: league.id,
parent_id: season.id,
name: league.name.clone()
});
season_leagues_ids.push(league.id);
}
seasons.insert(season.id, IfscSeason {
id: season.id,
name: season.name.clone(),
leagues: season_leagues_ids
});
}
IfscIndex {
seasons,
leagues,
current_season_id: raw.current.id
}
}
}
fn deserialize_event_datetime<'de, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
where
D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
Utc.datetime_from_str(&s, "%Y-%m-%d %H:%M:%S UTC").map_err(serde::de::Error::custom)
}
fn deserialize_event_date<'de, D>(deserializer: D) -> Result<NaiveDate, D::Error>
where
D: Deserializer<'de>
{
let s = String::deserialize(deserializer)?;
NaiveDate::parse_from_str(&s, "%Y-%m-%d").map_err(serde::de::Error::custom)
}
#[derive(Clone, Deserialize)]
pub struct IfscEvent {
pub event_id: i32,
pub event: String,
#[serde(deserialize_with = "deserialize_event_datetime")]
pub starts_at: DateTime<Utc>,
#[serde(deserialize_with = "deserialize_event_datetime")]
pub ends_at: DateTime<Utc>,
#[serde(deserialize_with = "deserialize_event_date")]
pub local_start_date: NaiveDate,
#[serde(deserialize_with = "deserialize_event_date")]
pub local_end_date: NaiveDate
}
#[derive(Clone, Deserialize)]
pub struct IfscCalendar {
pub events: Vec<IfscEvent>
}
impl IfscCalendar {
pub async fn retrieve(league_id: i32) -> Result<IfscCalendar, reqwest::Error> {
let url = API_CALENDAR_URL.replace("%LEAGUE_ID%", &league_id.to_string());
reqwest::get(url)
.await?
.error_for_status()?
.json::<IfscCalendar>()
.await
}
pub fn empty() -> IfscCalendar {
IfscCalendar {
events: Vec::new()
}
}
}