1use crate::person::{Person, PersonId};
4use crate::season::SeasonId;
5use crate::team::TeamId;
6use crate::{Copyright, Location};
7use crate::meta::PositionCode;
8use crate::request::RequestURL;
9use bon::Builder;
10use derive_more::Display;
11use serde::Deserialize;
12use std::fmt::{Display, Formatter};
13use thiserror::Error;
14use crate::team::NamedTeam;
15
16#[derive(Debug, Deserialize, PartialEq, Clone)]
18#[serde(rename_all = "camelCase")]
19pub struct DraftResponse {
20 pub copyright: Copyright,
21 pub drafts: DraftYear,
22}
23
24#[derive(Debug, Deserialize, PartialEq, Clone)]
26#[serde(rename_all = "camelCase")]
27pub struct DraftYear {
28 #[serde(rename = "draftYear")]
29 pub year: u32,
30 pub rounds: Vec<DraftRound>,
31}
32
33#[derive(Debug, Deserialize, PartialEq, Clone)]
41#[serde(rename_all = "camelCase")]
42pub struct DraftRound {
43 pub round: String,
44 pub picks: Vec<DraftPick>,
45}
46
47id!(#[doc = "Different from [`PersonId`](crate::person::PersonId).\n\nInternal eBIS person id, I'd be surprised if you had a use for this."] #[allow(non_camel_case_types)] eBISPersonId { id: u32 });
48
49#[derive(Debug, Deserialize, PartialEq, Clone)]
51#[serde(rename_all = "camelCase")]
52pub struct DraftProspectsResponse {
53 pub copyright: Copyright,
54 #[serde(rename = "totalSize")]
55 pub total_prospects: usize,
56 #[serde(rename = "returnedSize")]
57 pub returned_prospects: usize,
58 pub offset: usize,
59 pub prospects: Vec<DraftPick>,
60}
61
62#[derive(Debug, Deserialize, PartialEq, Clone)]
64#[serde(rename_all = "camelCase")]
65pub struct DraftPick {
66 #[serde(rename = "bisPlayerId")]
68 pub ebis_player_id: Option<eBISPersonId>,
69 #[serde(default, rename = "pickRound")]
70 pub round: String,
71 #[serde(default)]
72 pub pick_number: u32,
73 #[serde(rename = "displayPickNumber")]
74 pub displayed_pick_number: Option<u32>,
75 pub rank: Option<u32>,
76 #[serde(default, deserialize_with = "crate::try_from_str")]
77 pub signing_bonus: Option<u32>,
78 pub home: Location,
79 pub scouting_report_url: Option<String>,
80 pub school: School,
81 pub blurb: Option<String>,
82 #[serde(rename = "headshotLink", default = "get_default_headshot")]
83 pub headshot_url: String,
84 pub person: Option<Person>,
85 #[serde(default = "NamedTeam::unknown_team")]
86 pub team: NamedTeam,
87 pub draft_type: DraftType,
88 pub is_drafted: bool,
89 pub is_pass: bool,
90 pub year: SeasonId,
91}
92
93#[must_use]
94fn get_default_headshot() -> String {
95 "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()
96}
97
98impl DraftPick {
99 #[must_use]
100 pub fn displayed_pick_number(&self) -> u32 {
101 self.displayed_pick_number.unwrap_or(self.pick_number)
102 }
103}
104
105#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
106#[serde(rename_all = "camelCase")]
107pub struct School {
108 pub name: Option<String>,
109 pub city: Option<String>,
110 pub class: Option<String>,
111 pub country: Option<String>,
112 pub state: Option<String>,
113}
114
115#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone, Display)]
116#[serde(try_from = "__DraftTypeStruct")]
117pub enum DraftType {
118 #[display("June Amateur Draft")]
119 JR,
120 JS,
122 NS,
124 NR,
126 AL,
128 RA,
130 RT,
132 JD,
134 AD,
136}
137
138#[derive(Deserialize)]
139#[doc(hidden)]
140struct __DraftTypeStruct {
141 code: String,
142}
143
144#[derive(Debug, Error)]
145enum DraftTypeParseError {
146 #[error("Invalid draft type code {0}")]
147 InvalidCode(String),
148}
149
150impl TryFrom<__DraftTypeStruct> for DraftType {
151 type Error = DraftTypeParseError;
152
153 fn try_from(value: __DraftTypeStruct) -> Result<Self, Self::Error> {
154 Ok(match &*value.code {
155 "JR" => Self::JR,
156 _ => return Err(DraftTypeParseError::InvalidCode(value.code)),
157 })
158 }
159}
160
161#[derive(Builder)]
163#[builder(start_fn = __latest)]
164pub struct DraftRequestLatest {
165 #[builder(into)]
167 year: Option<SeasonId>,
168}
169
170impl Display for DraftRequestLatest {
171 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
172 write!(f, "http://statsapi.mlb.com/api/v1/draft/{year}/latest", year = self.year.map_or(String::new(), |x| x.to_string()))
173 }
174}
175
176impl RequestURL for DraftRequestLatest {
177 type Response = DraftResponse;
178}
179
180#[derive(Builder)]
184#[builder(start_fn = regular)]
185#[builder(derive(Into))]
186pub struct DraftRequest {
187 #[builder(into)]
189 year: Option<SeasonId>,
190 #[builder(into)]
192 limit: Option<u32>,
193 #[builder(into)]
195 offset: Option<u32>,
196 #[builder(into)]
198 round: Option<u32>,
199
200 #[builder(into)]
202 drafted_only: Option<bool>,
203 #[builder(into)]
205 last_name: Option<char>,
206 #[builder(into)]
208 school: Option<char>,
209 #[builder(into)]
211 position: Option<PositionCode>,
212 #[builder(into)]
214 team_id: Option<TeamId>,
215 #[builder(into)]
217 home_country: Option<String>,
218 #[builder(into)]
220 player_id: Option<PersonId>,
221}
222
223impl<S: draft_request_builder::State + draft_request_builder::IsComplete> crate::request::RequestURLBuilderExt for DraftRequestBuilder<S> {
224 type Built = DraftRequest;
225}
226
227impl DraftRequest {
228 pub fn latest() -> DraftRequestLatestBuilder {
229 DraftRequestLatest::__latest()
230 }
231}
232
233impl Display for DraftRequest {
234 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
235 let Self {
236 year,
237 limit,
238 offset,
239 round,
240 drafted_only,
241 last_name,
242 school,
243 position,
244 team_id,
245 home_country,
246 player_id,
247 } = self;
248 write!(
249 f,
250 "http://statsapi.mlb.com/api/v1/draft/{year}{params}",
251 year = year.map_or(String::new(), |x| x.to_string()),
252 params = gen_params! {
253 "limit"?: limit,
254 "offset"?: offset,
255 "round"?: round,
256 "drafted"?: drafted_only,
257 "name"?: last_name,
258 "school"?: school,
259 "position"?: position,
260 "teamId"?: team_id,
261 "homeCountry"?: home_country,
262 "playerId"?: player_id,
263 }
264 )
265 }
266}
267
268impl RequestURL for DraftRequest {
269 type Response = DraftResponse;
270}
271
272#[derive(Builder)]
276#[builder(start_fn = regular)]
277#[builder(derive(Into))]
278pub struct DraftProspectsRequest {
279 #[builder(into)]
281 year: Option<SeasonId>,
282 #[builder(into)]
284 limit: Option<u32>,
285 #[builder(into)]
287 offset: Option<u32>,
288 #[builder(into)]
290 round: Option<u32>,
291
292 #[builder(into)]
294 drafted_only: Option<bool>,
295 #[builder(into)]
297 last_name: Option<char>,
298 #[builder(into)]
300 school: Option<char>,
301 #[builder(into)]
303 position: Option<PositionCode>,
304 #[builder(into)]
306 team_id: Option<TeamId>,
307 #[builder(into)]
309 home_country: Option<String>,
310 #[builder(into)]
312 player_id: Option<PersonId>,
313}
314
315impl<S: draft_prospects_request_builder::State + draft_prospects_request_builder::IsComplete> crate::request::RequestURLBuilderExt for DraftProspectsRequestBuilder<S> {
316 type Built = DraftProspectsRequest;
317}
318
319impl Display for DraftProspectsRequest {
320 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
321 let Self {
322 year,
323 limit,
324 offset,
325 round,
326 drafted_only,
327 last_name,
328 school,
329 position,
330 team_id,
331 home_country,
332 player_id,
333 } = self;
334 write!(
335 f,
336 "http://statsapi.mlb.com/api/v1/draft/prospects/{year}{params}",
337 year = year.map_or(String::new(), |x| x.to_string()),
338 params = gen_params! {
339 "limit"?: limit,
340 "offset"?: offset,
341 "round"?: round,
342 "drafted"?: drafted_only,
343 "name"?: last_name,
344 "school"?: school,
345 "position"?: position,
346 "teamId"?: team_id,
347 "homeCountry"?: home_country,
348 "playerId"?: player_id,
349 }
350 )
351 }
352}
353
354impl RequestURL for DraftProspectsRequest {
355 type Response = DraftProspectsResponse;
356}
357
358#[cfg(test)]
359mod tests {
360 use crate::draft::{DraftProspectsRequest, DraftRequest};
361 use crate::request::RequestURLBuilderExt;
362 use crate::TEST_YEAR;
363
364 #[tokio::test]
365 async fn draft_test_year() {
366 let _ = DraftRequest::regular().year(TEST_YEAR).build_and_get().await.unwrap();
367 let _ = DraftProspectsRequest::regular().year(TEST_YEAR).build_and_get().await.unwrap();
368 }
369
370 #[tokio::test]
371 #[cfg_attr(not(feature = "_heavy_tests"), ignore)]
372 async fn draft_all_years() {
373 for year in 1965..=TEST_YEAR {
374 let _ = DraftRequest::regular().year(year).build_and_get().await.unwrap();
375 let _ = DraftProspectsRequest::regular().year(year).build_and_get().await.unwrap();
376 }
377 }
378}