mlb_api/lib.rs
1//! # The Rust MLB Stats API Wrapper
2//!
3//! This project and its author are not affiliated with MLB or any MLB team. This crate wraps the existing MLB Stats API, which is subject to the notice posted at <http://gdx.mlb.com/components/copyright.txt>.
4//!
5//! ## Endpoints
6//! This API contains wrappers / bindings for all known public MLB API endpoints (unless incomplete), the table of which can be seen below.
7//! Additional information can be found at <https://github.com/toddrob99/MLB-StatsAPI/wiki/Endpoints> (thanks Todd Roberts)
8//!
9//! Stars hightlight the most popular and used endpoints
10//!
11//! | Endpoint | Description |
12//! |----------------------------|--------------------------------------------------|
13//! | [`attendance`] | Team attendance data by season |
14//! | [`awards`] | List of all awards, Cy Young, MVP, etc. |
15//! | [`conference`] | Conferences, like divisions but not |
16//! | [`division`] | Names, has a wildcard or not, playoff teams |
17//! | [`draft`] | Draft rounds, players, etc. |
18//! | [`live_feed`] ⭐ | [`boxscore`], [`linescore`], [`plays`], and misc |
19//! | [`diff_patch`] | JSON diff patch for live feed |
20//! | [`timestamps`] | List of timestamps for game plays & play events |
21//! | [`changes`] | Games that underwent scheduling changes (?) |
22//! | [`context_metrics`] | Various metrics for game plays & play events |
23//! | [`win_probability`] | Win Probability calculations for games |
24//! | [`boxscore`] | Boxscore summary for game, includes stats |
25//! | [`content`] | Editorial content regarding games |
26//! | [`linescore`] | Linescore for games |
27//! | [`plays`] | Play by Play Data on a game |
28//! | [`uniforms`] | Game Uniforms |
29//! | [`pace`] | Average game durations and league stat totals |
30//! | [`home_run_derby`] | Home Run Derby stats |
31//! | [`league`] | League data, AL, NL, divisions contained, etc. |
32//! | [`all_star`] | ASG data |
33//! | [`person`] ⭐ | A person, lots of data here |
34//! | [`free_agents`] | Free agents in any given year |
35//! | [`person_stats`] | Player stats for a single game |
36//! | [`jobs`] | List of all people with a job, ex: scorer, ump |
37//! | [`jobs::umpire`] | List of all umpires |
38//! | [`jobs::datacasters`] | List of all datacasters |
39//! | [`jobs::official_scorers`] | List of all official scorers |
40//! | [`schedule`] ⭐ | All games played within a certain date range |
41//! | [`schedule::tied`] | All games that ended tied (?) |
42//! | [`schedule::postseason`] | Postseason schedule |
43//! | [`schedule::postseason::series`] | Postseason series schedule |
44//! | [`season`] | Date ranges for season states: reg, post, spring |
45//! | [`sport`] | List of sports, MLB, AAA, AA, A+, etc. |
46//! | [`players`] ⭐ | List of all players in a sport and season |
47//! | [`standings`] | Standings information for teams |
48//! | [`stats`] | Stats data |
49//! | [`stats::leaders`] | League leaders in specific stats |
50//! | [`team`] ⭐ | Team data |
51//! | [`team::history`] | History of a team, such as Brooklyn & LA Dodgers |
52//! | [`team::stats`] | Stats for a team |
53//! | [`team::affiliates`] | Minor league affiliate teams |
54//! | [`team::alumni`] | Alumni for a team |
55//! | [`team::coaches`] | List of coaches on a team |
56//! | [`team::personnel`] | Personnel on a team |
57//! | [`team::leaders`] | Stat leaders per team |
58//! | [`team::roster`] | Roster players on a team |
59//! | [`team::uniforms`] | Uniforms a team wears |
60//! | [`transactions`] | Trades, IL moves, etc. |
61//! | [`venue`] | Yankee Stadium, Dodger Stadium, Fenway Park, etc.|
62//! | [`meta`] | Metadata endpoints, typically cached or enums |
63//!
64//! ## Usage & Appendix
65//! 1. This API defaults to using `reqwest` and `tokio` for non-blocking IO, there is a `ureq` feature to switch to `ureq` and `parking_lot` for blocking IO.
66//! 2. Use `::request` functions on modules rather than the underlying `*Request` type, ideally those types never need to be `use`d.
67//! 3. Use [`PlayStream`](crate::game::PlayStream) for obtaining live updates on games.
68//! 4. Use [`single_stat!`](crate::single_stat) for simple stat requests rather than making [`person_hydrations!`] and [`PersonRequest`](crate::person::PersonRequest) yourself.
69//! 5. Use [`as_complete_or_request`](crate::cache::RequestableEntrypoint::as_complete_or_request) and the numerous `crate::*_hydrations!` items to obtain additional information in requests, try to minimize request quantity.
70//! 6. The [`precache`](crate::cache::precache) function allows caching commonly requested values before usage to make [`as_complete_or_request`](crate::cache::RequestableEntrypoint::as_complete_or_request) faster to use.
71//! 7. Supply [`SeasonId`](crate::season::SeasonId)s to requests to not have them break when the year changes.
72//!
73//! [`attendance`]: crate::requests::attendance
74//! [`awards`]: crate::requests::awards
75//! [`conference`]: crate::requests::conference
76//! [`division`]: crate::requests::division
77//! [`draft`]: crate::requests::draft
78//! [`live_feed`]: crate::requests::game::live_feed
79//! [`diff_patch`]: crate::requests::game::diff
80//! [`timestamps`]: crate::requests::game::timestamps
81//! [`changes`]: crate::requests::game::changes
82//! [`context_metrics`]: crate::requests::game::context_metrics
83//! [`win_probability`]: crate::requests::game::win_probability
84//! [`boxscore`]: crate::requests::game::boxscore
85//! [`content`]: crate::requests::game::content
86//! [`linescore`]: crate::requests::game::linescore
87//! [`plays`]: crate::requests::game::plays
88//! [`uniforms`]: crate::requests::game::uniforms
89//! [`pace`]: crate::requests::game::pace
90//! [`home_run_derby`]: crate::requests::home_run_derby
91//! [`league`]: crate::requests::league
92//! [`all_star`]: crate::requests::all_star
93//! [`person`]: crate::requests::person
94//! [`free_agents`]: crate::requests::person::free_agents
95//! [`person_stats`]: crate::requests::person::stats
96//! [`jobs`]: crate::requests::jobs
97//! [`jobs::umpire`]: crate::requests::jobs::umpire
98//! [`jobs::datacasters`]: crate::requests::jobs::datacasters
99//! [`jobs::official_scorers`]: crate::requests::jobs::official_scorers
100//! [`schedule`]: crate::requests::schedule
101//! [`schedule::tied`]: crate::requests::schedule::tied
102//! [`schedule::postseason`]: crate::requests::schedule::postseason
103//! [`schedule::postseason::series`]: crate::requests::schedule::postseason::series
104//! [`season`]: crate::requests::season
105//! [`sport`]: crate::requests::sport
106//! [`players`]: crate::requests::person::players
107//! [`standings`]: crate::requests::standings
108//! [`stats`]: crate::requests::stats
109//! [`stats::leaders`]: crate::requests::stats::leaders
110//! [`team`]: crate::requests::team
111//! [`team::history`]: crate::requests::team::history
112//! [`team::stats`]: crate::requests::team::stats
113//! [`team::affiliates`]: crate::requests::team::affiliates
114//! [`team::alumni`]: crate::requests::team::alumni
115//! [`team::coaches`]: crate::requests::team::coaches
116//! [`team::personnel`]: crate::requests::team::personnel
117//! [`team::leaders`]: crate::requests::team::leaders
118//! [`team::roster`]: crate::requests::team::roster
119//! [`team::uniforms`]: crate::requests::team::uniforms
120//! [`transactions`]: crate::requests::transactions
121//! [`venue`]: crate::requests::venue
122//! [`meta`]: crate::requests::meta
123
124#![warn(clippy::pedantic, clippy::nursery, clippy::complexity, clippy::cargo, clippy::perf, clippy::style)]
125#![warn(clippy::allow_attributes_without_reason)]
126#![allow(clippy::multiple_crate_versions, clippy::cast_lossless, reason = "deemed unnecessary")]
127
128macro_rules! id {
129 ($(#[$meta:meta])* $name:ident { $id_field:ident: String }) => {
130 $(#[$meta])*
131 #[derive(::core::fmt::Debug, ::derive_more::Deref, ::derive_more::Display, ::core::cmp::PartialEq, ::core::cmp::Eq, ::core::clone::Clone, ::core::hash::Hash, ::derive_more::From)]
132 #[repr(transparent)]
133 pub struct $name(String);
134
135 impl<'de> ::serde::Deserialize<'de> for $name {
136 #[allow(non_snake_case, reason = "is camel case because serde deserializes that from the API")]
137 fn deserialize<D: ::serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
138 #[derive(::serde::Deserialize)]
139 #[serde(untagged)]
140 enum Repr {
141 Wrapped { $id_field: String },
142 Inline(String),
143 }
144
145 let (Repr::Wrapped { $id_field } | Repr::Inline($id_field)) = Repr::deserialize(deserializer)?;
146 Ok($name($id_field))
147 }
148 }
149
150 impl $name {
151 #[must_use]
152 pub fn new(id: impl Into<String>) -> Self {
153 Self(id.into())
154 }
155 }
156 };
157 ($(#[$meta:meta])* $name:ident { $id_field:ident: u32 }) => {
158 $(#[$meta])*
159 #[derive(::core::fmt::Debug, ::derive_more::Deref, ::derive_more::Display, ::core::cmp::PartialEq, ::core::cmp::Eq, ::core::marker::Copy, ::core::clone::Clone, ::core::hash::Hash, ::derive_more::From)]
160 #[repr(transparent)]
161 pub struct $name(u32);
162
163 impl<'de> ::serde::Deserialize<'de> for $name {
164 #[allow(non_snake_case, reason = "is camel case because serde deserializes that from the API")]
165 fn deserialize<D: ::serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
166 #[derive(::serde::Deserialize)]
167 #[serde(untagged)]
168 enum Repr {
169 Wrapped { $id_field: u32 },
170 Inline(u32),
171 }
172
173 let (Repr::Wrapped { $id_field } | Repr::Inline($id_field)) = Repr::deserialize(deserializer)?;
174 Ok($name($id_field))
175 }
176 }
177
178 impl $name {
179 #[must_use]
180 pub const fn new(id: u32) -> Self {
181 Self(id)
182 }
183 }
184 };
185}
186
187// todo: add `request` fn to all requests rather than using the request type directly
188// todo: add deny_unknown_fields to everything
189
190pub mod hydrations;
191pub mod request;
192mod types;
193pub mod cache;
194mod requests;
195
196pub use requests::*;
197pub use types::*;
198
199#[cfg(test)]
200pub(crate) const TEST_YEAR: u32 = 2025;
201
202#[cfg(feature = "reqwest")]
203pub(crate) type RwLock<T> = tokio::sync::RwLock<T>;
204
205#[cfg(feature = "ureq")]
206pub(crate) type RwLock<T> = parking_lot::RwLock<T>;
207
208#[cfg(feature = "reqwest")]
209pub(crate) const fn rwlock_const_new<T>(t: T) -> RwLock<T> {
210 RwLock::const_new(t)
211}
212
213#[cfg(feature = "ureq")]
214pub(crate) const fn rwlock_const_new<T>(t: T) -> RwLock<T> {
215 parking_lot::const_rwlock(t)
216}