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