ferrex-model 0.1.2-alpha

Shared data models for the Ferrex media platform
Documentation
use std::{fmt, str::FromStr};

use crate::media_events::MediaEvent;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ScanSseEventType {
    Started,
    Progress,
    Quiescing,
    Completed,
    Failed,
}

impl ScanSseEventType {
    pub const fn event_name(self) -> &'static str {
        match self {
            Self::Started => "scan.started",
            Self::Progress => "scan.progress",
            Self::Quiescing => "scan.quiescing",
            Self::Completed => "scan.completed",
            Self::Failed => "scan.failed",
        }
    }
}

impl fmt::Display for ScanSseEventType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.event_name())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseScanSseEventTypeError {
    invalid_value: String,
}

impl ParseScanSseEventTypeError {
    pub fn new(value: &str) -> Self {
        Self {
            invalid_value: value.to_string(),
        }
    }
}

impl fmt::Display for ParseScanSseEventTypeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "invalid scan SSE event type: {}", self.invalid_value)
    }
}

impl std::error::Error for ParseScanSseEventTypeError {}

impl FromStr for ScanSseEventType {
    type Err = ParseScanSseEventTypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "scan.started" => Ok(Self::Started),
            "scan.progress" => Ok(Self::Progress),
            "scan.quiescing" => Ok(Self::Quiescing),
            "scan.completed" => Ok(Self::Completed),
            "scan.failed" => Ok(Self::Failed),
            other => Err(ParseScanSseEventTypeError::new(other)),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum MediaSseEventType {
    MovieAdded,
    MovieBatchFinalized,
    SeriesAdded,
    SeriesBundleFinalized,
    SeasonAdded,
    EpisodeAdded,
    MovieUpdated,
    SeriesUpdated,
    SeasonUpdated,
    EpisodeUpdated,
    MediaDeleted,
    Scan(ScanSseEventType),
}

impl MediaSseEventType {
    pub const fn event_name(self) -> &'static str {
        match self {
            Self::MovieAdded => "media.movie_added",
            Self::MovieBatchFinalized => "media.movie_batch_finalized",
            Self::SeriesAdded => "media.series_added",
            Self::SeriesBundleFinalized => "media.series_bundle_finalized",
            Self::SeasonAdded => "media.season_added",
            Self::EpisodeAdded => "media.episode_added",
            Self::MovieUpdated => "media.movie_updated",
            Self::SeriesUpdated => "media.series_updated",
            Self::SeasonUpdated => "media.season_updated",
            Self::EpisodeUpdated => "media.episode_updated",
            Self::MediaDeleted => "media.deleted",
            Self::Scan(kind) => kind.event_name(),
        }
    }
}

impl fmt::Display for MediaSseEventType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.event_name())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseMediaSseEventTypeError {
    invalid_value: String,
}

impl ParseMediaSseEventTypeError {
    pub fn new(value: &str) -> Self {
        Self {
            invalid_value: value.to_string(),
        }
    }
}

impl fmt::Display for ParseMediaSseEventTypeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "invalid media SSE event type: {}", self.invalid_value)
    }
}

impl std::error::Error for ParseMediaSseEventTypeError {}

impl FromStr for MediaSseEventType {
    type Err = ParseMediaSseEventTypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "media.movie_added" => Ok(Self::MovieAdded),
            "media.movie_batch_finalized" => Ok(Self::MovieBatchFinalized),
            "media.series_added" => Ok(Self::SeriesAdded),
            "media.series_bundle_finalized" => Ok(Self::SeriesBundleFinalized),
            "media.season_added" => Ok(Self::SeasonAdded),
            "media.episode_added" => Ok(Self::EpisodeAdded),
            "media.movie_updated" => Ok(Self::MovieUpdated),
            "media.series_updated" => Ok(Self::SeriesUpdated),
            "media.season_updated" => Ok(Self::SeasonUpdated),
            "media.episode_updated" => Ok(Self::EpisodeUpdated),
            "media.deleted" => Ok(Self::MediaDeleted),
            other => match ScanSseEventType::from_str(other) {
                Ok(kind) => Ok(Self::Scan(kind)),
                Err(_) => Err(ParseMediaSseEventTypeError::new(other)),
            },
        }
    }
}

