use time::Date;
use crate::backends::{ApiFootballProvider, EspnProvider, FootballDataProvider};
use crate::domain::{Bracket, Calendar, Group, Match, MatchDetail};
use crate::error::{DataError, Result};
use crate::transport::Http;
#[allow(async_fn_in_trait)]
pub trait ScoreProvider {
fn name(&self) -> &'static str;
async fn calendar(&self) -> Result<Calendar>;
async fn scoreboard(&self, day: Option<Date>) -> Result<Vec<Match>>;
async fn standings(&self) -> Result<Vec<Group>>;
async fn bracket(&self) -> Result<Bracket>;
async fn match_detail(&self, id: &str) -> Result<MatchDetail>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ProviderKind {
#[default]
Espn,
ApiFootball,
FootballData,
}
impl ProviderKind {
#[must_use]
pub fn all() -> [ProviderKind; 3] {
[
ProviderKind::Espn,
ProviderKind::ApiFootball,
ProviderKind::FootballData,
]
}
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
ProviderKind::Espn => "espn",
ProviderKind::ApiFootball => "api-football",
ProviderKind::FootballData => "football-data",
}
}
#[must_use]
pub fn parse(s: &str) -> Option<ProviderKind> {
match s.trim().to_ascii_lowercase().as_str() {
"espn" => Some(ProviderKind::Espn),
"api-football" | "apifootball" | "api_football" => Some(ProviderKind::ApiFootball),
"football-data" | "footballdata" | "football_data" => Some(ProviderKind::FootballData),
_ => None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ProviderConfig {
pub kind: ProviderKind,
pub api_football_key: Option<String>,
pub football_data_key: Option<String>,
}
pub enum Provider {
Espn(EspnProvider),
ApiFootball(ApiFootballProvider),
FootballData(FootballDataProvider),
}
impl Provider {
pub fn from_config(config: &ProviderConfig, http: Http) -> Result<Self> {
match config.kind {
ProviderKind::Espn => Ok(Provider::Espn(EspnProvider::new(http))),
ProviderKind::ApiFootball => {
let key = config
.api_football_key
.clone()
.filter(|k| !k.trim().is_empty())
.ok_or(DataError::MissingKey("API-Football"))?;
Ok(Provider::ApiFootball(ApiFootballProvider::new(http, key)))
}
ProviderKind::FootballData => {
let key = config
.football_data_key
.clone()
.filter(|k| !k.trim().is_empty())
.ok_or(DataError::MissingKey("football-data.org"))?;
Ok(Provider::FootballData(FootballDataProvider::new(http, key)))
}
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Provider::Espn(p) => p.name(),
Provider::ApiFootball(p) => p.name(),
Provider::FootballData(p) => p.name(),
}
}
pub async fn calendar(&self) -> Result<Calendar> {
match self {
Provider::Espn(p) => p.calendar().await,
Provider::ApiFootball(p) => p.calendar().await,
Provider::FootballData(p) => p.calendar().await,
}
}
pub async fn scoreboard(&self, day: Option<Date>) -> Result<Vec<Match>> {
match self {
Provider::Espn(p) => p.scoreboard(day).await,
Provider::ApiFootball(p) => p.scoreboard(day).await,
Provider::FootballData(p) => p.scoreboard(day).await,
}
}
pub async fn standings(&self) -> Result<Vec<Group>> {
match self {
Provider::Espn(p) => p.standings().await,
Provider::ApiFootball(p) => p.standings().await,
Provider::FootballData(p) => p.standings().await,
}
}
pub async fn bracket(&self) -> Result<Bracket> {
match self {
Provider::Espn(p) => p.bracket().await,
Provider::ApiFootball(p) => p.bracket().await,
Provider::FootballData(p) => p.bracket().await,
}
}
pub async fn match_detail(&self, id: &str) -> Result<MatchDetail> {
match self {
Provider::Espn(p) => p.match_detail(id).await,
Provider::ApiFootball(p) => p.match_detail(id).await,
Provider::FootballData(p) => p.match_detail(id).await,
}
}
}