mlb-api 1.0.3

Endpoints for MLB's public Statcast API.
Documentation
use serde::Deserialize;
use serde_json::Value;
use smallvec::SmallVec;
use thiserror::Error;
use crate::meta::StatGroup;
use crate::meta::StatType;
use crate::stats::Stat;

#[derive(Deserialize)]
#[doc(hidden)]
struct __RawStats {
	#[serde(alias = "stat")]
	stats: Vec<__RawStatEntry>,
}

#[derive(Deserialize)]
#[serde(untagged)]
#[doc(hidden)]
enum __RawStatEntry {
	Depth0(__Depth0StatEntry),
	Depth1(__Depth1StatEntry),
}

pub type __Depth0StatEntry = __ParsedStatEntry;

#[derive(Deserialize)]
#[doc(hidden)]
struct __Depth1StatEntry {
	splits: Vec<__InlineStatEntry>
}

#[derive(Deserialize)]
#[doc(hidden)]
struct __InlineStatEntry {
	#[serde(rename = "type")]
	stat_type: StatType,
	#[serde(rename = "group")]
	stat_group: StatGroup,
	stat: Value,
}

impl From<__InlineStatEntry> for __ParsedStatEntry {
	fn from(value: __InlineStatEntry) -> Self {
		Self {
			stat_type: value.stat_type,
			stat_group: value.stat_group,
			splits: SmallVec::from_buf::<1>([value.stat]),
		}
	}
}

impl From<__Depth1StatEntry> for Vec<__Depth0StatEntry> {
	fn from(value: __Depth1StatEntry) -> Self {
		value.splits.into_iter().map(Into::into).collect()
	}
}

impl From<__RawStatEntry> for Vec<__ParsedStatEntry> {
	fn from(value: __RawStatEntry) -> Self {
		match value {
			__RawStatEntry::Depth0(x) => vec![x],
			__RawStatEntry::Depth1(x) => x.into(),
		}
	}
}

impl From<__RawStats> for __ParsedStats {
	fn from(value: __RawStats) -> Self {
		let mut entries = Vec::with_capacity(value.stats.len());
		for entry in value.stats {
			match entry {
				__RawStatEntry::Depth0(entry) => entries.push(entry),
				__RawStatEntry::Depth1(entry) => {
					entries.reserve(entry.splits.len());
					for entry in entry.splits {
						entries.push(__ParsedStatEntry::from(entry));
					}
				},
			}
		}
		Self {
			entries
		}
	}
}

#[doc(hidden)]
#[derive(Deserialize, Debug)]
#[serde(from = "__RawStats")]
pub struct __ParsedStats {
	entries: Vec<__ParsedStatEntry>
}

#[doc(hidden)]
#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct __ParsedStatEntry {
	#[serde(rename = "type")]
	stat_type: StatType,
	#[serde(rename = "group")]
	stat_group: StatGroup,
	splits: SmallVec<Value, 1>,
}

#[doc(hidden)]
#[derive(Debug, Error)]
pub enum MakeStatSplitsError<S: Stat> {
	#[cfg(not(feature = "_debug"))]
	#[error("Failed to deserialize json split #{1} into split type ({name}): {0}", name = core::any::type_name::<S>())]
	FailedPartialDeserialize(serde_json::Error, usize),
	#[cfg(feature = "_debug")]
	#[error("Failed to deserialize json split #{1} into split type ({name}): {0}", name = core::any::type_name::<S>())]
	FailedPartialDeserialize(serde_path_to_error::Error<serde_json::Error>, usize),
	#[error("Failed to deserialize splits into greater split type ({name}): {0}", name = core::any::type_name::<S>())]
	FailedFullDeserialize(S::TryFromSplitError),
}

#[doc(hidden)]
pub fn make_stat_split<S: Stat>(stats: &mut __ParsedStats, target_stat_type_str: &'static str, target_stat_group: StatGroup) -> Result<S, MakeStatSplitsError<S>> {
	if let Some(idx) = stats.entries.iter().position(|entry| entry.stat_type.as_str().eq_ignore_ascii_case(target_stat_type_str) && entry.stat_group == target_stat_group) {
		let entry = stats.entries.remove(idx);
		let partially_deserialized = entry.splits
			.into_iter()
			.enumerate()
			.map(|(idx, split)| {
				#[cfg(not(feature = "_debug"))]
				return <<S as Stat>::Split as Deserialize>::deserialize(split).map_err(|e| (idx, e));
				#[cfg(feature = "_debug")]
				return serde_path_to_error::deserialize::<_, <S as Stat>::Split>(split).map_err(|e| (idx, e));
			})
			.collect::<Result<Vec<S::Split>, _>>()
			.map_err(|(idx, e)| MakeStatSplitsError::FailedPartialDeserialize(e, idx))?;
		let partially_deserialized_is_empty = partially_deserialized.is_empty();
		match <S as Stat>::from_splits(partially_deserialized.into_iter()) {
			Ok(s) => Ok(s),
			Err(_) if partially_deserialized_is_empty => Ok(S::default()),
			Err(e) => Err(MakeStatSplitsError::FailedFullDeserialize(e)),
		}
	} else {
		Ok(S::default())
	}
}