Skip to main content

mlb_api/requests/stats/
mod.rs

1//! # The Stats API
2//!
3//! The second most likely reason you're here.
4//!
5//! [`mlb_api`](crate)'s stats system designed to be simple to use.
6//! Create a stats type, then create the hydrations which have stats in it, then request it.
7//!
8//! Almost all the types here are for private use and occasionally might be
9//!
10//! ## Examples
11//! See [`stats_hydrations!`](crate::stats_hydrations) for examples on how to use the macro.
12//!
13//! ## Notes
14//! 1. The stat type registry is admittedly incomplete, only some stat types are implemented (see [`stat_types`]), more will come in the future.
15//! 2. Some stats are only implemented for specific [`StatGroup`](crate::meta::StatGroup)s, if you have a complicated request such as:
16//! ```
17//! stats_hydrations! {
18//!     pub struct TechnicalStats {
19//!         [Sabermetrics, Career] + [Hitting, Pitching, Fielding, Catching]
20//!     }
21//! }
22//! ```
23//! `stats.sabermetrics.fielding` and `.catching` will be of type `()`.
24//! 3. It is an intentional decision that [`SituationCode`](crate::meta::SituationCode)s
25//! are not registered in an enum as if new cutting-edge situation-codes come out,
26//! this API being outdated shouldn't limit in that factor.
27
28#![allow(clippy::trait_duplication_in_bounds, reason = "serde")]
29
30use serde::de::DeserializeOwned;
31use serde::Deserialize;
32use std::convert::Infallible;
33use std::fmt::Debug;
34
35#[doc(hidden)]
36pub mod macros;
37#[doc(hidden)]
38pub mod raw;
39#[doc(hidden)]
40pub mod wrappers;
41pub mod leaders;
42#[doc(hidden)]
43mod units;
44#[doc(hidden)]
45pub mod parse;
46pub mod derived;
47
48pub use units::*;
49
50#[cfg(test)]
51mod tests;
52
53// pub use derived::*;
54
55pub trait Stat: Debug + Clone + PartialEq + Default {
56	type Split: DeserializeOwned;
57
58	type TryFromSplitError;
59
60	/// # Errors
61	/// See [`Self::TryFromSplitError`]
62	fn from_splits(splits: impl Iterator<Item=Self::Split>) -> Result<Self, Self::TryFromSplitError> where Self: Sized;
63}
64
65/// Represents the types defined in [`raw`], not the wrapped final types. In the serialized format, this represents the `stat` field.
66pub trait RawStat: Debug + DeserializeOwned + Clone + PartialEq + Default {}
67
68impl RawStat for () {}
69impl SingletonSplitStat for () {}
70
71/// Represents types that are made from a single 'split' in the serialized format (able to be deserialized)
72pub trait SingletonSplitStat: Debug + DeserializeOwned + Clone + PartialEq + Default {
73
74}
75
76impl<T: SingletonSplitStat> Stat for T {
77	type Split = Self;
78
79	type TryFromSplitError = &'static str;
80
81	fn from_splits(mut splits: impl Iterator<Item=Self::Split>) -> Result<Self, Self::TryFromSplitError>
82	where
83		Self: Sized
84	{
85		splits.next().ok_or("length of stat splits is not >= 1, cannot convert to unit type.")
86	}
87}
88
89#[allow(unused)]
90pub(crate) trait StatTypeStats {
91	type Hitting: Stat;
92
93	type Pitching: Stat;
94
95	type Fielding: Stat;
96
97	type Catching: Stat;
98}
99
100#[derive(Debug, Deserialize, PartialEq, Clone, Default)]
101pub struct PlayStat {
102	// pub play: Play,
103}
104
105// todo: replace with real struct once game stuff is implemented
106pub type PitchStat = ();
107
108impl RawStat for PlayStat {}
109
110impl<T: Stat> Stat for Option<T> {
111	type Split = T::Split;
112	type TryFromSplitError = Infallible;
113
114	fn from_splits(splits: impl Iterator<Item=Self::Split>) -> Result<Self, Self::TryFromSplitError>
115	where
116		Self: Sized
117	{
118		Ok(T::from_splits(splits).ok())
119	}
120}
121
122#[doc(hidden)]
123pub mod stat_types {
124	use super::{StatTypeStats, PlayStat, PitchStat};
125	use crate::stats::raw::{catching, fielding, hitting, pitching, FieldedMatchup};
126	use crate::stats::wrappers::{AccumulatedVsPlayerMatchup, ByMonth, ByPosition, BySeason, ByWeekday, Career, Map, Map2D, SingleMatchup, WithGame, WithHomeAndAway, WithMonth, WithNone, WithPlayer, WithPositionAndSeason, WithSeason, WithTeam, WithWeekday, WithWinLoss};
127
128	macro_rules! stat_type_stats {
129		($name:ident {
130			$hitting:ty,
131			$pitching:ty,
132			$catching:ty,
133			$fielding:ty $(,)?
134		}) => {
135			::pastey::paste! {
136				#[doc(hidden)]
137				pub struct [<__ $name StatTypeStats>];
138
139				impl StatTypeStats for [<__ $name StatTypeStats>] {
140					type Hitting = $hitting;
141					type Pitching = $pitching;
142					type Fielding = $fielding;
143					type Catching = $catching;
144				}
145			}
146		};
147	}
148
149	// NOTES
150	// 1. Make sure all modules are correct, `hitting`, `pitching`, `catching`, then `fielding`.
151
152	stat_type_stats!(Projected { WithPlayer<hitting::__ProjectedStatsData>, WithPlayer<pitching::__ProjectedStatsData>, (), () });
153	stat_type_stats!(YearByYear { Map<WithSeason<hitting::__YearByYearStatsData>, BySeason>, Map<WithSeason<pitching::__YearByYearStatsData>, BySeason>, Map<WithSeason<catching::__YearByYearStatsData>, BySeason>, Map2D<WithPositionAndSeason<fielding::__YearByYearStatsData>, BySeason, ByPosition> });
154	stat_type_stats!(YearByYearAdvanced { Map<WithSeason<hitting::__YearByYearAdvancedStatsData>, BySeason>, Map<WithSeason<pitching::__YearByYearAdvancedStatsData>, BySeason>, (), () });
155	stat_type_stats!(Season { WithSeason<hitting::__SeasonStatsData>, WithSeason<pitching::__SeasonStatsData>, WithSeason<catching::__SeasonStatsData>, Map2D<WithPositionAndSeason<fielding::__SeasonStatsData>, BySeason, ByPosition> });
156	stat_type_stats!(Career { Career<hitting::__CareerStatsData>, Career<pitching::__CareerStatsData>, Career<catching::__CareerStatsData>, Career<fielding::__CareerStatsData> });
157	stat_type_stats!(SeasonAdvanced { WithSeason<hitting::__SeasonAdvancedStatsData>, WithSeason<pitching::__SeasonAdvancedStatsData>, (), () });
158	stat_type_stats!(CareerAdvanced { Career<hitting::__CareerAdvancedStatsData>, Career<pitching::__CareerAdvancedStatsData>, (), () });
159	stat_type_stats!(GameLog { Vec<WithGame<hitting::__GameLogStatsData>>, Vec<WithGame<pitching::__GameLogStatsData>>, Vec<WithGame<catching::__GameLogStatsData>>, Vec<WithGame<fielding::__GameLogStatsData>> });
160	stat_type_stats!(PlayLog { Vec<SingleMatchup<PlayStat>>, Vec<SingleMatchup<PlayStat>>, Vec<SingleMatchup<PlayStat>>, Vec<SingleMatchup<PlayStat>> });
161	stat_type_stats!(PitchLog { Vec<SingleMatchup<PitchStat>>, Vec<SingleMatchup<PitchStat>>, Vec<SingleMatchup<PitchStat>>, Vec<SingleMatchup<PitchStat>> });
162	// 'metricLog'?
163	// 'metricAverages'?
164	// stat_type_stats!(PitchArsenal { Vec<PitchUsage>, Vec<PitchUsage>, (), () }); // has no stat group
165	// 'outsAboveAverage'?
166	stat_type_stats!(ExpectedStatistics { WithPlayer<hitting::__ExpectedStatisticsStatsData>, WithPlayer<pitching::__ExpectedStatisticsStatsData>, (), () });
167	stat_type_stats!(Sabermetrics { WithPlayer<hitting::__SabermetricsStatsData>, WithPlayer<pitching::__SabermetricsStatsData>, (), () });
168	// stat_type_stats!(SprayChart { SprayChart, SprayChart, (), () }); // does not have statGroup on the response
169	// 'tracking'?
170	// stat_type_stats!(VsPlayer { AccumulatedMatchup<VsPlayerHittingStats>, AccumulatedMatchup<VsPlayerPitchingStats>, (), () });
171	// stat_type_stats!(VsPlayerTotal { AccumulatedMatchup<VsPlayerHittingStats>, AccumulatedMatchup<VsPlayerPitchingStats>, (), () });
172	stat_type_stats!(VsPlayer5Y { AccumulatedVsPlayerMatchup<hitting::__VsPlayerStatsData>, AccumulatedVsPlayerMatchup<pitching::__VsPlayerStatsData>, (), () });
173	// stat_type_stats!(VsTeam { Multiple<AccumulatedVsTeamSeasonalPitcherSplit<HittingStats>>, (), (), () });
174	// stat_type_stats!(VsTeam5Y { Multiple<AccumulatedVsTeamSeasonalPitcherSplit<HittingStats>>, (), (), () });
175	// stat_type_stats!(VsTeamTotal { AccumulatedVsTeamTotalMatchup<HittingStats>, (), (), () });
176	stat_type_stats!(LastXGames { WithTeam<hitting::__LastXGamesStatsData>, WithTeam<pitching::__LastXGamesStatsData>, WithTeam<catching::__LastXGamesStatsData>, WithTeam<fielding::__LastXGamesStatsData> });
177	stat_type_stats!(ByDateRange { WithTeam<hitting::__ByDateRangeStatsData>, WithTeam<pitching::__ByDateRangeStatsData>, WithTeam<catching::__ByDateRangeStatsData>, WithTeam<fielding::__ByDateRangeStatsData> });
178	stat_type_stats!(ByDateRangeAdvanced { WithTeam<hitting::__ByDateRangeAdvancedStatsData>, WithTeam<pitching::__ByDateRangeAdvancedStatsData>, WithTeam<catching::__ByDateRangeAdvancedStatsData>, WithTeam<fielding::__ByDateRangeAdvancedStatsData> });
179	stat_type_stats!(ByMonth { Map<WithMonth<hitting::__ByMonthStatsData>, ByMonth>, Map<WithMonth<pitching::__ByMonthStatsData>, ByMonth>, Map<WithMonth<catching::__ByMonthStatsData>, ByMonth>, Map<WithMonth<fielding::__ByMonthStatsData>, ByMonth> });
180	stat_type_stats!(ByDayOfWeek { Map<WithWeekday<hitting::__ByDayOfWeekStatsData>, ByWeekday>, Map<WithWeekday<pitching::__ByDayOfWeekStatsData>, ByWeekday>, Map<WithWeekday<catching::__ByDayOfWeekStatsData>, ByWeekday>, Map<WithWeekday<fielding::__ByDayOfWeekStatsData>, ByWeekday> });
181	stat_type_stats!(HomeAndAway { WithHomeAndAway<hitting::__HomeAndAwayStatsData>, WithHomeAndAway<pitching::__HomeAndAwayStatsData>, WithHomeAndAway<catching::__HomeAndAwayStatsData>, WithHomeAndAway<fielding::__HomeAndAwayStatsData> });
182	stat_type_stats!(WinLoss { WithWinLoss<hitting::__WinLossStatsData>, WithWinLoss<pitching::__WinLossStatsData>, WithWinLoss<catching::__WinLossStatsData>, WithWinLoss<fielding::__WinLossStatsData> });
183	// stat_type_stats!(Rankings { WithPlayerAndTeam<hitting::__RankingsStatsData>, WithPlayerAndTeam<pitching::__RankingsStatsData>, (), () });
184	// stat_type_stats!(RankingsByYear { Map<WithPlayerAndTeam<hitting::__RankingsByYearStatsData>, BySeason>, Map<WithPlayerAndTeam<pitching::__RankingsByYearStatsData>, BySeason>, (), () });
185	// stat_type_stats!(HotColdZones { HittingHotColdZones, PitchingHotColdZones, (), () }); // has no stat group
186	stat_type_stats!(OpponentsFaced { Vec<FieldedMatchup>, Vec<FieldedMatchup>, Vec<FieldedMatchup>, Vec<FieldedMatchup> });
187	stat_type_stats!(StatSplits { WithSeason<hitting::__StatSplitsStatsData>, WithSeason<pitching::__StatSplitsStatsData>, (), () });
188	stat_type_stats!(StatSplitsAdvanced { WithSeason<hitting::__StatSplitsAdvancedStatsData>, WithSeason<pitching::__StatSplitsAdvancedStatsData>, (), () });
189	// stat_type_stats!(AtGameStart { Multiple<WithGame<hitting::AtGameStart>>, Multiple<WithGame<pitching::AtGameStart>>, Multiple<WithGame<catching::AtGameStart>>, Multiple<WithGame<fielding::AtGameStart>> });
190	// 'vsOpponents'?
191	stat_type_stats!(Boxscore { WithNone<hitting::__BoxscoreStatsData>, WithNone<pitching::__BoxscoreStatsData>, (), WithNone<fielding::__BoxscoreStatsData>, });
192}