Skip to main content

mlb_api/requests/team/
mod.rs

1//! Endpoints related to teams; [`roster`], [`history`], [`affiliates`], etc.
2
3pub mod alumni;
4pub mod coaches;
5pub mod leaders;
6pub mod personnel;
7pub mod roster;
8pub mod stats;
9pub mod uniforms;
10pub mod history;
11pub mod affiliates;
12
13use std::fmt::{Debug, Display, Formatter};
14use std::hash::{Hash, Hasher};
15use std::marker::PhantomData;
16use bon::Builder;
17use serde_with::DefaultOnError;
18use crate::division::NamedDivision;
19use crate::league::{LeagueId, NamedLeague};
20use crate::season::SeasonId;
21use crate::venue::{NamedVenue, VenueId};
22use derive_more::{Deref, DerefMut};
23use serde::de::DeserializeOwned;
24use serde::Deserialize;
25use serde_with::serde_as;
26use crate::Copyright;
27use crate::hydrations::Hydrations;
28use crate::request::RequestURL;
29use crate::sport::SportId;
30
31#[serde_as]
32#[derive(Deserialize)]
33#[serde(rename_all = "camelCase", bound = "H: TeamHydrations")]
34struct __TeamRaw<H: TeamHydrations> {
35	#[serde(default)]
36	all_star_status: AllStarStatus,
37	active: bool,
38	season: u32,
39	#[serde(default)]
40	venue: Option<H::Venue>,
41	location_name: Option<String>,
42	#[serde(default, deserialize_with = "crate::try_from_str")]
43	first_year_of_play: Option<u32>,
44	#[serde(default)]
45	#[serde_as(deserialize_as = "DefaultOnError")]
46	league: Option<H::League>,
47	#[serde(default)]
48	#[serde_as(deserialize_as = "DefaultOnError")]
49	division: Option<H::Division>,
50	sport: H::Sport,
51	#[serde(flatten)]
52	parent_organization: Option<NamedOrganization>,
53	#[serde(flatten)]
54	name: __TeamNameRaw,
55	spring_venue: Option<H::SpringVenue>,
56	spring_league: Option<LeagueId>,
57	#[serde(flatten)]
58	inner: NamedTeam,
59	#[serde(flatten)]
60	extras: H,
61}
62
63/// A detailed `struct` representing a baseball team.
64///
65/// ## Examples
66/// ```no_run
67/// Team {
68///     all_star_status: AllStarStatus::Yes,
69///     active: true,
70///     season: 2025,
71///     venue: NamedVenue { name: "Rogers Centre", id: 14 },
72///     location_name: Some("Toronto"),
73///     first_year_of_play: 1977,
74///     league: NamedLeague { name: "American League", id: 103 },
75///     division: Some(NamedDivision { name: "American League East", id: 201 }),
76///     sport: SportId::MLB,
77///     parent_organization: None,
78///     name: TeamName {
79///         team_code: "tor",
80///         file_code: "tor",
81///         abbreviation: "TOR",
82///         team_name: "Blue Jays",
83///         short_name: "Toronto",
84///         franchise_name: "Toronto",
85///         club_name: "Blue Jays",
86///         full_name: "Toronto Blue Jays",
87///     },
88///     spring_venue: Some(VenueId::new(2536)),
89///     spring_league: Some(LeagueId::new(115)),
90///     id: 141,
91/// }
92/// ```
93#[derive(Debug, Deserialize, Deref, DerefMut, Clone)]
94#[serde(from = "__TeamRaw<H>", bound = "H: TeamHydrations")]
95pub struct Team<H: TeamHydrations> {
96	pub all_star_status: AllStarStatus,
97	pub active: bool,
98	pub season: SeasonId,
99	pub venue: H::Venue,
100	pub location_name: Option<String>,
101	pub first_year_of_play: SeasonId,
102	pub league: H::League,
103	pub division: Option<H::Division>,
104	pub sport: H::Sport,
105	pub parent_organization: Option<NamedOrganization>,
106	pub name: TeamName,
107	pub spring_venue: Option<H::SpringVenue>,
108	pub spring_league: Option<LeagueId>,
109
110	#[deref]
111	#[deref_mut]
112	#[serde(flatten)]
113	inner: NamedTeam,
114
115	pub extras: H,
116}
117
118impl<H: TeamHydrations> From<__TeamRaw<H>> for Team<H> {
119	fn from(value: __TeamRaw<H>) -> Self {
120		let __TeamRaw {
121			all_star_status,
122			active,
123			season,
124			venue,
125			location_name,
126			first_year_of_play,
127			league,
128			division,
129			sport,
130			parent_organization,
131			name,
132			spring_venue,
133			spring_league,
134			inner,
135			extras,
136		} = value;
137
138		Self {
139			all_star_status,
140			active,
141			season: SeasonId::new(season),
142			venue: venue.unwrap_or_else(H::unknown_venue),
143			location_name,
144			first_year_of_play: first_year_of_play.unwrap_or(season).into(),
145			league: league.unwrap_or_else(H::unknown_league),
146			division,
147			sport,
148			parent_organization,
149			spring_venue,
150			spring_league,
151			name: name.initialize(inner.id, inner.full_name.clone()),
152			inner,
153			extras,
154		}
155	}
156}
157
158/// A team with a name and [id](TeamId)
159/// 
160/// ## Examples
161/// ```no_run
162/// use mlb_api::team::NamedTeam;
163///
164/// NamedTeam {
165///     full_name: "Toronto Blue Jays".into(),
166///     id: 141.into(),
167/// }
168/// ```
169#[derive(Debug, Deserialize, Clone, Eq)]
170#[serde(rename_all = "camelCase")]
171pub struct NamedTeam {
172	#[serde(alias = "name")]
173	pub full_name: String,
174	#[serde(flatten)]
175	pub id: TeamId,
176}
177
178
179impl Hash for NamedTeam {
180	fn hash<H: Hasher>(&self, state: &mut H) {
181		self.id.hash(state);
182	}
183}
184
185impl NamedTeam {
186	#[must_use]
187	pub(crate) fn unknown_team() -> Self {
188		Self {
189			full_name: "null".to_owned(),
190			id: TeamId::new(0),
191		}
192	}
193
194	#[must_use]
195	pub fn is_unknown(&self) -> bool {
196		*self.id == 0
197	}
198}
199
200id_only_eq_impl!(NamedTeam, id);
201
202impl<H: TeamHydrations> PartialEq for Team<H> {
203	fn eq(&self, other: &Self) -> bool {
204		self.id == other.id
205	}
206}
207
208#[derive(Deserialize)]
209#[serde(rename_all = "camelCase")]
210struct __TeamNameRaw {
211	pub team_code: String,
212	pub abbreviation: String,
213	pub team_name: String,
214	pub short_name: String,
215	#[serde(default)]
216	pub file_code: Option<String>,
217	#[serde(default)]
218	pub franchise_name: Option<String>,
219	#[serde(default)]
220	pub club_name: Option<String>,
221}
222
223/// A detailed description of a team's name.
224///
225/// ## Table of MLB [`TeamName`] data
226/// | `full_name`           | `team_code` | `file_code` | `abbreviation` |`team_name`| `club_name`  |`franchise_name`| `short_name`  |
227/// |-----------------------|-------------|-------------|----------------|-----------|--------------|----------------|---------------|
228/// | Athletics             | `ath`       | `ath`       | `ATH`          | Athletics | Athletics    | Athletics      | Athletics     |
229/// | Pittsburgh Pirates    | `pit`       | `pit`       | `PIT`          | Pirates   | Pirates      | Pittsburgh     | Pittsburgh    |
230/// | San Diego Padres      | `sdn`       | `sd`        | `SD`           | Padres    | Padres       | San Diego      | San Diego     |
231/// | Seattle Mariners      | `sea`       | `sea`       | `SEA`          | Mariners  | Mariners     | Seattle        | Seattle       |
232/// | San Francisco Giants  | `sfn`       | `sf`        | `SF`           | Giants    | Giants       | San Francisco  | San Francisco |
233/// | St. Louis Cardinals   | `sln`       | `stl`       | `STL`          | Cardinals | Cardinals    | St. Louis      | St. Louis     |
234/// | Tampa Bay Rays        | `tba`       | `tb`        | `TB`           | Rays      | Rays         | Tampa Bay      | Tampa Bay     |
235/// | Texas Rangers         | `tex`       | `tex`       | `TEX`          | Rangers   | Rangers      | Texas          | Texas         |
236/// | Toronto Blue Jays     | `tor`       | `tor`       | `TOR`          | Blue Jays | Blue Jays    | Toronto        | Toronto       |
237/// | Minnesota Twins       | `min`       | `min`       | `MIN`          | Twins     | Twins        | Minnesota      | Minnesota     |
238/// | Philadelphia Phillies | `phi`       | `phi`       | `PHI`          | Phillies  | Phillies     | Philadelphia   | Philadelphia  |
239/// | Atlanta Braves        | `atl`       | `atl`       | `ATL`          | Braves    | Braves       | Atlanta        | Atlanta       |
240/// | Chicago White Sox     | `cha`       | `cws`       | `CWS`          | White Sox | White Sox    | Chicago        | Chi White Sox |
241/// | Miami Marlins         | `mia`       | `mia`       | `MIA`          | Marlins   | Marlins      | Miami          | Miami         |
242/// | New York Yankees      | `nya`       | `nyy`       | `NYY`          | Yankees   | Yankees      | New York       | NY Yankees    |
243/// | Milwaukee Brewers     | `mil`       | `mil`       | `MIL`          | Brewers   | Brewers      | Milwaukee      | Milwaukee     |
244/// | Los Angeles Angels    | `ana`       | `ana`       | `LAA`          | Angels    | Angels       | Los Angeles    | LA Angels     |
245/// | Arizona Diamondbacks  | `ari`       | `ari`       | `AZ`           | D-backs   | Diamondbacks | Arizona        | Arizona       |
246/// | Baltimore Orioles     | `bal`       | `bal`       | `BAL`          | Orioles   | Orioles      | Baltimore      | Baltimore     |
247/// | Boston Red Sox        | `bos`       | `bos`       | `BOS`          | Red Sox   | Red Sox      | Boston         | Boston        |
248/// | Chicago Cubs          | `chn`       | `chc`       | `CHC`          | Cubs      | Cubs         | Chicago        | Chi Cubs      |
249/// | Cincinnati Reds       | `cin`       | `cin`       | `CIN`          | Reds      | Reds         | Cincinnati     | Cincinnati    |
250/// | Cleveland Guardians   | `cle`       | `cle`       | `CLE`          | Guardians | Guardians    | Cleveland      | Cleveland     |
251/// | Colorado Rockies      | `col`       | `col`       | `COL`          | Rockies   | Rockies      | Colorado       | Colorado      |
252/// | Detroit Tigers        | `det`       | `det`       | `DET`          | Tigers    | Tigers       | Detroit        | Detroit       |
253/// | Houston Astros        | `hou`       | `hou`       | `HOU`          | Astros    | Astros       | Houston        | Houston       |
254/// | Kansas City Royals    | `kca`       | `kc`        | `KC`           | Royals    | Royals       | Kansas City    | Kansas City   |
255/// | Los Angeles Dodgers   | `lan`       | `la`        | `LAD`          | Dodgers   | Dodgers      | Los Angeles    | LA Dodgers    |
256/// | Washington Nationals  | `was`       | `was`       | `WSH`          | Nationals | Nationals    | Washington     | Washington    |
257/// | New York Mets         | `nyn`       | `nym`       | `NYM`          | Mets      | Mets         | New York       | NY Mets       |
258#[derive(Debug, PartialEq, Deref, DerefMut, Clone)]
259pub struct TeamName {
260	/// Typically 3 characters and all lowercase.
261	pub team_code: String,
262	pub file_code: String,
263	pub abbreviation: String,
264	pub team_name: String,
265	/// Effectively `franchise_name` but has changes for duplicates like 'New York'
266	pub short_name: String,
267	pub franchise_name: String,
268	pub club_name: String,
269	#[deref]
270	#[deref_mut]
271	pub full_name: String,
272}
273
274impl __TeamNameRaw {
275	fn initialize(self, id: TeamId, full_name: String) -> TeamName {
276		let Self {
277			team_code,
278			abbreviation,
279			team_name,
280			short_name,
281			file_code,
282			franchise_name,
283			club_name,
284		} = self;
285
286
287		TeamName {
288			file_code: file_code.unwrap_or_else(|| format!("t{id}")),
289			franchise_name: franchise_name.unwrap_or_else(|| short_name.clone()),
290			club_name: club_name.unwrap_or_else(|| team_name.clone()),
291			team_code,
292			abbreviation,
293			team_name,
294			short_name,
295			full_name,
296		}
297	}
298}
299
300id!(#[doc = "A [`u32`] representing a team's ID."] TeamId { id: u32 });
301
302/// A named organization.
303#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
304#[serde(rename_all = "camelCase")]
305pub struct NamedOrganization {
306	#[serde(rename = "parentOrgName")]
307	pub name: String,
308	#[serde(rename = "parentOrgId")]
309	pub id: OrganizationId,
310}
311
312id!(#[doc = "ID of a parent organization -- still don't know what this is."] OrganizationId { id: u32 });
313
314/// Honestly, no clue. Would love to know.
315#[derive(Debug, Deserialize, PartialEq, Copy, Clone, Default)]
316pub enum AllStarStatus {
317	/// 'tis an All-Star team (?)
318	#[serde(rename = "Y")]
319	Yes,
320	/// 'tis not an All-Star team (?)
321	#[default]
322	#[serde(rename = "N")]
323	No,
324	/// No clue.
325	#[serde(rename = "F")]
326	F,
327	/// No clue.
328	#[serde(rename = "O")]
329	O,
330}
331
332/// A [`Vec`] of [`Team`]s
333#[derive(Debug, Deserialize, PartialEq, Clone)]
334#[serde(rename_all = "camelCase", bound = "H: TeamHydrations")]
335pub struct TeamsResponse<H: TeamHydrations> {
336	pub copyright: Copyright,
337	pub teams: Vec<Team<H>>,
338}
339
340pub trait TeamHydrations: Hydrations<RequestData=()> {
341	/// By default [`SportId`]; with [`sport`] hydration: [`Sport`](crate::sport::Sport)
342	type Sport: Debug + DeserializeOwned + PartialEq + Clone;
343
344	/// By default [`NamedVenue`]; with [`venue`] hydration: [`Venue`](crate::venue::Venue)
345	type Venue: Debug + DeserializeOwned + PartialEq + Clone;
346
347	/// By default [`VenueId`]; with [`spring_venue`] hydration: [`Venue`](crate::venue::Venue)
348	type SpringVenue: Debug + DeserializeOwned + PartialEq + Clone;
349
350	/// By default [`NamedLeague`]; with [`league`] hydration: [`League`](crate::league::League)
351	type League: Debug + DeserializeOwned + PartialEq + Clone;
352
353	/// By default [`NamedDivision`]; with [`division`] hydration: [`Division`](crate::division::Division)
354	type Division: Debug + DeserializeOwned + PartialEq + Clone;
355
356	fn unknown_venue() -> Self::Venue;
357
358	fn unknown_league() -> Self::League;
359}
360
361impl TeamHydrations for () {
362	type Sport = SportId;
363	type Venue = NamedVenue;
364	type SpringVenue = VenueId;
365	type League = NamedLeague;
366	type Division = NamedDivision;
367
368	fn unknown_venue() -> Self::Venue {
369		NamedVenue::unknown_venue()
370	}
371
372	fn unknown_league() -> Self::League {
373		NamedLeague::unknown_league()
374	}
375}
376
377/// Creates hydrations for a team
378///
379/// ## Examples
380/// ```
381/// use mlb_api::team::{Team, TeamsRequest};
382/// use mlb_api::team_hydrations;
383///
384/// team_hydrations! {
385///     pub struct ExampleHydrations {
386///          venue: { field_info },
387///          social,
388///          sport: (),
389///          standings: (),
390///          external_references
391///     }
392/// }
393///
394/// let [team]: [Team<ExampleHydrations>; 1] = TeamsRequest::<ExampleHydrations>::builder().team_id(141).build_and_get().await.unwrap().teams.try_into().unwrap();
395/// ```
396///
397/// ## Team Hydrations
398/// <u>Note: Fields must appear in exactly this order (or be omitted)</u>
399///
400/// | Name                    | Type                             |
401/// |-------------------------|----------------------------------|
402/// | `previous_schedule`     | [`schedule_hydrations!`]         |
403/// | `next_schedule`         | [`schedule_hydrations!`]         |
404/// | `venue`                 | [`venue_hydrations!`]            |
405/// | `spring_venue`          | [`venue_hydrations!`]            |
406/// | `social`                | [`HashMap<String, Vec<String>>`] |
407/// | `league`                | [`League`]                       |
408/// | `sport`                 | [`sports_hydrations!`]           |
409/// | `standings`             | [`standings_hydrations!`]        |
410/// | `division`              | [`Division`]                     |
411/// | `external_references`   | [`ExternalReference`]            |
412///
413/// [`schedule_hydrations!`]: crate::schedule_hydrations
414/// [`venue_hydrations!`]: crate::venue_hydrations
415/// [`sports_hydrations!`]: crate::sports_hydrations
416/// [`standings_hydrations!`]: crate::standings_hydrations
417/// [`HashMap<String, Vec<String>>`]: std::collections::HashMap
418/// [`League`]: crate::league::League
419/// [`Division`]: crate::division::Division
420/// [`ExternalReference`]: crate::types::ExternalReference
421#[macro_export]
422macro_rules! team_hydrations {
423	(@ inline_structs [previous_schedule: { $($inline_tt:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
424		::pastey::paste! {
425			$crate::schedule_hydrations! {
426				$vis struct [<$name InlinePreviousSchedule>] {
427					$($inline_tt)*
428				}
429			}
430
431			$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
432				$vis struct $name {
433					$($field_tt)*
434					venue: [<$name InlinePreviousSchedule>],
435				}
436			}
437		}
438	};
439	(@ inline_structs [next_schedule: { $($inline_tt:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
440		::pastey::paste! {
441			$crate::schedule_hydrations! {
442				$vis struct [<$name InlineNextSchedule>] {
443					$($inline_tt)*
444				}
445			}
446
447			$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
448				$vis struct $name {
449					$($field_tt)*
450					venue: [<$name InlineNextSchedule>],
451				}
452			}
453		}
454	};
455	(@ inline_structs [venue: { $($inline_tt:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
456		::pastey::paste! {
457			$crate::venue_hydrations! {
458				$vis struct [<$name InlineVenue>] {
459					$($inline_tt)*
460				}
461			}
462
463			$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
464				$vis struct $name {
465					$($field_tt)*
466					venue: [<$name InlineVenue>],
467				}
468			}
469		}
470	};
471	(@ inline_structs [spring_venue: { $($inline_tt:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
472		::pastey::paste! {
473			$crate::venue_hydrations! {
474				$vis struct [<$name InlineSpringVenue>] {
475					$($inline_tt)*
476				}
477			}
478
479			$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
480				$vis struct $name {
481					$($field_tt)*
482					spring_venue: [<$name InlineSpringVenue>],
483				}
484			}
485		}
486	};
487	(@ inline_structs [sport: { $($inline_tt:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
488		::pastey::paste! {
489			$crate::sports_hydrations! {
490				$vis struct [<$name InlineSport>] {
491					$($inline_tt)*
492				}
493			}
494
495			$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
496				$vis struct $name {
497					$($field_tt)*
498					sport: [<$name InlineSport>],
499				}
500			}
501		}
502	};
503	(@ inline_structs [standings: { $($inline_tt:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
504		::pastey::paste! {
505			$crate::standings_hydrations! {
506				$vis struct [<$name InlineStandings>] {
507					$($inline_tt)*
508				}
509			}
510
511			$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
512				$vis struct $name {
513					$($field_tt)*
514					standings: [<$name InlineStandings>],
515				}
516			}
517		}
518	};
519	(@ inline_structs [$_01:ident : { $($_02:tt)* } $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
520		::core::compile_error!("Found unknown inline struct");
521	};
522	(@ inline_structs [$field:ident $(: $value:ty)? $(, $($tt:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
523		$crate::team_hydrations! { @ inline_structs [$($($tt)*)?]
524			$vis struct $name {
525				$($field_tt)*
526				$field $(: $value)?,
527			}
528		}
529	};
530	(@ inline_structs [] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
531		$crate::team_hydrations! { @ actual
532			$vis struct $name {
533				$($field_tt)*
534			}
535		}
536	};
537
538	(@ sport) => { $crate::sport::SportId };
539	(@ sport $hydrations:ty) => { $crate::sport::Sport<$hydrations> };
540
541	(@ venue) => { $crate::venue::NamedVenue };
542	(@ venue $hydrations:ty) => { $crate::venue::Venue<$hydrations> };
543	(@ unknown_venue) => { $crate::venue::NamedVenue::unknown_venue() };
544	(@ unknown_venue $hydrations:ty) => { unimplemented!() }; // todo: hrmm... forward error?
545
546	(@ spring_venue) => { $crate::venue::VenueId };
547	(@ spring_venue $hydrations:ty) => { $crate::venue::Venue<$hydrations> };
548
549	(@ league) => { $crate::league::NamedLeague };
550	(@ league ,) => { $crate::league::League };
551	(@ unknown_league) => { $crate::league::NamedLeague::unknown_league() };
552	(@ unknown_league ,) => { unimplemented!() }; // todo: hrmm... forward error?
553
554	(@ division) => { $crate::division::NamedDivision };
555	(@ division ,) => { $crate::division::Division };
556
557	(@ actual $vis:vis struct $name:ident {
558		$(previous_schedule: $previous_schedule:ty ,)?
559		$(next_schedule: $next_schedule:ty ,)?
560		$(venue: $venue:ty ,)?
561		$(spring_venue: $spring_venue:ty ,)?
562		$(social $social_comma:tt)?
563		$(league $league_comma:tt)?
564		$(sport: $sport:ty ,)?
565		$(standings: $standings:ty ,)?
566		$(division $division_comma:tt)?
567		$(external_references $external_references_comma:tt)?
568	}) => {
569		#[derive(::core::fmt::Debug, ::serde::Deserialize, ::core::cmp::PartialEq, ::core::clone::Clone)]
570		$vis struct $name {
571			$(#[serde(rename = "previousGameSchedule")] previous_schedule: $crate::schedule::ScheduleResponse<$previous_schedule>,)?
572			$(#[serde(rename = "nextGameSchedule")] next_schedule: $crate::schedule::ScheduleResponse<$next_schedule>,)?
573			$(#[serde(rename = "xrefIds")] external_references: ::std::vec::Vec<$crate::types::ExternalReference> $external_references_comma)?
574			$(#[serde(default, rename = "social")] socials: ::std::collections::HashMap<::std::string::String, ::std::vec::Vec<::std::string::String> $social_comma>)?
575		}
576
577		impl $crate::team::TeamHydrations for $name {
578			type Sport = $crate::team_hydrations!(@ sport $($sport)?);
579
580			type Venue = $crate::team_hydrations!(@ venue $($venue)?);
581
582			type SpringVenue = $crate::team_hydrations!(@ spring_venue $($spring_venue)?);
583
584			type League = $crate::team_hydrations!(@ league $($league_comma)?);
585
586			type Division = $crate::team_hydrations!(@ league $($division_comma)?);
587
588			fn unknown_venue() -> Self::Venue {
589				$crate::team_hydrations!(@ unknown_venue $($venue)?)
590			}
591
592			fn unknown_league() -> Self::League {
593				$crate::team_hydrations!(@ unknown_league $($league_comma)?)
594			}
595		}
596
597		impl $crate::hydrations::Hydrations for $name {
598			type RequestData = ();
599
600			fn hydration_text(&(): &Self::RequestData) -> ::std::borrow::Cow<'static, str> {
601				let text = ::std::borrow::Cow::Borrowed(::core::concat!(
602					$("social," $social_comma)?
603					$("xrefId," $external_references_comma)?
604					$("league," $league_comma)?
605					$("division," $division_comma)?
606				));
607
608				$(let text = ::std::borrow::Cow::<'static, str>::Owned(::std::format!("{text}previousSchedule({}),", <$previous_schedule as $crate::hydrations::Hydrations>::hydration_text(&())));)?
609				$(let text = ::std::borrow::Cow::<'static, str>::Owned(::std::format!("{text}nextSchedule({}),", <$next_schedule as $crate::hydrations::Hydrations>::hydration_text(&())));)?
610				$(let text = ::std::borrow::Cow::<'static, str>::Owned(::std::format!("{text}venue({}),", <$venue as $crate::hydrations::Hydrations>::hydration_text(&())));)?
611				$(let text = ::std::borrow::Cow::<'static, str>::Owned(::std::format!("{text}springVenue({}),", <$spring_venue as $crate::hydrations::Hydrations>::hydration_text(&())));)?
612				$(let text = ::std::borrow::Cow::<'static, str>::Owned(::std::format!("{text}sport({}),", <$sport as $crate::hydrations::Hydrations>::hydration_text(&())));)?
613				$(let text = ::std::borrow::Cow::<'static, str>::Owned(::std::format!("{text}standings({}),", <$standings as $crate::hydrations::Hydrations>::hydration_text(&())));)?
614
615				text
616			}
617		}
618	};
619    ($vis:vis struct $name:ident {
620		$($tt:tt)*
621	}) => {
622		$crate::team_hydrations! { @ inline_structs [$($tt)*] $vis struct $name {} }
623	};
624}
625
626/// Returns a [`TeamsResponse`].
627#[derive(Builder)]
628#[builder(derive(Into))]
629pub struct TeamsRequest<H: TeamHydrations> {
630	#[builder(into)]
631	sport_id: Option<SportId>,
632	#[builder(into)]
633	season: Option<SeasonId>,
634	#[builder(into)]
635	team_id: Option<TeamId>,
636	#[builder(skip)]
637	_marker: PhantomData<H>,
638}
639
640impl TeamsRequest<()> {
641	pub fn for_sport(sport_id: impl Into<SportId>) -> TeamsRequestBuilder<(), teams_request_builder::SetSportId> {
642		Self::builder().sport_id(sport_id)
643	}
644
645	pub fn mlb_teams() -> TeamsRequestBuilder<(), teams_request_builder::SetSportId> {
646		Self::for_sport(SportId::MLB)
647	}
648
649	pub fn all_sports() -> TeamsRequestBuilder<()> {
650		Self::builder()
651	}
652}
653
654impl<H: TeamHydrations, S: teams_request_builder::State + teams_request_builder::IsComplete> crate::request::RequestURLBuilderExt for TeamsRequestBuilder<H, S> {
655	type Built = TeamsRequest<H>;
656}
657
658impl<H: TeamHydrations> Display for TeamsRequest<H> {
659	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
660		let hydrations = Some(H::hydration_text(&())).filter(|s| !s.is_empty());
661		write!(f, "http://statsapi.mlb.com/api/v1/teams{}", gen_params! { "sportId"?: self.sport_id, "season"?: self.season, "teamId"?: self.team_id, "hydrate"?: hydrations })
662	}
663}
664
665impl<H: TeamHydrations> RequestURL for TeamsRequest<H> {
666	type Response = TeamsResponse<H>;
667}
668
669#[cfg(test)]
670mod tests {
671	use crate::request::RequestURLBuilderExt;
672	use crate::TEST_YEAR;
673	use super::*;
674
675	#[tokio::test]
676	#[cfg_attr(not(feature = "_heavy_tests"), ignore)]
677	async fn parse_all_teams_all_seasons() {
678		for season in 1871..=TEST_YEAR {
679			let _response = TeamsRequest::all_sports().season(season).build_and_get().await.unwrap();
680		}
681	}
682
683	#[tokio::test]
684	async fn parse_all_mlb_teams_this_season() {
685		let _ = TeamsRequest::mlb_teams().build_and_get().await.unwrap();
686	}
687
688	#[tokio::test]
689	async fn parse_all_mlb_teams_this_season_hydrated() {
690		team_hydrations! {
691			pub struct TestHydrations {
692				previous_schedule: (),
693				next_schedule: (),
694				venue: (),
695				spring_venue: (),
696				social,
697				league,
698				sport: (),
699				standings: (),
700				division,
701				external_references,
702			}
703		}
704
705		let _ = TeamsRequest::<TestHydrations>::builder().sport_id(SportId::MLB).season(TEST_YEAR).build_and_get().await.unwrap();
706	}
707}