impl MediaEvent {
    pub fn sse_event_type(&self) -> MediaSseEventType {
        match self {
            MediaEvent::MovieAdded { .. } => MediaSseEventType::MovieAdded,
            MediaEvent::MovieBatchFinalized { .. } => {
                MediaSseEventType::MovieBatchFinalized
            }
            MediaEvent::SeriesBundleFinalized { .. } => {
                MediaSseEventType::SeriesBundleFinalized
            }
            MediaEvent::SeriesAdded { .. } => MediaSseEventType::SeriesAdded,
            MediaEvent::MovieUpdated { .. } => MediaSseEventType::MovieUpdated,
            MediaEvent::SeriesUpdated { .. } => {
                MediaSseEventType::SeriesUpdated
            }
            MediaEvent::MediaDeleted { .. } => MediaSseEventType::MediaDeleted,
            MediaEvent::ScanStarted { .. } => {
                MediaSseEventType::Scan(ScanSseEventType::Started)
            }
            MediaEvent::ScanProgress { .. } => {
                MediaSseEventType::Scan(ScanSseEventType::Progress)
            }
            MediaEvent::ScanCompleted { .. } => {
                MediaSseEventType::Scan(ScanSseEventType::Completed)
            }
            MediaEvent::ScanFailed { .. } => {
                MediaSseEventType::Scan(ScanSseEventType::Failed)
            }
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
pub enum ImageSseEventType {
    Ready,
}

impl ImageSseEventType {
    pub const fn event_name(self) -> &'static str {
        match self {
            Self::Ready => "image.ready",
        }
    }
}

impl fmt::Display for ImageSseEventType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(self.event_name())
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseImageSseEventTypeError {
    invalid_value: String,
}

impl ParseImageSseEventTypeError {
    pub fn new(value: &str) -> Self {
        Self {
            invalid_value: value.to_string(),
        }
    }
}

impl fmt::Display for ParseImageSseEventTypeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "invalid image SSE event type: {}", self.invalid_value)
    }
}

impl std::error::Error for ParseImageSseEventTypeError {}

impl FromStr for ImageSseEventType {
    type Err = ParseImageSseEventTypeError;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value {
            "image.ready" => Ok(Self::Ready),
            other => Err(ParseImageSseEventTypeError::new(other)),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{ImageSseEventType, MediaSseEventType, ScanSseEventType};
    use std::str::FromStr;

    #[test]
    fn scan_event_name_roundtrip() {
        for (name, value) in [
            ("scan.started", ScanSseEventType::Started),
            ("scan.progress", ScanSseEventType::Progress),
            ("scan.quiescing", ScanSseEventType::Quiescing),
            ("scan.completed", ScanSseEventType::Completed),
            ("scan.failed", ScanSseEventType::Failed),
        ] {
            assert_eq!(value.event_name(), name);
            assert_eq!(ScanSseEventType::from_str(name).unwrap(), value);
        }
    }

    #[test]
    fn media_event_name_roundtrip() {
        for (name, value) in [
            ("media.movie_added", MediaSseEventType::MovieAdded),
            (
                "media.movie_batch_finalized",
                MediaSseEventType::MovieBatchFinalized,
            ),
            ("media.series_added", MediaSseEventType::SeriesAdded),
            (
                "media.series_bundle_finalized",
                MediaSseEventType::SeriesBundleFinalized,
            ),
            ("media.season_added", MediaSseEventType::SeasonAdded),
            ("media.episode_added", MediaSseEventType::EpisodeAdded),
            ("media.movie_updated", MediaSseEventType::MovieUpdated),
            ("media.series_updated", MediaSseEventType::SeriesUpdated),
            ("media.season_updated", MediaSseEventType::SeasonUpdated),
            ("media.episode_updated", MediaSseEventType::EpisodeUpdated),
            ("media.deleted", MediaSseEventType::MediaDeleted),
            (
                "scan.started",
                MediaSseEventType::Scan(ScanSseEventType::Started),
            ),
            (
                "scan.progress",
                MediaSseEventType::Scan(ScanSseEventType::Progress),
            ),
            (
                "scan.quiescing",
                MediaSseEventType::Scan(ScanSseEventType::Quiescing),
            ),
            (
                "scan.completed",
                MediaSseEventType::Scan(ScanSseEventType::Completed),
            ),
            (
                "scan.failed",
                MediaSseEventType::Scan(ScanSseEventType::Failed),
            ),
        ] {
            assert_eq!(value.event_name(), name);
            assert_eq!(MediaSseEventType::from_str(name).unwrap(), value);
        }
    }

    #[test]
    fn image_event_name_roundtrip() {
        {
            let (name, value) = ("image.ready", ImageSseEventType::Ready);
            assert_eq!(value.event_name(), name);
            assert_eq!(ImageSseEventType::from_str(name).unwrap(), value);
        }
    }
}