Skip to main content

mlb_api/requests/
draft.rs

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	/// a `PlayerId` on the EBIS System
54	#[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	/// Never appears
108	JS,
109	/// Never appears
110	NS,
111	/// Never appears
112	NR,
113	/// Never appears
114	AL,
115	/// Never appears
116	RA,
117	/// Never appears
118	RT,
119	/// Never appears
120	JD,
121	/// Never appears
122	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	/// Year of the draft.
152	#[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/// This request sorts into rounds
167#[derive(Builder)]
168#[builder(start_fn = regular)]
169#[builder(derive(Into))]
170pub struct DraftRequest {
171	/// Year of the draft.
172	#[builder(into)]
173	year: Option<SeasonId>,
174	/// Number of results to return.
175	#[builder(into)]
176	limit: Option<u32>,
177	/// Offset in the results (used for pagination).
178	#[builder(into)]
179	offset: Option<u32>,
180	/// Draft round.
181	#[builder(into)]
182	round: Option<u32>,
183
184	/// Include only successfully drafted players
185	#[builder(into)]
186	drafted_only: Option<bool>,
187	/// Filter players by the first character of their last name.
188	#[builder(into)]
189	last_name: Option<char>,
190	/// Filter players by the first character of their school they were drafted from.
191	#[builder(into)]
192	school: Option<char>,
193	/// Filter players by their position.
194	#[builder(into)]
195	position: Option<PositionCode>,
196	/// Filter players by the team they were drafted by.
197	#[builder(into)]
198	team_id: Option<TeamId>,
199	/// Filter players by their home country.
200	#[builder(into)]
201	home_country: Option<String>,
202	/// Filter for a specific player id.
203	#[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/// This request gives a list of prospects.
257#[derive(Builder)]
258#[builder(start_fn = regular)]
259#[builder(derive(Into))]
260pub struct DraftProspectsRequest {
261	/// Year of the draft.
262	#[builder(into)]
263	year: Option<SeasonId>,
264	/// Number of results to return.
265	#[builder(into)]
266	limit: Option<u32>,
267	/// Offset in the results (used for pagination).
268	#[builder(into)]
269	offset: Option<u32>,
270	/// Draft round.
271	#[builder(into)]
272	round: Option<u32>,
273
274	/// Include only successfully drafted players
275	#[builder(into)]
276	drafted_only: Option<bool>,
277	/// Filter players by the first character of their last name.
278	#[builder(into)]
279	last_name: Option<char>,
280	/// Filter players by the first character of their school they were drafted from.
281	#[builder(into)]
282	school: Option<char>,
283	/// Filter players by their position.
284	#[builder(into)]
285	position: Option<PositionCode>,
286	/// Filter players by the team they were drafted by.
287	#[builder(into)]
288	team_id: Option<TeamId>,
289	/// Filter players by their home country.
290	#[builder(into)]
291	home_country: Option<String>,
292	/// Filter for a specific player id.
293	#[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}