Skip to main content

mlb_api/requests/
transactions.rs

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