1use crate::person::NamedPerson;
2use crate::season::SeasonId;
3use crate::team::TeamId;
4use crate::types::{Copyright, MLB_API_DATE_FORMAT};
5use bon::Builder;
6use chrono::NaiveDate;
7use serde::Deserialize;
8use std::fmt::{Display, Formatter};
9use crate::positions::NamedPosition;
10use crate::request::RequestURL;
11use crate::roster_types::RosterType;
12use crate::team::NamedTeam;
13
14#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
15#[serde(rename_all = "camelCase")]
16pub struct RosterResponse {
17 pub copyright: Copyright,
18 #[serde(default)]
19 pub roster: Vec<RosterPlayer>,
20 pub team_id: TeamId,
21 pub roster_type: RosterType,
22}
23
24#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
25#[serde(rename_all = "camelCase")]
26pub struct RosterPlayer {
27 pub person: NamedPerson,
28 #[serde(deserialize_with = "crate::types::try_from_str")]
29 pub jersey_number: Option<u8>,
30 pub position: NamedPosition,
31 pub status: RosterStatus,
32 pub parent_team_id: Option<TeamId>,
33}
34
35#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
36#[serde(try_from = "__RosterStatusStruct")]
37pub enum RosterStatus {
38 Active,
39 Claimed,
40 ReassignedToMinors,
41 Released,
42 MinorLeagueContract,
43 InjuryLeave7Day,
44 InjuryLeave10Day,
45 InjuryLeave15Day,
46 InjuryLeave60Day,
47 Traded,
48 DesignatedForAssignment,
49 FreeAgent,
50 RestrictedList,
51 AssignedToNewTeam,
52 RehabAssignment,
53 NonRosterInvitee,
54 Waived,
55 Deceased,
56 VoluntarilyRetired,
57}
58
59#[derive(Deserialize)]
60#[doc(hidden)]
61struct __RosterStatusStruct {
62 code: String,
63 description: String,
64}
65
66impl TryFrom<__RosterStatusStruct> for RosterStatus {
67 type Error = String;
68
69 fn try_from(value: __RosterStatusStruct) -> Result<Self, Self::Error> {
70 Ok(match &*value.code {
71 "A" => Self::Active,
72 "CL" => Self::Claimed,
73 "RM" => Self::ReassignedToMinors,
74 "RL" => Self::Released,
75 "MIN" => Self::MinorLeagueContract,
76 "D7" => Self::InjuryLeave7Day,
77 "D10" => Self::InjuryLeave10Day,
78 "D15" => Self::InjuryLeave15Day,
79 "D60" => Self::InjuryLeave60Day,
80 "TR" => Self::Traded,
81 "DES" => Self::DesignatedForAssignment,
82 "FA" => Self::FreeAgent,
83 "RST" => Self::RestrictedList,
84 "ASG" => Self::AssignedToNewTeam,
85 "RA" => Self::RehabAssignment,
86 "NRI" => Self::NonRosterInvitee,
87 "WA" => Self::Waived,
88 "DEC" => Self::Deceased,
89 "RET" => Self::VoluntarilyRetired,
90 code => return Err(format!("Invalid code '{code}' (desc: {})", value.description)),
91 })
92 }
93}
94
95#[derive(Builder)]
96#[builder(derive(Into))]
97pub struct RosterRequest {
98 #[builder(into)]
99 team_id: TeamId,
100 #[builder(into)]
101 season: Option<SeasonId>,
102 date: Option<NaiveDate>,
103 #[builder(into)]
104 roster_type: RosterType,
105}
106
107impl<S: roster_request_builder::State + roster_request_builder::IsComplete> crate::request::RequestURLBuilderExt for RosterRequestBuilder<S> {
108 type Built = RosterRequest;
109}
110
111impl Display for RosterRequest {
112 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
113 write!(f, "http://statsapi.mlb.com/api/v1/teams/{}/roster{}", self.team_id, gen_params! { "season"?: self.season, "date"?: self.date.as_ref().map(|date| date.format(MLB_API_DATE_FORMAT)), "rosterType": &self.roster_type })
114 }
115}
116
117impl RequestURL for RosterRequest {
118 type Response = RosterResponse;
119}
120
121#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
122#[serde(rename_all = "camelCase")]
123pub struct RosterEntry {
124 pub position: NamedPosition,
125 pub status: RosterStatus,
126 pub team: NamedTeam,
127 pub is_active: bool,
128 pub is_active_forty_man: bool,
129 pub start_date: NaiveDate,
130 pub end_date: Option<NaiveDate>,
131 pub status_date: NaiveDate,
132}
133
134#[cfg(test)]
135mod tests {
136 use crate::meta::MetaRequest;
137 use crate::request::{RequestURL, RequestURLBuilderExt};
138 use crate::roster_types::RosterType;
139 use crate::team::roster::RosterRequest;
140 use crate::team::teams::TeamsRequest;
141 use crate::TEST_YEAR;
142
143 #[tokio::test]
144 #[cfg_attr(not(feature = "_heavy_tests"), ignore)]
145 async fn test_this_year_all_mlb_teams_all_roster_types() {
146 let season = TEST_YEAR;
147 let teams = TeamsRequest::mlb_teams().season(season).build_and_get().await.unwrap().teams;
148 let roster_types = MetaRequest::<RosterType>::new().get().await.unwrap().entries;
149 for team in teams {
150 for roster_type in &roster_types {
151 let _ = RosterRequest::builder().team_id(team.id).season(season).roster_type(*roster_type).build_and_get().await.unwrap();
152 }
153 }
154 }
155}
156