Skip to main content

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