use std::fmt;
use std::str::FromStr;
macro_rules! element_kinds {
($($variant:ident => $name:literal),+ $(,)?) => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ElementKind {
$($variant),+
}
impl ElementKind {
pub const ALL: &'static [ElementKind] = &[$(ElementKind::$variant),+];
pub fn as_str(self) -> &'static str {
match self {
$(ElementKind::$variant => $name),+
}
}
}
impl FromStr for ElementKind {
type Err = ParseElementKindError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
$($name => ElementKind::$variant,)+
_ => return Err(ParseElementKindError),
})
}
}
};
}
element_kinds! {
AudioTerm => "audio_term",
Device => "device",
Episode => "episode",
EpisodeTitle => "episode_title",
FileChecksum => "file_checksum",
FileExtension => "file_extension",
Language => "language",
Other => "other",
Part => "part",
ReleaseGroup => "release_group",
ReleaseInformation => "release_information",
ReleaseVersion => "release_version",
Season => "season",
Source => "source",
Subtitles => "subtitles",
Title => "title",
Type => "type",
VideoResolution => "video_resolution",
VideoTerm => "video_term",
Volume => "volume",
Year => "year",
}
impl fmt::Display for ElementKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ParseElementKindError;
impl fmt::Display for ParseElementKindError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("not a valid ElementKind")
}
}
impl std::error::Error for ParseElementKindError {}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Element {
pub kind: ElementKind,
pub value: String,
pub position: usize,
}
#[cfg(feature = "serde")]
impl serde::Serialize for ElementKind {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_str())
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for ElementKind {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = <&str as serde::Deserialize>::deserialize(d)?;
s.parse()
.map_err(|_| serde::de::Error::unknown_variant(s, &[]))
}
}
#[cfg(all(test, feature = "serde"))]
#[allow(clippy::unwrap_used)]
mod serde_tests {
use crate::{Element, ElementKind, Options};
#[test]
fn element_kind_wire_name_matches_as_str() {
for &kind in ElementKind::ALL {
let json = serde_json::to_string(&kind).unwrap();
assert_eq!(json, format!("\"{}\"", kind.as_str()));
assert_eq!(serde_json::from_str::<ElementKind>(&json).unwrap(), kind);
}
}
#[test]
fn element_kind_rejects_unknown() {
assert!(serde_json::from_str::<ElementKind>("\"nope\"").is_err());
}
#[test]
fn element_and_options_round_trip() {
let elements = crate::parse("[Grp] Show - 05 [1080p].mkv", Options::default());
let json = serde_json::to_string(&elements).unwrap();
assert_eq!(
serde_json::from_str::<Vec<Element>>(&json).unwrap(),
elements
);
let opts = Options {
parse_title: false,
..Default::default()
};
let back: Options = serde_json::from_str(&serde_json::to_string(&opts).unwrap()).unwrap();
assert_eq!(back, opts);
let partial: Options = serde_json::from_str(r#"{"parse_title":false}"#).unwrap();
assert_eq!(partial, opts);
}
}