rs-plugin-common-interfaces 0.31.0

Common description for plugin creation
Documentation
use crate::domain::tools::rating_serializer;
use crate::{
    domain::{
        other_ids::OtherIds,
        rs_ids::{ApplyRsIds, RsIds},
    },
    url::RsLink,
};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use strum_macros::{Display, EnumString};

#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Display, EnumString)]
#[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum MovieStatus {
    Returning,
    Released,
    InProduction,
    PostProduction,
    Planned,
    Rumored,
    Canceled,
    #[strum(default)]
    Other(String),
    #[default]
    Unknown,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Movie {
    #[serde(default)]
    pub id: String,

    pub name: String,
    #[serde(rename = "type")]
    pub kind: Option<Value>,
    pub year: Option<u16>,
    pub airdate: Option<i64>,
    pub digitalairdate: Option<i64>,

    pub duration: Option<u32>,
    pub overview: Option<String>,
    pub country: Option<String>,
    pub status: Option<MovieStatus>,

    pub imdb: Option<String>,
    pub slug: Option<String>,
    pub tmdb: Option<u64>,
    pub trakt: Option<u64>,
    pub otherids: Option<OtherIds>,

    pub lang: Option<String>,
    pub original: Option<String>,

    #[serde(serialize_with = "rating_serializer")]
    pub imdb_rating: Option<f32>,
    pub imdb_votes: Option<u64>,
    #[serde(serialize_with = "rating_serializer")]
    pub trakt_rating: Option<f32>,
    pub trakt_votes: Option<u32>,

    pub trailer: Option<RsLink>,

    #[serde(default)]
    pub modified: u64,
    #[serde(default)]
    pub added: u64,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub watched: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub progress: Option<u64>,

    #[serde(default)]
    pub posterv: u64,
    #[serde(default)]
    pub backgroundv: u64,
    #[serde(default)]
    pub cardv: u64,
}

impl From<Movie> for RsIds {
    fn from(value: Movie) -> Self {
        let mut ids = RsIds {
            trakt: value.trakt,
            slug: value.slug,
            imdb: value.imdb,
            tmdb: value.tmdb,
            other_ids: value.otherids,
            ..Default::default()
        };
        if ids.try_add(value.id.clone()).is_err() {
            ids.redseat = Some(value.id);
        }
        ids
    }
}

impl ApplyRsIds for Movie {
    fn apply_rs_ids(&mut self, ids: &RsIds) {
        if let Some(redseat) = ids.redseat.as_ref() {
            self.id = redseat.clone();
        }
        if let Some(trakt) = ids.trakt {
            self.trakt = Some(trakt);
        }
        if let Some(slug) = ids.slug.as_ref() {
            self.slug = Some(slug.clone());
        }
        if let Some(imdb) = ids.imdb.as_ref() {
            self.imdb = Some(imdb.clone());
        }
        if let Some(tmdb) = ids.tmdb {
            self.tmdb = Some(tmdb);
        }
        if let Some(other_ids) = ids.other_ids.as_ref() {
            self.otherids = Some(other_ids.clone());
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Movie;
    use crate::domain::{
        other_ids::OtherIds,
        rs_ids::{ApplyRsIds, RsIds},
    };
    use serde_json::json;

    #[test]
    fn movie_otherids_serializes_as_array_and_rejects_string() {
        let movie = Movie {
            id: "movie-1".to_string(),
            name: "Movie 1".to_string(),
            otherids: Some(OtherIds(vec!["imdb:tt1234567".to_string()])),
            ..Default::default()
        };
        let value = serde_json::to_value(&movie).unwrap();
        assert_eq!(value.get("otherids"), Some(&json!(["imdb:tt1234567"])));

        let parsed: Movie = serde_json::from_value(json!({
            "id": "movie-1",
            "name": "Movie 1",
            "otherids": ["tmdb:42"]
        }))
        .unwrap();
        assert_eq!(parsed.otherids, Some(OtherIds(vec!["tmdb:42".to_string()])));

        let invalid = serde_json::from_value::<Movie>(json!({
            "id": "movie-1",
            "name": "Movie 1",
            "otherids": "tmdb:42"
        }));
        assert!(invalid.is_err());
    }

    #[test]
    fn movie_apply_rs_ids_updates_only_present_values() {
        let mut movie = Movie {
            id: "movie-old".to_string(),
            name: "Movie".to_string(),
            tmdb: Some(10),
            ..Default::default()
        };
        let ids = RsIds {
            redseat: Some("movie-new".to_string()),
            trakt: Some(100),
            imdb: Some("tt0000100".to_string()),
            slug: Some("movie-slug".to_string()),
            other_ids: Some(OtherIds(vec!["allocine:1".to_string()])),
            ..Default::default()
        };

        movie.apply_rs_ids(&ids);

        assert_eq!(movie.id, "movie-new");
        assert_eq!(movie.trakt, Some(100));
        assert_eq!(movie.imdb.as_deref(), Some("tt0000100"));
        assert_eq!(movie.slug.as_deref(), Some("movie-slug"));
        assert_eq!(movie.tmdb, Some(10));
        assert_eq!(
            movie.otherids,
            Some(OtherIds(vec!["allocine:1".to_string()]))
        );
    }
}