Skip to main content

mlb_api/requests/
transactions.rs

1//! Roster transactions; calling someone up, free agent signings, etc.
2
3use crate::person::{NamedPerson, PersonId};
4use crate::team::{TeamId};
5use crate::{Copyright, NaiveDateRange, MLB_API_DATE_FORMAT};
6use crate::request::RequestURL;
7use bon::Builder;
8use chrono::NaiveDate;
9use itertools::Itertools;
10use serde::{Deserialize, Deserializer};
11use std::fmt::{Display, Formatter};
12use std::ops::{Deref, DerefMut};
13use crate::sport::SportId;
14use crate::team::NamedTeam;
15
16#[derive(Debug, Deserialize, PartialEq, Clone)]
17#[serde(rename_all = "camelCase")]
18pub struct TransactionsResponse {
19	pub copyright: Copyright,
20	pub transactions: Vec<Transaction>,
21}
22
23#[derive(Debug, Deserialize, PartialEq, Clone)]
24#[serde(rename_all = "camelCase")]
25pub struct TransactionCommon {
26	pub id: TransactionId,
27	#[serde(default)]
28	pub description: String,
29	#[serde(flatten)]
30	pub dates: TransactionDates,
31}
32
33#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
34#[serde(rename_all = "camelCase")]
35pub struct TransactionDates {
36	pub date: NaiveDate,
37	pub effective_date: Option<NaiveDate>,
38	pub resolution_date: Option<NaiveDate>,
39}
40
41//noinspection DuplicatedCode
42#[derive(Debug, Deserialize, PartialEq, Clone)]
43#[serde(tag = "typeCode")]
44pub enum Transaction {
45	#[serde(rename = "ASG", rename_all = "camelCase")]
46	Assigned {
47		#[serde(flatten)]
48		common: TransactionCommon,
49		#[serde(deserialize_with = "deserialize_named_person")]
50		#[serde(default = "NamedPerson::unknown_person")]
51		person: NamedPerson,
52		#[serde(rename = "fromTeam")]
53		#[serde(deserialize_with = "deserialize_named_team_opt")]
54		#[serde(default)]
55		source_team: Option<NamedTeam>,
56		#[serde(default = "NamedTeam::unknown_team")]
57		#[serde(rename = "toTeam")]
58		#[serde(deserialize_with = "deserialize_named_team")]
59		destination_team: NamedTeam,
60	},
61	#[serde(rename = "SC", rename_all = "camelCase")]
62	StatusChange {
63		#[serde(flatten)]
64		common: TransactionCommon,
65		#[serde(deserialize_with = "deserialize_named_person")]
66		#[serde(default = "NamedPerson::unknown_person")]
67		person: NamedPerson,
68		#[serde(default = "NamedTeam::unknown_team")]
69		#[serde(rename = "toTeam")]
70		#[serde(deserialize_with = "deserialize_named_team")]
71		team: NamedTeam,
72	},
73	#[serde(rename = "SFA", rename_all = "camelCase")]
74	SignedAsFreeAgent {
75		#[serde(flatten)]
76		common: TransactionCommon,
77		#[serde(deserialize_with = "deserialize_named_person")]
78		#[serde(default = "NamedPerson::unknown_person")]
79		person: NamedPerson,
80		#[serde(default = "NamedTeam::unknown_team")]
81		#[serde(rename = "toTeam")]
82		#[serde(deserialize_with = "deserialize_named_team")]
83		team: NamedTeam,
84	},
85	#[serde(rename = "DES", rename_all = "camelCase")]
86	DesignatedForAssignment {
87		#[serde(flatten)]
88		common: TransactionCommon,
89		#[serde(deserialize_with = "deserialize_named_person")]
90		#[serde(default = "NamedPerson::unknown_person")]
91		person: NamedPerson,
92		#[serde(default = "NamedTeam::unknown_team")]
93		#[serde(rename = "toTeam")]
94		#[serde(deserialize_with = "deserialize_named_team")]
95		team: NamedTeam,
96	},
97	#[serde(rename = "TR", rename_all = "camelCase")]
98	Trade {
99		#[serde(flatten)]
100		common: TransactionCommon,
101		/// No person here indicates a trade occurred that gave the team cash.
102		#[serde(deserialize_with = "deserialize_named_person_opt")]
103		#[serde(default)]
104		person: Option<NamedPerson>,
105		#[serde(default = "NamedTeam::unknown_team")]
106		#[serde(rename = "fromTeam")]
107		#[serde(deserialize_with = "deserialize_named_team")]
108		source_team: NamedTeam,
109		#[serde(default = "NamedTeam::unknown_team")]
110		#[serde(rename = "toTeam")]
111		#[serde(deserialize_with = "deserialize_named_team")]
112		destination_team: NamedTeam,
113	},
114	#[serde(rename = "NUM", rename_all = "camelCase")]
115	NumberChange {
116		#[serde(flatten)]
117		common: TransactionCommon,
118		#[serde(deserialize_with = "deserialize_named_person")]
119		#[serde(default = "NamedPerson::unknown_person")]
120		person: NamedPerson,
121		#[serde(default = "NamedTeam::unknown_team")]
122		#[serde(rename = "toTeam")]
123		#[serde(deserialize_with = "deserialize_named_team")]
124		team: NamedTeam,
125	},
126	#[serde(rename = "OUT", rename_all = "camelCase")]
127	Outrighted {
128		#[serde(flatten)]
129		common: TransactionCommon,
130		#[serde(deserialize_with = "deserialize_named_person")]
131		#[serde(default = "NamedPerson::unknown_person")]
132		person: NamedPerson,
133		#[serde(default = "NamedTeam::unknown_team")]
134		#[serde(rename = "fromTeam")]
135		#[serde(deserialize_with = "deserialize_named_team")]
136		source_team: NamedTeam,
137		#[serde(default = "NamedTeam::unknown_team")]
138		#[serde(rename = "toTeam")]
139		#[serde(deserialize_with = "deserialize_named_team")]
140		destination_team: NamedTeam,
141	},
142	#[serde(rename = "CLW", rename_all = "camelCase")]
143	ClaimedOffWaivers {
144		#[serde(flatten)]
145		common: TransactionCommon,
146		#[serde(deserialize_with = "deserialize_named_person")]
147		#[serde(default = "NamedPerson::unknown_person")]
148		person: NamedPerson,
149		#[serde(default = "NamedTeam::unknown_team")]
150		#[serde(rename = "fromTeam")]
151		#[serde(deserialize_with = "deserialize_named_team")]
152		source_team: NamedTeam,
153		#[serde(default = "NamedTeam::unknown_team")]
154		#[serde(rename = "toTeam")]
155		#[serde(deserialize_with = "deserialize_named_team")]
156		destination_team: NamedTeam,
157	},
158	#[serde(rename = "SGN", rename_all = "camelCase")]
159	Signed {
160		#[serde(flatten)]
161		common: TransactionCommon,
162		#[serde(deserialize_with = "deserialize_named_person")]
163		#[serde(default = "NamedPerson::unknown_person")]
164		person: NamedPerson,
165		#[serde(rename = "toTeam")]
166		#[serde(deserialize_with = "deserialize_named_team")]
167		team: NamedTeam,
168	},
169	#[serde(rename = "REL", rename_all = "camelCase")]
170	Released {
171		#[serde(flatten)]
172		common: TransactionCommon,
173		#[serde(deserialize_with = "deserialize_named_person")]
174		#[serde(default = "NamedPerson::unknown_person")]
175		person: NamedPerson,
176		#[serde(default = "NamedTeam::unknown_team")]
177		#[serde(rename = "toTeam")]
178		#[serde(deserialize_with = "deserialize_named_team")]
179		team: NamedTeam,
180	},
181	#[serde(rename = "DFA", rename_all = "camelCase")]
182	DeclaredFreeAgency {
183		#[serde(flatten)]
184		common: TransactionCommon,
185		#[serde(deserialize_with = "deserialize_named_person")]
186		#[serde(default = "NamedPerson::unknown_person")]
187		person: NamedPerson,
188		#[serde(default = "NamedTeam::unknown_team")]
189		#[serde(rename = "toTeam")]
190		#[serde(deserialize_with = "deserialize_named_team")]
191		source_team: NamedTeam,
192	},
193	#[serde(rename = "OPT", rename_all = "camelCase")]
194	Optioned {
195		#[serde(flatten)]
196		common: TransactionCommon,
197		#[serde(deserialize_with = "deserialize_named_person")]
198		#[serde(default = "NamedPerson::unknown_person")]
199		person: NamedPerson,
200		#[serde(default = "NamedTeam::unknown_team")]
201		#[serde(rename = "fromTeam")]
202		#[serde(deserialize_with = "deserialize_named_team")]
203		source_team: NamedTeam,
204		#[serde(default = "NamedTeam::unknown_team")]
205		#[serde(rename = "toTeam")]
206		#[serde(deserialize_with = "deserialize_named_team")]
207		destination_team: NamedTeam,
208	},
209	#[serde(rename = "RTN", rename_all = "camelCase")]
210	Returned {
211		#[serde(flatten)]
212		common: TransactionCommon,
213		#[serde(deserialize_with = "deserialize_named_person")]
214		#[serde(default = "NamedPerson::unknown_person")]
215		person: NamedPerson,
216		#[serde(default = "NamedTeam::unknown_team")]
217		#[serde(rename = "fromTeam")]
218		#[serde(deserialize_with = "deserialize_named_team")]
219		source_team: NamedTeam,
220		#[serde(default = "NamedTeam::unknown_team")]
221		#[serde(rename = "toTeam")]
222		#[serde(deserialize_with = "deserialize_named_team")]
223		destination_team: NamedTeam,
224	},
225	#[serde(rename = "SE", rename_all = "camelCase")]
226	Selected {
227		#[serde(flatten)]
228		common: TransactionCommon,
229		#[serde(deserialize_with = "deserialize_named_person")]
230		#[serde(default = "NamedPerson::unknown_person")]
231		person: NamedPerson,
232		#[serde(rename = "fromTeam")]
233		#[serde(deserialize_with = "deserialize_named_team_opt")]
234		#[serde(default)]
235		source_team: Option<NamedTeam>,
236		#[serde(default = "NamedTeam::unknown_team")]
237		#[serde(rename = "toTeam")]
238		#[serde(deserialize_with = "deserialize_named_team")]
239		destination_team: NamedTeam,
240	},
241	#[serde(rename = "CU", rename_all = "camelCase")]
242	Recalled {
243		#[serde(flatten)]
244		common: TransactionCommon,
245		#[serde(deserialize_with = "deserialize_named_person")]
246		#[serde(default = "NamedPerson::unknown_person")]
247		person: NamedPerson,
248		#[serde(rename = "fromTeam")]
249		#[serde(deserialize_with = "deserialize_named_team_opt")]
250		#[serde(default)]
251		source_team: Option<NamedTeam>,
252		#[serde(default = "NamedTeam::unknown_team")]
253		#[serde(rename = "toTeam")]
254		#[serde(deserialize_with = "deserialize_named_team")]
255		destination_team: NamedTeam,
256	},
257	#[serde(rename = "SU", rename_all = "camelCase")]
258	Suspension {
259		#[serde(flatten)]
260		common: TransactionCommon,
261		#[serde(deserialize_with = "deserialize_named_person")]
262		#[serde(default = "NamedPerson::unknown_person")]
263		person: NamedPerson,
264		#[serde(default = "NamedTeam::unknown_team")]
265		#[serde(rename = "toTeam")]
266		#[serde(deserialize_with = "deserialize_named_team")]
267		team: NamedTeam,
268	},
269	#[serde(rename = "RET", rename_all = "camelCase")]
270	Retired {
271		#[serde(flatten)]
272		common: TransactionCommon,
273		#[serde(deserialize_with = "deserialize_named_person")]
274		#[serde(default = "NamedPerson::unknown_person")]
275		person: NamedPerson,
276		#[serde(default = "NamedTeam::unknown_team")]
277		#[serde(rename = "toTeam")]
278		#[serde(deserialize_with = "deserialize_named_team")]
279		team: NamedTeam,
280	},
281	#[serde(rename = "PUR", rename_all = "camelCase")]
282	Purchase {
283		#[serde(flatten)]
284		common: TransactionCommon,
285		#[serde(deserialize_with = "deserialize_named_person")]
286		#[serde(default = "NamedPerson::unknown_person")]
287		person: NamedPerson,
288		#[serde(rename = "fromTeam")]
289		#[serde(deserialize_with = "deserialize_named_team_opt")]
290		#[serde(default)]
291		source_team: Option<NamedTeam>,
292		#[serde(default = "NamedTeam::unknown_team")]
293		#[serde(rename = "toTeam")]
294		#[serde(deserialize_with = "deserialize_named_team")]
295		destination_team: NamedTeam,
296	},
297	#[serde(rename = "R5", rename_all = "camelCase")]
298	RuleFiveDraft {
299		#[serde(flatten)]
300		common: TransactionCommon,
301		#[serde(deserialize_with = "deserialize_named_person")]
302		#[serde(default = "NamedPerson::unknown_person")]
303		person: NamedPerson,
304		#[serde(default = "NamedTeam::unknown_team")]
305		#[serde(rename = "fromTeam")]
306		#[serde(deserialize_with = "deserialize_named_team")]
307		source_team: NamedTeam,
308		#[serde(default = "NamedTeam::unknown_team")]
309		#[serde(rename = "toTeam")]
310		#[serde(deserialize_with = "deserialize_named_team")]
311		destination_team: NamedTeam,
312	},
313	#[serde(rename = "RE", rename_all = "camelCase")]
314	Reinstated {
315		#[serde(flatten)]
316		common: TransactionCommon,
317		#[serde(deserialize_with = "deserialize_named_person")]
318		#[serde(default = "NamedPerson::unknown_person")]
319		person: NamedPerson,
320		#[serde(default = "NamedTeam::unknown_team")]
321		#[serde(rename = "toTeam")]
322		#[serde(deserialize_with = "deserialize_named_team")]
323		team: NamedTeam,
324	},
325	#[serde(rename = "LON", rename_all = "camelCase")]
326	Loan {
327		#[serde(flatten)]
328		common: TransactionCommon,
329		#[serde(deserialize_with = "deserialize_named_person")]
330		#[serde(default = "NamedPerson::unknown_person")]
331		person: NamedPerson,
332		#[serde(default = "NamedTeam::unknown_team")]
333		#[serde(rename = "fromTeam")]
334		#[serde(deserialize_with = "deserialize_named_team")]
335		source_team: NamedTeam,
336		#[serde(default = "NamedTeam::unknown_team")]
337		#[serde(rename = "toTeam")]
338		#[serde(deserialize_with = "deserialize_named_team")]
339		destination_team: NamedTeam,
340	},
341	#[serde(rename = "CP", rename_all = "camelCase")]
342	ContractPurchased {
343		#[serde(flatten)]
344		common: TransactionCommon,
345		#[serde(deserialize_with = "deserialize_named_person")]
346		#[serde(default = "NamedPerson::unknown_person")]
347		person: NamedPerson,
348		#[serde(default = "NamedTeam::unknown_team")]
349		#[serde(rename = "toTeam")]
350		#[serde(deserialize_with = "deserialize_named_team")]
351		team: NamedTeam,
352	},
353	#[serde(rename = "DR", rename_all = "camelCase")]
354	Drafted {
355		#[serde(flatten)]
356		common: TransactionCommon,
357		#[serde(deserialize_with = "deserialize_named_person")]
358		#[serde(default = "NamedPerson::unknown_person")]
359		person: NamedPerson,
360		#[serde(default = "NamedTeam::unknown_team")]
361		#[serde(rename = "toTeam")]
362		#[serde(deserialize_with = "deserialize_named_team")]
363		team: NamedTeam,
364	},
365	#[serde(rename = "DEI", rename_all = "camelCase")]
366	DeclaredIneligible {
367		#[serde(flatten)]
368		common: TransactionCommon,
369		#[serde(deserialize_with = "deserialize_named_person")]
370		#[serde(default = "NamedPerson::unknown_person")]
371		person: NamedPerson,
372		#[serde(default = "NamedTeam::unknown_team")]
373		#[serde(rename = "toTeam")]
374		#[serde(deserialize_with = "deserialize_named_team")]
375		team: NamedTeam,
376	},
377	#[serde(rename = "R5M", rename_all = "camelCase")]
378	RuleFiveDraftMinors {
379		#[serde(flatten)]
380		common: TransactionCommon,
381		#[serde(deserialize_with = "deserialize_named_person")]
382		#[serde(default = "NamedPerson::unknown_person")]
383		person: NamedPerson,
384		#[serde(default = "NamedTeam::unknown_team")]
385		#[serde(rename = "fromTeam")]
386		#[serde(deserialize_with = "deserialize_named_team")]
387		source_team: NamedTeam,
388		#[serde(default = "NamedTeam::unknown_team")]
389		#[serde(rename = "toTeam")]
390		#[serde(deserialize_with = "deserialize_named_team")]
391		destination_team: NamedTeam,
392	},
393	#[serde(rename = "RES", rename_all = "camelCase")]
394	Reserved {
395		#[serde(flatten)]
396		common: TransactionCommon,
397		#[serde(deserialize_with = "deserialize_named_person")]
398		#[serde(default = "NamedPerson::unknown_person")]
399		person: NamedPerson,
400		#[serde(default = "NamedTeam::unknown_team")]
401		#[serde(rename = "toTeam")]
402		#[serde(deserialize_with = "deserialize_named_team")]
403		team: NamedTeam,
404	},
405}
406
407impl Deref for Transaction {
408	type Target = TransactionCommon;
409
410	#[rustfmt::skip]
411	fn deref(&self) -> &Self::Target {
412		match self {
413			Self::Assigned { common, .. }
414			| Self::StatusChange { common, .. }
415			| Self::SignedAsFreeAgent { common, .. }
416			| Self::DesignatedForAssignment { common, .. }
417			| Self::Trade { common, .. }
418			| Self::NumberChange { common, .. }
419			| Self::Outrighted { common, .. }
420			| Self::ClaimedOffWaivers { common, .. }
421			| Self::Signed { common, .. }
422			| Self::Released { common, .. }
423			| Self::DeclaredFreeAgency { common, .. }
424			| Self::Optioned { common, .. }
425			| Self::Returned { common, .. }
426			| Self::Selected { common, .. }
427			| Self::Recalled { common, .. }
428			| Self::Suspension { common, .. }
429			| Self::Retired { common, .. }
430			| Self::Purchase { common, .. }
431			| Self::RuleFiveDraft { common, .. }
432			| Self::Reinstated { common, .. }
433			| Self::Loan { common, .. }
434			| Self::ContractPurchased { common, .. }
435			| Self::Drafted { common, .. }
436			| Self::DeclaredIneligible { common, .. }
437			| Self::RuleFiveDraftMinors { common, .. }
438			| Self::Reserved { common, .. } => common,
439		}
440	}
441}
442
443impl DerefMut for Transaction {
444	#[rustfmt::skip]
445	fn deref_mut(&mut self) -> &mut Self::Target {
446		match self {
447			Self::Assigned { common, .. }
448			| Self::StatusChange { common, .. }
449			| Self::SignedAsFreeAgent { common, .. }
450			| Self::DesignatedForAssignment { common, .. }
451			| Self::Trade { common, .. }
452			| Self::NumberChange { common, .. }
453			| Self::Outrighted { common, .. }
454			| Self::ClaimedOffWaivers { common, .. }
455			| Self::Signed { common, .. }
456			| Self::Released { common, .. }
457			| Self::DeclaredFreeAgency { common, .. }
458			| Self::Optioned { common, .. }
459			| Self::Returned { common, .. }
460			| Self::Selected { common, .. }
461			| Self::Recalled { common, .. }
462			| Self::Suspension { common, .. }
463			| Self::Retired { common, .. }
464			| Self::Purchase { common, .. }
465			| Self::RuleFiveDraft { common, .. }
466			| Self::Reinstated { common, .. }
467			| Self::Loan { common, .. }
468			| Self::ContractPurchased { common, .. }
469			| Self::Drafted { common, .. }
470			| Self::DeclaredIneligible { common, .. }
471			| Self::RuleFiveDraftMinors { common, .. }
472			| Self::Reserved { common, .. } => common,
473		}
474	}
475}
476
477impl Display for Transaction {
478	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
479		write!(f, "{}", self.description)
480	}
481}
482
483id!(TransactionId { id: u32 });
484
485fn deserialize_named_person<'de, D: Deserializer<'de>>(deserializer: D) -> Result<NamedPerson, D::Error> {
486	#[derive(Deserialize)]
487	#[serde(rename = "camelCase")]
488	struct NamedPersonWrapper {
489		#[serde(alias = "name")]
490		full_name: Option<String>,
491		#[serde(flatten)]
492		id: Option<PersonId>,
493	}
494
495	let NamedPersonWrapper { full_name, id } = NamedPersonWrapper::deserialize(deserializer)?;
496	Ok(NamedPerson {
497		full_name: full_name.unwrap_or(String::new()),
498		id: id.unwrap_or(PersonId::new(0))
499	})
500}
501
502fn deserialize_named_person_opt<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<NamedPerson>, D::Error> {
503	#[derive(Deserialize)]
504	#[serde(rename = "camelCase")]
505	struct NamedPersonWrapper {
506		#[serde(alias = "name")]
507		full_name: Option<String>,
508		#[serde(flatten)]
509		id: Option<PersonId>,
510	}
511
512	let wrapped = NamedPersonWrapper::deserialize(deserializer)?;
513	match wrapped {
514		NamedPersonWrapper { full_name, id: Some(id) } => Ok(Some(NamedPerson { full_name: full_name.unwrap_or(String::new()), id })),
515		_ => Ok(None)
516	}
517}
518
519fn deserialize_named_team<'de, D: Deserializer<'de>>(deserializer: D) -> Result<NamedTeam, D::Error> {
520	#[derive(Deserialize)]
521	#[serde(rename = "camelCase")]
522	struct NamedTeamWrapper {
523		#[serde(alias = "name")]
524		full_name: Option<String>,
525		#[serde(flatten)]
526		id: Option<TeamId>,
527	}
528
529	let NamedTeamWrapper { full_name, id } = NamedTeamWrapper::deserialize(deserializer)?;
530	Ok(NamedTeam {
531		full_name: full_name.unwrap_or(String::new()),
532		id: id.unwrap_or(TeamId::new(0))
533	})
534}
535
536fn deserialize_named_team_opt<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<NamedTeam>, D::Error> {
537	#[derive(Deserialize)]
538	#[serde(rename = "camelCase")]
539	struct NamedTeamWrapper {
540		#[serde(alias = "name")]
541		full_name: Option<String>,
542		#[serde(flatten)]
543		id: Option<TeamId>,
544	}
545
546	let wrapped = NamedTeamWrapper::deserialize(deserializer)?;
547	match wrapped {
548		NamedTeamWrapper { full_name, id: Some(id) } => Ok(Some(NamedTeam { full_name: full_name.unwrap_or(String::new()), id })),
549		_ => Ok(None),
550	}
551}
552
553pub enum TransactionsRequestKind {
554	Team(TeamId),
555	Player(PersonId),
556	Transactions(Vec<TransactionId>),
557	DateRange(NaiveDateRange),
558}
559
560impl Display for TransactionsRequestKind {
561	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
562		match self {
563			Self::Team(team_id) => write!(f, "teamId={team_id}"),
564			Self::Player(person_id) => write!(f, "playerId={person_id}"),
565			Self::Transactions(transactions) => write!(f, "transactionIds={}", transactions.iter().join(",")),
566			Self::DateRange(range) => write!(f, "startDate={}&endDate={}", range.start().format(MLB_API_DATE_FORMAT), range.end().format(MLB_API_DATE_FORMAT)),
567		}
568	}
569}
570
571/// Sends a request to get the relevant transactions for a player.
572///
573/// This API request is somewhat unreliable. For an example of what I mean: <http://statsapi.mlb.com/api/v1/transactions?transactionIds=477955>. Vladimir Guerrero Jr.'s `.` in his name causes the API to be super confused and generate 5 players, four of which don't exist.
574///
575/// Of course putting [`Option<Person>`] for the `person` field is needlessly overkill since mostly all situations will not cause this, but the transactions shouldn't be discarded.
576///
577/// Instead, these values (no team, no date, no player) are given default values such that they are valid, but any further API requests with them return an error, such as a person with ID 0.
578#[derive(Builder)]
579#[builder(derive(Into))]
580#[builder(start_fn(vis = "", name = "__builder_internal"))]
581pub struct TransactionsRequest {
582	#[builder(setters(vis = "", name = __kind_internal))]
583	kind: TransactionsRequestKind,
584	#[builder(into)]
585	sport_id: Option<SportId>,
586}
587
588impl TransactionsRequest {
589	pub fn for_team(team_id: impl Into<TeamId>) -> TransactionsRequestBuilder<transactions_request_builder::SetKind> {
590		Self::__builder_internal().__kind_internal(TransactionsRequestKind::Team(team_id.into()))
591	}
592
593	pub fn for_player(person_id: impl Into<PersonId>) -> TransactionsRequestBuilder<transactions_request_builder::SetKind> {
594		Self::__builder_internal().__kind_internal(TransactionsRequestKind::Player(person_id.into()))
595	}
596
597	pub fn for_ids(transactions: Vec<TransactionId>) -> TransactionsRequestBuilder<transactions_request_builder::SetKind> {
598		Self::__builder_internal().__kind_internal(TransactionsRequestKind::Transactions(transactions))
599	}
600
601	pub fn for_date_range(range: NaiveDateRange) -> TransactionsRequestBuilder<transactions_request_builder::SetKind> {
602		Self::__builder_internal().__kind_internal(TransactionsRequestKind::DateRange(range))
603	}
604}
605
606impl<S: transactions_request_builder::State + transactions_request_builder::IsComplete> crate::request::RequestURLBuilderExt for TransactionsRequestBuilder<S> {
607	type Built = TransactionsRequest;
608}
609
610impl Display for TransactionsRequest {
611	fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
612		write!(
613			f,
614			"http://statsapi.mlb.com/api/v1/transactions{}",
615			gen_params! {
616				self.kind,
617				"sportId"?: self.sport_id,
618			}
619		)
620	}
621}
622
623impl RequestURL for TransactionsRequest {
624	type Response = TransactionsResponse;
625}
626
627#[cfg(test)]
628mod tests {
629	use crate::person::players::PlayersRequest;
630	use crate::team::TeamsRequest;
631	use crate::transactions::TransactionsRequest;
632	use crate::TEST_YEAR;
633	use chrono::NaiveDate;
634	use crate::request::RequestURLBuilderExt;
635	use crate::sport::SportId;
636
637	#[tokio::test]
638	async fn parse_current_year() {
639		let _ = TransactionsRequest::for_date_range(NaiveDate::from_ymd_opt(TEST_YEAR.try_into().unwrap(), 1, 1).unwrap()..=NaiveDate::from_ymd_opt(TEST_YEAR.try_into().unwrap(), 12, 31).unwrap()).sport_id(SportId::MLB).build_and_get().await.unwrap();
640	}
641
642	#[tokio::test]
643	async fn test_single_transaction() {
644		let _ = TransactionsRequest::for_ids(vec![809_972.into()]).build_and_get().await.unwrap();
645	}
646
647	#[tokio::test]
648	async fn parse_sample_requests() {
649		let blue_jays = TeamsRequest::mlb_teams()
650			.season(TEST_YEAR)
651			.build_and_get()
652			.await
653			.unwrap()
654			.teams
655			.into_iter()
656			.find(|team| team.name.as_str() == "Toronto Blue Jays")
657			.unwrap();
658		let bo_bichette = PlayersRequest::for_sport(SportId::MLB)
659			.season(2024)
660			.build_and_get()
661			.await
662			.unwrap()
663			.people
664			.into_iter()
665			.find(|person| person.full_name == "Bo Bichette")
666			.unwrap();
667		
668		let response = TransactionsRequest::for_date_range(NaiveDate::from_ymd_opt(TEST_YEAR.try_into().unwrap(), 1, 1).unwrap()..=NaiveDate::from_ymd_opt(TEST_YEAR.try_into().unwrap(), 12, 31).unwrap()).build_and_get().await.unwrap();
669		let transaction_ids = response.transactions.into_iter().take(20).map(|transaction| transaction.id).collect::<Vec<_>>();
670		let _response = TransactionsRequest::for_team(blue_jays.id).build_and_get().await.unwrap();
671		let _response = TransactionsRequest::for_player(bo_bichette.id).build_and_get().await.unwrap();
672		let _response = TransactionsRequest::for_ids(transaction_ids).build_and_get().await.unwrap();
673	}
674}