1use crate::person::{Person, PersonId};
2use crate::season::SeasonId;
3use crate::team::TeamId;
4use crate::types::{Copyright, Location};
5use crate::positions::PositionCode;
6use crate::request::RequestURL;
7use bon::Builder;
8use derive_more::Display;
9use serde::Deserialize;
10use std::fmt::{Display, Formatter};
11use thiserror::Error;
12use crate::team::NamedTeam;
13
14#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
15#[serde(rename_all = "camelCase")]
16pub struct DraftResponse {
17 pub copyright: Copyright,
18 pub drafts: DraftYear,
19}
20
21#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
22#[serde(rename_all = "camelCase")]
23pub struct DraftYear {
24 #[serde(rename = "draftYear")]
25 pub year: u32,
26 pub rounds: Vec<DraftRound>,
27}
28
29#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
30#[serde(rename_all = "camelCase")]
31pub struct DraftRound {
32 pub round: String,
33 pub picks: Vec<DraftPick>,
34}
35
36id!(EBISPersonId { id: u32 });
37
38#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
39#[serde(rename_all = "camelCase")]
40pub struct DraftProspectsResponse {
41 pub copyright: Copyright,
42 #[serde(rename = "totalSize")]
43 pub total_prospects: usize,
44 #[serde(rename = "returnedSize")]
45 pub returned_prospects: usize,
46 pub offset: usize,
47 pub prospects: Vec<DraftPick>,
48}
49
50#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
51#[serde(rename_all = "camelCase")]
52pub struct DraftPick {
53 #[serde(rename = "bisPlayerId")]
55 pub ebis_player_id: Option<EBISPersonId>,
56 #[serde(default, rename = "pickRound")]
57 pub round: String,
58 #[serde(default)]
59 pub pick_number: u32,
60 #[serde(rename = "displayPickNumber")]
61 pub displayed_pick_number: Option<u32>,
62 pub rank: Option<u32>,
63 #[serde(default, deserialize_with = "crate::types::try_from_str")]
64 pub signing_bonus: Option<u32>,
65 pub home: Location,
66 pub scouting_report_url: Option<String>,
67 pub school: School,
68 pub blurb: Option<String>,
69 #[serde(rename = "headshotLink", default = "get_default_headshot")]
70 pub headshot_url: String,
71 pub person: Option<Person>,
72 #[serde(default = "NamedTeam::unknown_team")]
73 pub team: NamedTeam,
74 pub draft_type: DraftType,
75 pub is_drafted: bool,
76 pub is_pass: bool,
77 pub year: SeasonId,
78}
79
80#[must_use]
81pub fn get_default_headshot() -> String {
82 "https://img.mlbstatic.com/mlb-photos/image/upload/d_people:generic:headshot:silo:current.png/w_120,q_auto:best/v1/people/0/headshot/draft/current".to_owned()
83}
84
85impl DraftPick {
86 #[must_use]
87 pub fn displayed_pick_number(&self) -> u32 {
88 self.displayed_pick_number.unwrap_or(self.pick_number)
89 }
90}
91
92#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
93#[serde(rename_all = "camelCase")]
94pub struct School {
95 pub name: Option<String>,
96 pub city: Option<String>,
97 pub class: Option<String>,
98 pub country: Option<String>,
99 pub state: Option<String>,
100}
101
102#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Display)]
103#[serde(try_from = "__DraftTypeStruct")]
104pub enum DraftType {
105 #[display("June Amateur Draft")]
106 JR,
107 JS,
109 NS,
111 NR,
113 AL,
115 RA,
117 RT,
119 JD,
121 AD,
123}
124
125#[derive(Deserialize)]
126#[doc(hidden)]
127struct __DraftTypeStruct {
128 code: String,
129}
130
131#[derive(Debug, Error)]
132enum DraftTypeParseError {
133 #[error("Invalid draft type code {0}")]
134 InvalidCode(String),
135}
136
137impl TryFrom<__DraftTypeStruct> for DraftType {
138 type Error = DraftTypeParseError;
139
140 fn try_from(value: __DraftTypeStruct) -> Result<Self, Self::Error> {
141 Ok(match &*value.code {
142 "JR" => Self::JR,
143 _ => return Err(DraftTypeParseError::InvalidCode(value.code)),
144 })
145 }
146}
147
148#[derive(Builder)]
149#[builder(start_fn = __latest)]
150pub struct DraftRequestLatest {
151 #[builder(into)]
153 year: Option<SeasonId>,
154}
155
156impl Display for DraftRequestLatest {
157 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
158 write!(f, "http://statsapi.mlb.com/api/v1/draft/{year}/latest", year = self.year.map_or(String::new(), |x| x.to_string()))
159 }
160}
161
162impl RequestURL for DraftRequestLatest {
163 type Response = DraftResponse;
164}
165
166#[derive(Builder)]
168#[builder(start_fn = regular)]
169#[builder(derive(Into))]
170pub struct DraftRequest {
171 #[builder(into)]
173 year: Option<SeasonId>,
174 #[builder(into)]
176 limit: Option<u32>,
177 #[builder(into)]
179 offset: Option<u32>,
180 #[builder(into)]
182 round: Option<u32>,
183
184 #[builder(into)]
186 drafted_only: Option<bool>,
187 #[builder(into)]
189 last_name: Option<char>,
190 #[builder(into)]
192 school: Option<char>,
193 #[builder(into)]
195 position: Option<PositionCode>,
196 #[builder(into)]
198 team_id: Option<TeamId>,
199 #[builder(into)]
201 home_country: Option<String>,
202 #[builder(into)]
204 player_id: Option<PersonId>,
205}
206
207impl<S: draft_request_builder::State + draft_request_builder::IsComplete> crate::request::RequestURLBuilderExt for DraftRequestBuilder<S> {
208 type Built = DraftRequest;
209}
210
211impl DraftRequest {
212 pub fn latest() -> DraftRequestLatestBuilder {
213 DraftRequestLatest::__latest()
214 }
215}
216
217impl Display for DraftRequest {
218 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
219 let Self {
220 year,
221 limit,
222 offset,
223 round,
224 drafted_only,
225 last_name,
226 school,
227 position,
228 team_id,
229 home_country,
230 player_id,
231 } = self;
232 write!(
233 f,
234 "http://statsapi.mlb.com/api/v1/draft/{year}{params}",
235 year = year.map_or(String::new(), |x| x.to_string()),
236 params = gen_params! {
237 "limit"?: limit,
238 "offset"?: offset,
239 "round"?: round,
240 "drafted"?: drafted_only,
241 "name"?: last_name,
242 "school"?: school,
243 "position"?: position,
244 "teamId"?: team_id,
245 "homeCountry"?: home_country,
246 "playerId"?: player_id,
247 }
248 )
249 }
250}
251
252impl RequestURL for DraftRequest {
253 type Response = DraftResponse;
254}
255
256#[derive(Builder)]
258#[builder(start_fn = regular)]
259#[builder(derive(Into))]
260pub struct DraftProspectsRequest {
261 #[builder(into)]
263 year: Option<SeasonId>,
264 #[builder(into)]
266 limit: Option<u32>,
267 #[builder(into)]
269 offset: Option<u32>,
270 #[builder(into)]
272 round: Option<u32>,
273
274 #[builder(into)]
276 drafted_only: Option<bool>,
277 #[builder(into)]
279 last_name: Option<char>,
280 #[builder(into)]
282 school: Option<char>,
283 #[builder(into)]
285 position: Option<PositionCode>,
286 #[builder(into)]
288 team_id: Option<TeamId>,
289 #[builder(into)]
291 home_country: Option<String>,
292 #[builder(into)]
294 player_id: Option<PersonId>,
295}
296
297impl<S: draft_prospects_request_builder::State + draft_prospects_request_builder::IsComplete> crate::request::RequestURLBuilderExt for DraftProspectsRequestBuilder<S> {
298 type Built = DraftProspectsRequest;
299}
300
301impl Display for DraftProspectsRequest {
302 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
303 let Self {
304 year,
305 limit,
306 offset,
307 round,
308 drafted_only,
309 last_name,
310 school,
311 position,
312 team_id,
313 home_country,
314 player_id,
315 } = self;
316 write!(
317 f,
318 "http://statsapi.mlb.com/api/v1/draft/prospects/{year}{params}",
319 year = year.map_or(String::new(), |x| x.to_string()),
320 params = gen_params! {
321 "limit"?: limit,
322 "offset"?: offset,
323 "round"?: round,
324 "drafted"?: drafted_only,
325 "name"?: last_name,
326 "school"?: school,
327 "position"?: position,
328 "teamId"?: team_id,
329 "homeCountry"?: home_country,
330 "playerId"?: player_id,
331 }
332 )
333 }
334}
335
336impl RequestURL for DraftProspectsRequest {
337 type Response = DraftProspectsResponse;
338}
339
340#[cfg(test)]
341mod tests {
342 use crate::draft::{DraftProspectsRequest, DraftRequest};
343 use crate::request::RequestURLBuilderExt;
344 use crate::TEST_YEAR;
345
346 #[tokio::test]
347 async fn draft_test_year() {
348 let _ = DraftRequest::regular().year(TEST_YEAR).build_and_get().await.unwrap();
349 let _ = DraftProspectsRequest::regular().year(TEST_YEAR).build_and_get().await.unwrap();
350 }
351
352 #[tokio::test]
353 #[cfg_attr(not(feature = "_heavy_tests"), ignore)]
354 async fn draft_all_years() {
355 for year in 1965..=TEST_YEAR {
356 let _ = DraftRequest::regular().year(year).build_and_get().await.unwrap();
357 let _ = DraftProspectsRequest::regular().year(year).build_and_get().await.unwrap();
358 }
359 }
360}