mlb_api/lib.rs
1//! <div align="center">
2//! <!-- Version -->
3//! <a href="https://crates.io/crates/mlb-api">
4//! <img src="https://img.shields.io/crates/v/mlb-api.svg?style=flat-square"
5//! alt="Crates.io version" />
6//! </a>
7//! <!-- Docs -->
8//! <a href="https://docs.rs/mlb-api">
9//! <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=flat-square"
10//! alt="docs.rs docs" />
11//! </a>
12//! <!-- Downloads -->
13//! <a href="https://crates.io/crates/mlb-api">
14//! <img src="https://img.shields.io/crates/d/mlb-api.svg?style=flat-square"
15//! alt="Crates.io downloads" />
16//! </a>
17//! </div>
18//!
19//! # The Rust MLB API Wrapper
20//!
21//! 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>.
22//!
23//! ## Usage
24//! Endpoints are most commonly used using their module's builder functions.
25//! ```
26//! use mlb_api::sport::SportId;
27//! use mlb_api::request::RequestUrlBuilderExt;
28//! use mlb_api::schedule::{self, ScheduleResponse, ScheduleDate};
29//!
30//! let response: ScheduleResponse = schedule::request()
31//! .sport_id(SportId::MLB)
32//! .build_and_get()
33//! .await?;
34//!
35//! let [date]: [ScheduleDate; 1] = response.dates.try_into()?;
36//! ```
37//!
38//! Play Streams are the recommended way to process live games
39//! ```no_run
40//! use mlb_api::game::PlayStream;
41//!
42//! let game: ScheduleGame = ...;
43//!
44//! PlayStream::new(game.game_id).run(|event, _meta, _data| { ... }).await?;
45//! ```
46//!
47//! Use [`single_stat!`](crate::single_stat) for simple stats requests and make your own hydrations for more complicated requests
48//! ```no_run
49//! use mlb_api::single_stat;
50//! use mlb_api::person::{self, PeopleResonse};
51//!
52//! let season_hitting = single_stat!( Season + Hitting for 660_271 ).await?;
53//! let sabermetrics_pitching = single_stat!( Sabermetrics + Pitching for 660_271; with |builder| builder.season(2024) ).await?;
54//!
55//! person_hydrations! {
56//! struct PersonDisplayHydrations {
57//! nicknames,
58//! stats: { [Season, Sabermetrics] = [Hitting, Pitching] },
59//! }
60//! }
61//!
62//! let response: PeopleResponse = person::request_with_hydrations::<PersonDisplayHydrations>(660_271).await?;
63//! ```
64//!
65//! ## Endpoints
66//! This API contains wrappers / bindings for all known public MLB API endpoints (unless incomplete), the table of which can be seen below.
67//! Additional information can be found at <https://github.com/toddrob99/MLB-StatsAPI/wiki/Endpoints> (thanks Todd Roberts)
68//!
69//! Stars hightlight the most popular and used endpoints
70//!
71//! | Endpoint | Description |
72//! |----------------------------|--------------------------------------------------|
73//! | [`attendance`] | Team attendance data by season |
74//! | [`awards`] | List of all awards, Cy Young, MVP, etc. |
75//! | [`conference`] | Conferences, like divisions but not |
76//! | [`division`] | Names, has a wildcard or not, playoff teams |
77//! | [`draft`] | Draft rounds, players, etc. |
78//! | [`live_feed`] ⭐ | [`boxscore`], [`linescore`], [`plays`], and misc |
79//! | [`diff_patch`] | JSON diff patch for live feed |
80//! | [`timestamps`] | List of timestamps for game plays & play events |
81//! | [`changes`] | Games that underwent scheduling changes (?) |
82//! | [`context_metrics`] | Various metrics for game plays & play events |
83//! | [`win_probability`] | Win Probability calculations for games |
84//! | [`boxscore`] | Boxscore summary for game, includes stats |
85//! | [`content`] | Editorial content regarding games |
86//! | [`linescore`] | Linescore for games |
87//! | [`plays`] | Play by Play Data on a game |
88//! | [`uniforms`] | Game Uniforms |
89//! | [`pace`] | Average game durations and league stat totals |
90//! | [`home_run_derby`] | Home Run Derby stats |
91//! | [`league`] | League data, AL, NL, divisions contained, etc. |
92//! | [`all_star`] | ASG data |
93//! | [`person`] ⭐ | A person, lots of data here |
94//! | [`free_agents`] | Free agents in any given year |
95//! | [`person_stats`] | Player stats for a single game |
96//! | [`jobs`] | List of all people with a job, ex: scorer, ump |
97//! | [`jobs::umpire`] | List of all umpires |
98//! | [`jobs::datacasters`] | List of all datacasters |
99//! | [`jobs::official_scorers`] | List of all official scorers |
100//! | [`schedule`] ⭐ | All games played within a certain date range |
101//! | [`schedule::tied`] | All games that ended tied (?) |
102//! | [`schedule::postseason`] | Postseason schedule |
103//! | [`schedule::postseason::series`] | Postseason series schedule |
104//! | [`season`] | Date ranges for season states: reg, post, spring |
105//! | [`sport`] | List of sports, MLB, AAA, AA, A+, etc. |
106//! | [`players`] ⭐ | List of all players in a sport and season |
107//! | [`standings`] | Standings information for teams |
108//! | [`stats`] | Stats data |
109//! | [`stats::leaders`] | League leaders in specific stats |
110//! | [`team`] ⭐ | Team data |
111//! | [`team::history`] | History of a team, such as Brooklyn & LA Dodgers |
112//! | [`team::stats`] | Stats for a team |
113//! | [`team::affiliates`] | Minor league affiliate teams |
114//! | [`team::alumni`] | Alumni for a team |
115//! | [`team::coaches`] | List of coaches on a team |
116//! | [`team::personnel`] | Personnel on a team |
117//! | [`team::leaders`] | Stat leaders per team |
118//! | [`team::roster`] | Roster players on a team |
119//! | [`team::uniforms`] | Uniforms a team wears |
120//! | [`transactions`] | Trades, IL moves, etc. |
121//! | [`venue`] | Yankee Stadium, Dodger Stadium, Fenway Park, etc.|
122//! | [`meta`] | Metadata endpoints, typically cached or enums |
123//!
124//! ## Usage & Appendix
125//! 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.
126//! 3. Use [`PlayStream`](crate::game::PlayStream) for obtaining live updates on games.
127//! 4. Use [`single_stat!`](crate::single_stat) for simple stat requests rather than making [`person_hydrations!`] and [`PersonRequest`](crate::person::PersonRequest) yourself.
128//! 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.
129//! 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.
130//! 7. Supply [`SeasonId`](crate::season::SeasonId)s to requests to not have them break when the year changes.
131//!
132//! [`attendance`]: crate::requests::attendance
133//! [`awards`]: crate::requests::awards
134//! [`conference`]: crate::requests::conference
135//! [`division`]: crate::requests::division
136//! [`draft`]: crate::requests::draft
137//! [`live_feed`]: crate::requests::game::live_feed
138//! [`diff_patch`]: crate::requests::game::diff
139//! [`timestamps`]: crate::requests::game::timestamps
140//! [`changes`]: crate::requests::game::changes
141//! [`context_metrics`]: crate::requests::game::context_metrics
142//! [`win_probability`]: crate::requests::game::win_probability
143//! [`boxscore`]: crate::requests::game::boxscore
144//! [`content`]: crate::requests::game::content
145//! [`linescore`]: crate::requests::game::linescore
146//! [`plays`]: crate::requests::game::plays
147//! [`uniforms`]: crate::requests::game::uniforms
148//! [`pace`]: crate::requests::game::pace
149//! [`home_run_derby`]: crate::requests::home_run_derby
150//! [`league`]: crate::requests::league
151//! [`all_star`]: crate::requests::all_star
152//! [`person`]: crate::requests::person
153//! [`free_agents`]: crate::requests::person::free_agents
154//! [`person_stats`]: crate::requests::person::stats
155//! [`jobs`]: crate::requests::jobs
156//! [`jobs::umpire`]: crate::requests::jobs::umpire
157//! [`jobs::datacasters`]: crate::requests::jobs::datacasters
158//! [`jobs::official_scorers`]: crate::requests::jobs::official_scorers
159//! [`schedule`]: crate::requests::schedule
160//! [`schedule::tied`]: crate::requests::schedule::tied
161//! [`schedule::postseason`]: crate::requests::schedule::postseason
162//! [`schedule::postseason::series`]: crate::requests::schedule::postseason::series
163//! [`season`]: crate::requests::season
164//! [`sport`]: crate::requests::sport
165//! [`players`]: crate::requests::person::players
166//! [`standings`]: crate::requests::standings
167//! [`stats`]: crate::requests::stats
168//! [`stats::leaders`]: crate::requests::stats::leaders
169//! [`team`]: crate::requests::team
170//! [`team::history`]: crate::requests::team::history
171//! [`team::stats`]: crate::requests::team::stats
172//! [`team::affiliates`]: crate::requests::team::affiliates
173//! [`team::alumni`]: crate::requests::team::alumni
174//! [`team::coaches`]: crate::requests::team::coaches
175//! [`team::personnel`]: crate::requests::team::personnel
176//! [`team::leaders`]: crate::requests::team::leaders
177//! [`team::roster`]: crate::requests::team::roster
178//! [`team::uniforms`]: crate::requests::team::uniforms
179//! [`transactions`]: crate::requests::transactions
180//! [`venue`]: crate::requests::venue
181//! [`meta`]: crate::requests::meta
182
183#![warn(clippy::pedantic, clippy::nursery, clippy::complexity, clippy::cargo, clippy::perf, clippy::style)]
184#![warn(clippy::allow_attributes_without_reason)]
185#![allow(clippy::multiple_crate_versions, clippy::cast_lossless, reason = "deemed unnecessary")]
186
187macro_rules! id {
188 ($(#[$meta:meta])* $name:ident { $id_field:ident: String }) => {
189 $(#[$meta])*
190 #[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)]
191 #[repr(transparent)]
192 pub struct $name(String);
193
194 impl<'de> ::serde::Deserialize<'de> for $name {
195 #[allow(non_snake_case, reason = "is camel case because serde deserializes that from the API")]
196 fn deserialize<D: ::serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
197 #[derive(::serde::Deserialize)]
198 #[serde(untagged)]
199 enum Repr {
200 Wrapped { $id_field: String },
201 Inline(String),
202 }
203
204 let (Repr::Wrapped { $id_field } | Repr::Inline($id_field)) = Repr::deserialize(deserializer)?;
205 Ok($name($id_field))
206 }
207 }
208
209 impl $name {
210 #[must_use]
211 pub fn new(id: impl Into<String>) -> Self {
212 Self(id.into())
213 }
214 }
215 };
216 ($(#[$meta:meta])* $name:ident { $id_field:ident: u32 }) => {
217 $(#[$meta])*
218 #[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)]
219 #[repr(transparent)]
220 pub struct $name(u32);
221
222 impl<'de> ::serde::Deserialize<'de> for $name {
223 #[allow(non_snake_case, reason = "is camel case because serde deserializes that from the API")]
224 fn deserialize<D: ::serde::de::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
225 #[derive(::serde::Deserialize)]
226 #[serde(untagged)]
227 enum Repr {
228 Wrapped { $id_field: u32 },
229 Inline(u32),
230 }
231
232 let (Repr::Wrapped { $id_field } | Repr::Inline($id_field)) = Repr::deserialize(deserializer)?;
233 Ok($name($id_field))
234 }
235 }
236
237 impl $name {
238 #[must_use]
239 pub const fn new(id: u32) -> Self {
240 Self(id)
241 }
242 }
243 };
244}
245
246// todo: add `request` fn to all requests rather than using the request type directly
247// todo: add deny_unknown_fields to everything
248
249pub mod hydrations;
250pub mod request;
251mod types;
252pub mod cache;
253mod requests;
254
255pub use requests::*;
256pub use types::*;
257
258#[cfg(test)]
259pub(crate) const TEST_YEAR: u32 = 2025;
260
261#[cfg(feature = "reqwest")]
262pub(crate) type RwLock<T> = tokio::sync::RwLock<T>;
263
264#[cfg(feature = "ureq")]
265pub(crate) type RwLock<T> = parking_lot::RwLock<T>;
266
267#[cfg(feature = "reqwest")]
268pub(crate) const fn rwlock_const_new<T>(t: T) -> RwLock<T> {
269 RwLock::const_new(t)
270}
271
272#[cfg(feature = "ureq")]
273pub(crate) const fn rwlock_const_new<T>(t: T) -> RwLock<T> {
274 parking_lot::const_rwlock(t)
275}