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#[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 _, 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>;