mlb_api/
types.rs

1use std::fmt::Debug;
2use std::num::ParseIntError;
3use std::str::FromStr;
4use chrono::{Datelike, Local, NaiveDate};
5use serde::{Deserialize, Deserializer};
6use serde::de::Error;
7use thiserror::Error;
8
9/// Shared types across multiple endpoints
10#[derive(Debug, Deserialize, PartialEq, Eq, Clone)]
11pub struct Copyright(pub String);
12
13impl Default for Copyright {
14    fn default() -> Self {
15        let year = Local::now().year();
16        Self(format!("Copyright {year} MLB Advanced Media, L.P.  Use of any content on this page acknowledges agreement to the terms posted here http://gdx.mlb.com/components/copyright.txt"))
17    }
18}
19
20#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
21#[serde(try_from = "PositionStruct")]
22pub enum Position {
23    Unknown = 0,
24    
25    Pitcher = 1,
26    Catcher = 2,
27    FirstBaseman = 3,
28    SecondBaseman = 4,
29    ThirdBaseman = 5,
30    Shortstop = 6,
31    Leftfielder = 7,
32    Centerfielder = 8,
33    Rightfielder = 9,
34    
35    Outfielder = b'0' as _, // Typically seen as primary position
36    DesignatedHitter = 10,
37    PinchHitter = 11,
38    PinchRunner = 12,
39    TwoWayPlayer = b'Y' as _,
40}
41
42#[derive(Deserialize)]
43struct PositionStruct {
44    code: String,
45    abbreviation: String,
46}
47
48#[derive(Debug, Error)]
49pub enum PositionParseError {
50    #[error("Invlaid player position '{abbreviation}' (code = {code})")]
51    InvalidPosition {
52        code: String,
53        abbreviation: String,
54    }
55}
56
57impl TryFrom<PositionStruct> for Position {
58    type Error = PositionParseError;
59
60    fn try_from(value: PositionStruct) -> Result<Self, Self::Error> {
61        Ok(match &*value.code {
62            "X" => Self::Unknown,
63            "P" => Self::Pitcher,
64            "C" => Self::Catcher,
65            "1B" => Self::FirstBaseman,
66            "2B" => Self::SecondBaseman,
67            "3B" => Self::ThirdBaseman,
68            "SS" => Self::Shortstop,
69            "LF" => Self::Leftfielder,
70            "CF" => Self::Centerfielder,
71            "RF" => Self::Rightfielder,
72            "OF" => Self::Outfielder,
73            "DH" => Self::DesignatedHitter,
74            "PH" => Self::PinchHitter,
75            "PR" => Self::PinchRunner,
76            "TWP" => Self::TwoWayPlayer,
77            _ => return Err(PositionParseError::InvalidPosition { code: value.code, abbreviation: value.abbreviation }),
78        })
79    }
80}
81
82pub fn try_from_str<'de, D: Deserializer<'de>, T: FromStr>(deserializer: D) -> Result<Option<T>, D::Error> {
83    Ok(String::deserialize(deserializer)?.parse::<T>().ok())
84}
85
86pub fn from_str<'de, D: Deserializer<'de>, T: FromStr>(deserializer: D) -> Result<T, D::Error> where <T as FromStr>::Err: Debug {
87    String::deserialize(deserializer)?.parse::<T>().map_err(|e| Error::custom(format!("{e:?}")))
88}
89
90pub fn from_yes_no<'de, D: Deserializer<'de>>(deserializer: D) -> Result<bool, D::Error> {
91    #[derive(Deserialize)]
92    enum Boolean {
93        #[serde(rename = "Y")] Yes,
94        #[serde(rename = "N")] No,
95    }
96    
97    Ok(match Boolean::deserialize(deserializer)? {
98        Boolean::Yes => true,
99        Boolean::No => false,
100    })
101}
102
103#[derive(Debug, PartialEq, Eq, Copy, Clone)]
104pub enum HeightMeasurement {
105    FeetAndInches {
106        feet: u8,
107        inches: u8,
108    },
109    Centimeters {
110        cm: u16,
111    }
112}
113
114impl FromStr for HeightMeasurement {
115    type Err = HeightMeasurementParseError;
116
117    fn from_str(s: &str) -> Result<Self, Self::Err> {
118        match s.split_once("' ").map(|(feet, rest)| (feet, rest.split_once(r#"""#))) {
119            Some((feet, Some((inches, "")))) => {
120                let feet = feet.parse::<u8>()?;
121                let inches = inches.parse::<u8>()?;
122                Ok(Self::FeetAndInches { feet, inches })
123            },
124            _ => {
125                match s.split_once("cm") {
126                    Some((cm, "")) => {
127                        let cm = cm.parse::<u16>()?;
128                        Ok(Self::Centimeters { cm })
129                    },
130                    _ => Err(HeightMeasurementParseError::UnknownSpec(s.to_owned())),
131                }
132            }
133        }
134    }
135}
136
137#[derive(Debug, Error)]
138pub enum HeightMeasurementParseError {
139    #[error(transparent)]
140    ParseIntError(#[from] ParseIntError),
141    #[error("Unknown height '{0}'")]
142    UnknownSpec(String),
143}
144
145#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
146pub enum Gender {
147    #[serde(rename = "M")]
148    Male,
149    #[serde(rename = "F")]
150    Female,
151    #[serde(other)]
152    Other,
153}
154
155#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
156#[serde(try_from = "HandednessStruct")]
157pub enum Handedness {
158    Left,
159    Right,
160}
161
162#[derive(Deserialize)]
163struct HandednessStruct {
164    code: String,
165}
166
167#[derive(Debug, Error)]
168pub enum HandednessParseError {
169    #[error("Invalid handedness '{0}'")]
170    InvalidHandedness(String),
171}
172
173impl TryFrom<HandednessStruct> for Handedness {
174    type Error = HandednessParseError;
175
176    fn try_from(value: HandednessStruct) -> Result<Self, Self::Error> {
177        Ok(match &*value.code {
178            "L" => Self::Left,
179            "R" => Self::Right,
180            _ => return Err(HandednessParseError::InvalidHandedness(value.code)),
181        })
182    }
183}
184
185#[derive(Debug, Deserialize, PartialEq, Eq, Copy, Clone)]
186pub enum GameType {
187    #[serde(rename = "R")]
188    RegularSeason,
189    #[serde(rename = "S")]
190    SpringTraining,
191    #[serde(rename = "E")]
192    Exhibition,
193    #[serde(rename = "A")]
194    AllStarGame,
195    #[serde(rename = "D")]
196    DivisionalSeries,
197    #[serde(rename = "F")]
198    WildCardSeries,
199    #[serde(rename = "L")]
200    ChampionshipSeries,
201    #[serde(rename = "W")]
202    WorldSeries,
203}
204
205pub type NaiveDateRange = std::ops::RangeInclusive<NaiveDate>;