Skip to main content

mlb_api/requests/person/
mod.rs

1//! A description of a person in baseball.
2//!
3//! Person works through multi-level definition.
4//!
5//! 1. [`PersonId`], which deserializes a:
6//! ```json
7//! "person": {
8//!     "id": 660271,
9//!     "link": "/api/v1/people/660271"
10//! }
11//! ```
12//! 2. [`NamedPerson`], which deserializes a:
13//! ```json
14//! "person": {
15//!     "id": 660271,
16//!     "link": "/api/v1/people/660271",
17//!     "fullName": "Shohei Ohtani"
18//! }
19//! ```
20//! 3. [`Person`], which deserializes a lot of extra fields, see <http://statsapi.mlb.com/api/v1/people/660271>.
21//! Technically, [`Person`] is actually an enum which separates fields supplied for [`Ballplayer`]s (handedness, draft year, etc.), and fields available to people like coaches and umpires (such as last name, age, etc.) [`RegularPerson`].
22//!
23//! This module also contains [`person_hydrations`](crate::person_hydrations), which are used to get additional data about people when making requests.
24
25#![allow(clippy::trait_duplication_in_bounds, reason = "serde duplicates it")]
26
27pub mod free_agents;
28pub mod stats;
29pub mod players;
30
31use crate::cache::Requestable;
32use crate::draft::School;
33use crate::hydrations::Hydrations;
34use crate::{Copyright, Gender, Handedness, HeightMeasurement};
35use crate::request::RequestURL;
36use bon::Builder;
37use chrono::{Local, NaiveDate};
38use derive_more::{Deref, DerefMut, Display, From};
39use serde::de::Error;
40use serde::{Deserialize, Deserializer};
41use serde_with::{serde_as, DefaultOnError};
42use std::fmt::{Debug, Display, Formatter};
43use std::hash::{Hash, Hasher};
44use std::ops::{Deref, DerefMut};
45use crate::meta::NamedPosition;
46use crate::team::NamedTeam;
47
48#[cfg(feature = "cache")]
49use crate::{rwlock_const_new, RwLock, cache::CacheTable};
50
51/// Response containing a list of people
52#[derive(Debug, Deserialize, PartialEq, Clone)]
53#[serde(rename_all = "camelCase")]
54#[serde(bound = "H: PersonHydrations")]
55pub struct PeopleResponse<H: PersonHydrations> {
56	pub copyright: Copyright,
57	#[serde(default)]
58	pub people: Vec<Person<H>>,
59}
60
61/// A baseball player.
62///
63/// [`Deref`]s to [`RegularPerson`]
64#[derive(Debug, Deref, DerefMut, Deserialize, Clone)]
65#[serde(rename_all = "camelCase")]
66#[serde(bound = "H: PersonHydrations")]
67pub struct Ballplayer<H: PersonHydrations> {
68	#[serde(deserialize_with = "crate::try_from_str")]
69	#[serde(default)]
70	pub primary_number: Option<u8>,
71	#[serde(flatten)]
72	pub birth_data: BirthData,
73	#[serde(flatten)]
74	pub body_measurements: BodyMeasurements,
75	pub gender: Gender,
76	pub draft_year: Option<u16>,
77	#[serde(rename = "mlbDebutDate")]
78	pub mlb_debut: Option<NaiveDate>,
79	pub bat_side: Handedness,
80	pub pitch_hand: Handedness,
81	#[serde(flatten)]
82	pub strike_zone: StrikeZoneMeasurements,
83	#[serde(rename = "nickName")]
84	pub nickname: Option<String>,
85
86	#[deref]
87	#[deref_mut]
88	#[serde(flatten)]
89	inner: Box<RegularPerson<H>>,
90}
91
92/// A regular person; detailed-name stuff.
93///
94/// [`Deref`]s to [`NamedPerson`]
95#[derive(Debug, Deserialize, Deref, DerefMut, Clone)]
96#[serde(rename_all = "camelCase")]
97#[serde(bound = "H: PersonHydrations")]
98pub struct RegularPerson<H: PersonHydrations> {
99	pub primary_position: NamedPosition,
100	// '? ? Brown' in 1920 does not have a first name or a middle name, rather than dealing with Option and making everyone hate this API, the better approach is an empty String.
101	#[serde(default)]
102	pub first_name: String,
103	#[serde(rename = "nameSuffix")]
104	pub suffix: Option<String>,
105	#[serde(default)] // this is how their API does it, so I'll copy that.
106	pub middle_name: String,
107	#[serde(default)]
108	pub last_name: String,
109	#[serde(default)]
110	#[serde(rename = "useName")]
111	pub use_first_name: String,
112	#[serde(default)]
113	pub use_last_name: String,
114	#[serde(default)]
115	pub boxscore_name: String,
116
117	pub is_player: bool,
118	#[serde(default)]
119	pub is_verified: bool,
120	pub active: bool,
121
122	#[deref]
123	#[deref_mut]
124	#[serde(flatten)]
125	inner: NamedPerson,
126
127	#[serde(flatten)]
128	pub extras: H,
129}
130
131impl<H: PersonHydrations> RegularPerson<H> {
132	#[must_use]
133	pub fn name_first_last(&self) -> String {
134		format!("{0} {1}", self.use_first_name, self.use_last_name)
135	}
136
137	#[must_use]
138	pub fn name_last_first(&self) -> String {
139		format!("{1}, {0}", self.use_first_name, self.use_last_name)
140	}
141
142	#[must_use]
143	pub fn name_last_first_initial(&self) -> String {
144		self.use_first_name.chars().next().map_or_else(|| self.use_last_name.clone(), |char| format!("{1}, {0}", char, self.use_last_name))
145	}
146
147	#[must_use]
148	pub fn name_first_initial_last(&self) -> String {
149		self.use_first_name.chars().next().map_or_else(|| self.use_last_name.clone(), |char| format!("{0} {1}", char, self.use_last_name))
150	}
151
152	#[must_use]
153	pub fn name_fml(&self) -> String {
154		format!("{0} {1} {2}", self.use_first_name, self.middle_name, self.use_last_name)
155	}
156
157	#[must_use]
158	pub fn name_lfm(&self) -> String {
159		format!("{2}, {0} {1}", self.use_first_name, self.middle_name, self.use_last_name)
160	}
161}
162
163/// A person with a name.
164///
165/// [`Deref`]s to [`PersonId`]
166#[derive(Debug, Deserialize, Clone, Eq)]
167#[serde(rename_all = "camelCase")]
168pub struct NamedPerson {
169	pub full_name: String,
170
171	#[serde(flatten)]
172	pub id: PersonId,
173}
174
175impl Hash for NamedPerson {
176	fn hash<H: Hasher>(&self, state: &mut H) {
177		self.id.hash(state);
178	}
179}
180
181impl NamedPerson {
182	#[must_use]
183	pub(crate) fn unknown_person() -> Self {
184		Self {
185			full_name: "null".to_owned(),
186			id: PersonId::new(0),
187		}
188	}
189
190	#[must_use]
191	pub fn is_unknown(&self) -> bool {
192		*self.id == 0
193	}
194}
195
196id!(#[doc = "A [`u32`] that represents a person."] PersonId { id: u32 });
197
198/// A complete person response for a hydrated request. Ballplayers have more fields.
199#[derive(Debug, Deserialize, Clone, From)]
200#[serde(untagged)]
201#[serde(bound = "H: PersonHydrations")]
202pub enum Person<H: PersonHydrations = ()> {
203	Ballplayer(Ballplayer<H>),
204	Regular(RegularPerson<H>),
205}
206
207impl<H: PersonHydrations> Person<H> {
208	#[must_use]
209	pub fn as_ballplayer(&self) -> Option<&Ballplayer<H>> {
210		match self {
211			Self::Ballplayer(x) => Some(x),
212			Self::Regular(_) => None,
213		}
214	}
215}
216
217impl<H: PersonHydrations> Person<H> {
218	#[must_use]
219	pub fn as_ballplayer_mut(&mut self) -> Option<&mut Ballplayer<H>> {
220		match self {
221			Self::Ballplayer(x) => Some(x),
222			Self::Regular(_) => None,
223		}
224	}
225}
226
227impl<H: PersonHydrations> Person<H> {
228	#[must_use]
229	pub fn into_ballplayer(self) -> Option<Ballplayer<H>> {
230		match self {
231			Self::Ballplayer(x) => Some(x),
232			Self::Regular(_) => None,
233		}
234	}
235}
236
237impl<H: PersonHydrations> Deref for Person<H> {
238	type Target = RegularPerson<H>;
239
240	fn deref(&self) -> &Self::Target {
241		match self {
242			Self::Ballplayer(x) => x,
243			Self::Regular(x) => x,
244		}
245	}
246}
247
248impl<H: PersonHydrations> DerefMut for Person<H> {
249	fn deref_mut(&mut self) -> &mut Self::Target {
250		match self {
251			Self::Ballplayer(x) => x,
252			Self::Regular(x) => x,
253		}
254	}
255}
256
257impl<H1: PersonHydrations, H2: PersonHydrations> PartialEq<Person<H2>> for Person<H1> {
258	fn eq(&self, other: &Person<H2>) -> bool {
259		self.id == other.id
260	}
261}
262
263impl<H1: PersonHydrations, H2: PersonHydrations> PartialEq<Ballplayer<H2>> for Ballplayer<H1> {
264	fn eq(&self, other: &Ballplayer<H2>) -> bool {
265		self.id == other.id
266	}
267}
268
269impl<H1: PersonHydrations, H2: PersonHydrations> PartialEq<RegularPerson<H2>> for RegularPerson<H1> {
270	fn eq(&self, other: &RegularPerson<H2>) -> bool {
271		self.id == other.id
272	}
273}
274
275id_only_eq_impl!(NamedPerson, id);
276
277/// Returns a [`PeopleResponse`].
278#[derive(Builder)]
279#[builder(derive(Into))]
280pub struct PersonRequest<H: PersonHydrations> {
281	#[builder(into)]
282	id: PersonId,
283
284	#[builder(into)]
285	hydrations: H::RequestData,
286}
287
288impl PersonRequest<()> {
289	pub fn for_id(id: impl Into<PersonId>) -> PersonRequestBuilder<(), person_request_builder::SetHydrations<person_request_builder::SetId>> {
290		Self::builder().id(id).hydrations(<() as Hydrations>::RequestData::default())
291	}
292}
293
294impl<H: PersonHydrations, S: person_request_builder::State + person_request_builder::IsComplete> crate::request::RequestURLBuilderExt for PersonRequestBuilder<H, S> {
295	type Built = PersonRequest<H>;
296}
297
298impl<H: PersonHydrations> Display for PersonRequest<H> {
299	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
300		let hydration_text = H::hydration_text(&self.hydrations);
301		if hydration_text.is_empty() {
302			write!(f, "http://statsapi.mlb.com/api/v1/people/{}", self.id)
303		} else {
304			write!(f, "http://statsapi.mlb.com/api/v1/people/{}?hydrate={hydration_text}", self.id)
305		}
306	}
307}
308
309impl<H: PersonHydrations> RequestURL for PersonRequest<H> {
310	type Response = PeopleResponse<H>;
311}
312
313/// The number on the back of a jersey, useful for radix sorts maybe??
314#[repr(transparent)]
315#[derive(Debug, Deref, Display, PartialEq, Copy, Clone, Hash, From)]
316pub struct JerseyNumber(u8);
317
318impl<'de> Deserialize<'de> for JerseyNumber {
319	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
320	where
321		D: Deserializer<'de>,
322	{
323		String::deserialize(deserializer)?.parse::<u8>().map(JerseyNumber).map_err(D::Error::custom)
324	}
325}
326
327/// Data regarding birthplace.
328#[derive(Debug, Deserialize, PartialEq, Clone)]
329#[serde(rename_all = "camelCase")]
330pub struct BirthData {
331	pub birth_date: NaiveDate,
332	pub birth_city: String,
333	#[serde(rename = "birthStateProvince")]
334	pub birth_state_or_province: Option<String>,
335	pub birth_country: String,
336}
337
338impl BirthData {
339	#[must_use]
340	pub fn current_age(&self) -> u16 {
341		Local::now().naive_local().date().years_since(self.birth_date).and_then(|x| u16::try_from(x).ok()).unwrap_or(0)
342	}
343}
344
345/// Height and weight
346#[derive(Debug, Deserialize, PartialEq, Clone)]
347#[serde(rename_all = "camelCase")]
348pub struct BodyMeasurements {
349	pub height: HeightMeasurement,
350	pub weight: u16,
351}
352
353/// Strike zone dimensions, measured in feet from the ground.
354#[derive(Debug, Deserialize, PartialEq, Clone)]
355#[serde(rename_all = "camelCase")]
356pub struct StrikeZoneMeasurements {
357	pub strike_zone_top: f64,
358	pub strike_zone_bottom: f64,
359}
360
361/// Data regarding preferred team, likely for showcasing the player with a certain look regardless of the time.
362#[serde_as]
363#[derive(Debug, Deserialize, PartialEq, Clone)]
364#[serde(rename_all = "camelCase")]
365pub struct PreferredTeamData {
366	#[serde(default)]
367	#[serde_as(deserialize_as = "DefaultOnError")]
368	pub jersey_number: Option<JerseyNumber>,
369	pub position: NamedPosition,
370	pub team: NamedTeam,
371}
372
373/// Relative to the ballplayer, father, son, etc.
374#[derive(Debug, Deserialize, PartialEq, Clone)]
375#[serde(rename_all = "camelCase")]
376pub struct Relative {
377	pub has_stats: bool,
378	pub relation: String,
379	#[serde(flatten)]
380	pub person: NamedPerson,
381}
382
383/// Schools the ballplayer went to.
384#[derive(Debug, Deserialize, PartialEq, Clone, Default)]
385pub struct Education {
386	#[serde(default)]
387	pub highschools: Vec<School>,
388	#[serde(default)]
389	pub colleges: Vec<School>,
390}
391
392/// A type that is made with [`person_hydrations!`](crate::person_hydrations)
393pub trait PersonHydrations: Hydrations {}
394
395impl PersonHydrations for () {}
396
397/// Creates hydrations for a person
398/// 
399/// ## Examples
400///```no_run
401/// person_hydrations! {
402///     pub struct TestHydrations {  ->  pub struct TestHydrations {
403///         awards,                     ->      awards: Vec<Award>,
404///         social,                     ->      social: HashMap<String, Vec<String>>,
405///         stats: MyStats,             ->      stats: MyStats,
406///     }                               ->  }
407/// }
408///
409/// person_hydrations! {
410///     pub struct TestHydrations {        ->  pub struct TestHydrations {
411///         stats: { [Season] + [Hitting] },  ->      stats: TestHydrationsInlineStats,
412///     }                                     ->  }
413/// }
414///
415/// let request = PersonRequest::<TestHydrations>::builder()
416///     .id(660_271)
417///     .hydrations(TestHydrations::builder())
418///     .build();
419///
420/// let response = request.get().await.unwrap();
421///```
422///
423/// ## Person Hydrations
424/// <u>Note: Fields must appear in exactly this order (or be omitted)</u>
425///
426/// | Name             | Type                             |
427/// |------------------|----------------------------------|
428/// | `awards`         | [`Vec<Award>`]                   |
429/// | `current_team`   | [`Team`]                         |
430/// | `depth_charts`   | [`Vec<RosterEntry>`]             |
431/// | `draft`          | [`Vec<DraftPick>`]               |
432/// | `education`      | [`Education`]                    |
433/// | `jobs`           | [`Vec<EmployedPerson>`]          |
434/// | `nicknames`      | [`Vec<String>`]                  |
435/// | `preferred_team` | [`Team`]                         |
436/// | `relatives`      | [`Vec<Relative>`]                |
437/// | `roster_entries` | [`Vec<RosterEntry>`]             |
438/// | `transactions`   | [`Vec<Transaction>`]             |
439/// | `social`         | [`HashMap<String, Vec<String>>`] |
440/// | `stats`          | [`stats_hydrations!`]            |
441/// | `external_references` | [`Vec<ExternalReference>`]  |
442///
443/// [`Vec<Award>`]: crate::awards::Award
444/// [`Team`]: crate::team::Team
445/// [`Vec<RosterEntry>`]: crate::team::roster::RosterEntry
446/// [`Vec<DraftPick>`]: crate::draft::DraftPick
447/// [`Education`]: Education
448/// [`Vec<EmployedPerson>`]: crate::jobs::EmployedPerson
449/// [`Vec<String>`]: String
450/// [`Team`]: crate::team::Team
451/// [`Vec<Relative>`]: Relative
452/// [`Vec<RosterEntry>`]: crate::team::rosterRosterEntry
453/// [`Vec<Transaction>`]: crate::transactions::Transaction
454/// [`HashMap<String, Vec<String>>`]: std::collections::HashMap
455/// [`stats_hydrations!`]: crate::stats_hydrations
456/// [`Vec<ExternalReference>`]: crate::types::ExternalReference
457#[macro_export]
458macro_rules! person_hydrations {
459	(@ inline_structs [stats: { $($contents:tt)* } $(, $($rest:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
460        ::pastey::paste! {
461            $crate::stats_hydrations! {
462                $vis struct [<$name InlineStats>] {
463                    $($contents)*
464                }
465            }
466
467            $crate::person_hydrations! { @ inline_structs [$($($rest)*)?]
468                $vis struct $name {
469                    $($field_tt)*
470                    stats: [<$name InlineStats>],
471                }
472            }
473        }
474    };
475    (@ inline_structs [$marker:ident : { $($contents:tt)* } $(, $($rest:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
476        ::core::compile_error!("Found unknown inline struct");
477    };
478    (@ inline_structs [$marker:ident $(: $value:ty)? $(, $($rest:tt)*)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
479        ::pastey::paste! {
480            $crate::person_hydrations! { @ inline_structs [$($($rest)*)?]
481                $vis struct $name {
482                    $($field_tt)*
483                    $marker $(: $value)?,
484                }
485            }
486        }
487    };
488    (@ inline_structs [$(,)?] $vis:vis struct $name:ident { $($field_tt:tt)* }) => {
489        ::pastey::paste! {
490            $crate::person_hydrations! { @ actual
491                $vis struct $name {
492                    $($field_tt)*
493                }
494            }
495        }
496    };
497    (@ actual
498		$vis:vis struct $name:ident {
499			$(awards $awards_comma:tt)?
500			$(current_team $current_team_comma:tt)?
501			$(depth_charts $depth_charts_comma:tt)?
502			$(draft $draft_comma:tt)?
503			$(education $education_comma:tt)?
504			$(jobs $jobs_comma:tt)?
505			$(nicknames $nicknames_comma:tt)?
506			$(preferred_team $preferred_team_comma:tt)?
507			$(relatives $relatives_comma:tt)?
508			$(roster_entries $roster_entries_comma:tt)?
509			$(transactions $transactions_comma:tt)?
510			$(social $social_comma:tt)?
511			$(stats: $stats:ty ,)?
512			$(external_references $external_references_comma:tt)?
513		}
514    ) => {
515		::pastey::paste! {
516			#[derive(::core::fmt::Debug, ::serde::Deserialize, ::core::cmp::PartialEq, ::core::clone::Clone)]
517			#[serde(rename_all = "camelCase")]
518			$vis struct $name {
519				$(#[serde(default)] pub awards: ::std::vec::Vec<$crate::awards::Award> $awards_comma)?
520				$(pub current_team: ::core::option::Option<$crate::team::NamedTeam> $current_team_comma)?
521				$(#[serde(default)] pub depth_charts: ::std::vec::Vec<$crate::team::roster::RosterEntry> $depth_charts_comma)?
522				$(#[serde(default, rename = "drafts")] pub draft: ::std::vec::Vec<$crate::draft::DraftPick> $draft_comma)?
523				$(#[serde(default)] pub education: $crate::person::Education $education_comma)?
524				$(#[serde(default, rename = "jobEntries")] pub jobs: ::std::vec::Vec<$crate::jobs::EmployedPerson> $jobs_comma)?
525				$(#[serde(default)] pub nicknames: ::std::vec::Vec<String> $nicknames_comma)?
526				$(pub preferred_team: ::core::option::Option<$crate::person::PreferredTeamData> $preferred_team_comma)?
527				$(#[serde(default)] pub relatives: ::std::vec::Vec<$crate::person::Relative> $relatives_comma)?
528				$(#[serde(default)] pub roster_entries: ::std::vec::Vec<$crate::team::roster::RosterEntry> $roster_entries_comma)?
529				$(#[serde(default)] pub transactions: ::std::vec::Vec<$crate::transactions::Transaction> $transactions_comma)?
530				$(#[serde(flatten)] pub stats: $stats ,)?
531				$(#[serde(default, rename = "social")] pub socials: ::std::collections::HashMap<String, Vec<String>> $social_comma)?
532				$(#[serde(default, rename = "xrefIds")] pub external_references: ::std::vec::Vec<$crate::types::ExternalReference> $external_references_comma)?
533			}
534
535			impl $crate::person::PersonHydrations for $name {}
536
537			impl $crate::hydrations::Hydrations for $name {
538				type RequestData = [<$name RequestData>];
539
540				fn hydration_text(_data: &Self::RequestData) -> ::std::borrow::Cow<'static, str> {
541					let text = ::std::borrow::Cow::Borrowed(::std::concat!(
542						$("awards," $awards_comma)?
543						$("currentTeam," $current_team_comma)?
544						$("depthCharts," $depth_charts_comma)?
545						$("draft," $draft_comma)?
546						$("education," $education_comma)?
547						$("jobs," $jobs_comma)?
548						$("nicknames," $nicknames_comma)?
549						$("preferredTeam," $preferred_team_comma)?
550						$("relatives," $relatives_comma)?
551						$("rosterEntries," $roster_entries_comma)?
552						$("transactions," $transactions_comma)?
553						$("social," $social_comma)?
554						$("xrefId," $external_references_comma)?
555					));
556
557					$(
558					let text = ::std::borrow::Cow::Owned(::std::format!("{text}stats({}),", <$stats as $crate::hydrations::Hydrations>::hydration_text(&_data.stats)));
559					)?
560
561					text
562				}
563			}
564
565			#[derive(::bon::Builder)]
566			#[builder(derive(Into))]
567			$vis struct [<$name RequestData>] {
568				$(#[builder(into)] stats: <$stats as $crate::hydrations::Hydrations>::RequestData,)?
569			}
570
571			impl $name {
572				#[allow(unused, reason = "potentially unused if the builder is Default")]
573				pub fn builder() -> [<$name RequestDataBuilder>] {
574					[<$name RequestData>]::builder()
575				}
576			}
577
578			impl ::core::default::Default for [<$name RequestData>]
579			where
580				$(for<'no_rfc_2056> <$stats as $crate::hydrations::Hydrations>::RequestData: ::core::default::Default,)?
581			{
582				fn default() -> Self {
583					Self {
584						$(stats: <<$stats as $crate::hydrations::Hydrations>::RequestData as ::core::default::Default>::default(),)?
585					}
586				}
587			}
588		}
589    };
590	($vis:vis struct $name:ident {
591		$($tt:tt)*
592	}) => {
593		$crate::person_hydrations! { @ inline_structs [$($tt)*] $vis struct $name {} }
594	};
595}
596
597#[cfg(feature = "cache")]
598static CACHE: RwLock<CacheTable<Person<()>>> = rwlock_const_new(CacheTable::new());
599
600impl Requestable for Person<()> {
601	type Identifier = PersonId;
602	type URL = PersonRequest<()>;
603
604	fn id(&self) -> &Self::Identifier {
605		&self.id
606	}
607
608	fn url_for_id(id: &Self::Identifier) -> Self::URL {
609		PersonRequest::for_id(*id).build()
610	}
611
612	fn get_entries(response: <Self::URL as RequestURL>::Response) -> impl IntoIterator<Item = Self>
613	where
614		Self: Sized,
615	{
616		response.people
617	}
618
619	#[cfg(feature = "cache")]
620	fn get_cache_table() -> &'static RwLock<CacheTable<Self>>
621	where
622		Self: Sized,
623	{
624		&CACHE
625	}
626}
627
628entrypoint!(PersonId => Person);
629entrypoint!(NamedPerson.id => Person);
630entrypoint!(for < H > RegularPerson < H > . id => Person < > where H: PersonHydrations);
631entrypoint!(for < H > Ballplayer < H > . id => Person < > where H: PersonHydrations);
632
633#[cfg(test)]
634mod tests {
635	use crate::person::players::PlayersRequest;
636	use crate::request::RequestURLBuilderExt;
637	use crate::sport::SportId;
638	use super::*;
639	use crate::TEST_YEAR;
640
641	#[tokio::test]
642	async fn no_hydrations() {
643		person_hydrations! {
644			pub struct EmptyHydrations {}
645		}
646
647		let _ = PersonRequest::<()>::for_id(665_489).build_and_get().await.unwrap();
648		let _ = PersonRequest::<EmptyHydrations>::builder().id(665_489).hydrations(EmptyHydrationsRequestData::default()).build_and_get().await.unwrap();
649	}
650
651	#[tokio::test]
652	async fn all_but_stats_hydrations() {
653		person_hydrations! {
654			pub struct AllButStatHydrations {
655				awards,
656				current_team,
657				depth_charts,
658				draft,
659				education,
660				jobs,
661				nicknames,
662				preferred_team,
663				relatives,
664				roster_entries,
665				transactions,
666				social,
667				external_references
668			}
669		}
670
671		let _person = PersonRequest::<AllButStatHydrations>::builder().hydrations(AllButStatHydrationsRequestData::default()).id(665_489).build_and_get().await.unwrap().people.into_iter().next().unwrap();
672	}
673
674	#[rustfmt::skip]
675	#[tokio::test]
676	async fn only_stats_hydrations() {
677		person_hydrations! {
678			pub struct StatOnlyHydrations {
679				stats: { [Sabermetrics] + [Pitching] },
680			}
681		}
682
683		let player = PlayersRequest::<()>::for_sport(SportId::MLB)
684			.season(TEST_YEAR)
685			.build_and_get()
686			.await
687			.unwrap()
688			.people
689			.into_iter()
690			.find(|player| player.full_name == "Kevin Gausman")
691			.unwrap();
692
693		let request = PersonRequest::<StatOnlyHydrations>::builder()
694			.id(player.id)
695			.hydrations(StatOnlyHydrations::builder()
696				.stats(StatOnlyHydrationsInlineStats::builder()
697					.season(2023)
698					// .situation(SituationCodeId::new("h"))
699				)
700			).build();
701		println!("{request}");
702		let player = request.get()
703			.await
704			.unwrap()
705			.people
706			.into_iter()
707			.next()
708			.unwrap();
709
710		dbg!(&player.extras.stats);
711	}
712}