mlb-api 1.0.3

Endpoints for MLB's public Statcast API.
Documentation
//! Returns a list of uniform assets. No image sadly, just item names.

use crate::cache::{Requestable, RequestableEntrypoint};
use crate::season::SeasonId;
use crate::team::TeamId;
use crate::Copyright;
use crate::request::RequestURL;
use bon::Builder;
use itertools::Itertools;
use serde::Deserialize;
use std::fmt::{Display, Formatter};

#[cfg(feature = "cache")]
use crate::{rwlock_const_new, RwLock, cache::CacheTable};

/// A [`Vec`] of [`TeamUniformAssets`]s
///
/// Split by Team
#[derive(Debug, Deserialize, PartialEq, Clone)]
pub struct UniformsResponse {
    pub copyright: Copyright,
    #[serde(rename = "uniforms")] pub teams: Vec<TeamUniformAssets>,
}

id!(UniformAssetId { uniformAssetId: u32 });
id!(UniformAssetCategoryId { uniformAssetTypeId: u32 });

/// A [`Vec`] of a team's [`UniformAsset`]s
#[derive(Debug, Deserialize, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TeamUniformAssets {
    pub team_id: TeamId,
    pub uniform_assets: Vec<UniformAsset>,
}

/// A uniform asset, like a Blue Jays Canada Day Hat.
#[derive(Debug, Deserialize, Clone)]
pub struct UniformAsset {
    #[serde(rename = "uniformAssetText")] pub name: String,
    #[serde(rename = "uniformAssetType")] pub category: UniformAssetCategory,
    #[serde(rename = "uniformAssetCode")] pub code: String,
    #[serde(flatten)]
    pub id: UniformAssetId,
}

/// Category; Hat, Shirt, etc.
#[derive(Debug, Deserialize, Clone)]
pub struct UniformAssetCategory {
    #[serde(rename = "uniformAssetTypeText")] pub name: String,
    #[serde(rename = "uniformAssetTypeCode")] pub code: String,
    #[serde(rename = "uniformAssetTypeDesc")] pub description: String,
    #[serde(rename = "uniformAssetTypeId")] pub id: UniformAssetCategoryId,
}

id_only_eq_impl!(UniformAsset, id);
id_only_eq_impl!(UniformAssetCategory, id);

/// Returns a [`UniformsResponse`]
#[derive(Builder)]
#[builder(derive(Into))]
pub struct UniformsRequest {
    teams: Vec<TeamId>,
    #[builder(into)]
    season: Option<SeasonId>,
}

impl<S: uniforms_request_builder::State + uniforms_request_builder::IsComplete> crate::request::RequestURLBuilderExt for UniformsRequestBuilder<S> {
    type Built = UniformsRequest;
}

impl Display for UniformsRequest {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "http://statsapi.mlb.com/api/v1/uniforms/team{}", gen_params! { "teamIds": self.teams.iter().copied().join(","), "season"?: self.season })
    }
}

impl RequestURL for UniformsRequest {
    type Response = UniformsResponse;
}

#[cfg(feature = "cache")]
static CACHE: RwLock<CacheTable<UniformAsset>> = rwlock_const_new(CacheTable::new());

impl Requestable for UniformAsset {
    type Identifier = String;
    type URL = UniformsRequest;

    fn id(&self) -> &Self::Identifier {
        &self.code
    }

    fn url_for_id(id: &Self::Identifier) -> Self::URL {
        UniformsRequest::builder()
            .teams(vec![TeamId::new(id.split_once('_').and_then(|(num, _)| num.parse().ok()).unwrap_or(0))])
            .build()
    }

    fn get_entries(response: <Self::URL as RequestURL>::Response) -> impl IntoIterator<Item=Self>
    where
        Self: Sized
    {
        response.teams.into_iter().flat_map(|team| team.uniform_assets)
    }

    #[cfg(feature = "cache")]
    fn get_cache_table() -> &'static RwLock<CacheTable<Self>>
    where
        Self: Sized
    {
        &CACHE
    }
}

impl RequestableEntrypoint for UniformAsset {
    type Complete = Self;

    fn id(&self) -> &<<Self as RequestableEntrypoint>::Complete as Requestable>::Identifier {
        &self.code
    }
}

#[cfg(test)]
mod tests {
    use crate::team::uniforms::UniformsRequest;
    use crate::team::TeamsRequest;
    use crate::request::RequestURLBuilderExt;

    #[tokio::test]
    async fn parse_all_mlb_teams_this_season() {
        let mlb_teams = TeamsRequest::mlb_teams().build_and_get().await.unwrap();
        let team_ids = mlb_teams.teams.into_iter().map(|team| team.id).collect::<Vec<_>>();
        let _ = UniformsRequest::builder().teams(team_ids).build_and_get().await.unwrap();
    }
}