Skip to content

Commit

Permalink
refactor!: make Guids to be enum variants
Browse files Browse the repository at this point in the history
Previously Guid values could be either Guid struct with an `id` field,
or a plain-text String.
Converting all the guids to enum simplifies the resulting api.
  • Loading branch information
andrey-yantsen committed Mar 31, 2023
1 parent 853c8e9 commit 332d07d
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 11 deletions.
103 changes: 103 additions & 0 deletions crates/plex-api/src/media_container/server/library/guid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use std::fmt::Formatter;

use serde::{
de::{MapAccess, Visitor},
Deserialize, Deserializer,
};

#[derive(Debug, Clone)]
pub enum Guid {
Local(String),
Imdb(String),
Tmdb(String),
Tvdb(String),
LastFm(String),
Plex(String, String),
None(String),
Collection(String),
Mbid(String),
PlexMusic(String),
#[cfg(not(feature = "tests_deny_unknown_fields"))]
Unknown(String),
}

impl<'de> Deserialize<'de> for Guid {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct V;

impl<'de> Visitor<'de> for V {
type Value = Guid;

fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("plex-formatted guid string or object")
}

fn visit_str<E>(self, value: &str) -> Result<Guid, E>
where
E: ::serde::de::Error,
{
let parts = value.split("://").collect::<Vec<&str>>();
if parts.len() != 2 {
return Err(serde::de::Error::custom(
"guid value must be formatted as protocol://path",
));
}

Ok(match parts[..] {
["imdb", id] | ["com.plexapp.agents.imdb", id] => Guid::Imdb(id.to_owned()),
["local", id] => Guid::Local(id.to_owned()),
["tvdb", id] | ["com.plexapp.agents.thetvdb", id] => Guid::Tvdb(id.to_owned()),
["tmdb", id] => Guid::Tmdb(id.to_owned()),
["collection", id] => Guid::Collection(id.to_owned()),
["com.plexapp.agents.lastfm", id] => Guid::LastFm(id.to_owned()),
["mbid", id] => Guid::Mbid(id.to_owned()),
["com.plexapp.agents.none", id] => Guid::None(id.to_owned()),
["com.plexapp.agents.plexmusic", id] => Guid::PlexMusic(id.to_owned()),
["plex", id] => {
let plex_guid_parts = id.split('/').collect::<Vec<&str>>();
if plex_guid_parts.len() != 2 {
return Err(serde::de::Error::custom(
"guid value must be formatted as protocol://path",
));
}

Guid::Plex(plex_guid_parts[0].to_owned(), plex_guid_parts[1].to_owned())
}
_ => {
#[cfg(not(feature = "tests_deny_unknown_fields"))]
{
Guid::Unknown(value.to_owned())
}
#[cfg(feature = "tests_deny_unknown_fields")]
{
return Err(serde::de::Error::custom("unknown guid format"));
}
}
})
}

fn visit_map<M>(self, mut map: M) -> Result<Guid, M::Error>
where
M: MapAccess<'de>,
{
if let Some((key, value)) = map.next_entry::<&str, &str>()? {
if key == "id" {
self.visit_str(value)
} else {
Err(serde::de::Error::unknown_field(
value,
&["object shouldn't have fields other than id"],
))
}
} else {
Err(serde::de::Error::missing_field("object must have id field"))
}
}
}

deserializer.deserialize_any(V)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
mod guid;

use crate::media_container::MediaContainer;
pub use guid::Guid;
use monostate::MustBe;
use serde::{Deserialize, Deserializer, Serialize};
use serde_aux::prelude::{
Expand Down Expand Up @@ -434,12 +437,6 @@ pub struct Location {
pub path: String,
}

#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
pub struct Guid {
pub id: String,
}

#[derive(Debug, Deserialize, Clone)]
#[cfg_attr(feature = "tests_deny_unknown_fields", serde(deny_unknown_fields))]
pub struct Tag {
Expand Down Expand Up @@ -486,7 +483,7 @@ pub struct ParentMetadata {
pub parent_key: Option<String>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub parent_rating_key: Option<u32>,
pub parent_guid: Option<String>,
pub parent_guid: Option<Guid>,

pub parent_title: Option<String>,
pub parent_studio: Option<String>,
Expand All @@ -506,7 +503,7 @@ pub struct GrandParentMetadata {
pub grandparent_key: Option<String>,
#[serde(default, deserialize_with = "deserialize_option_number_from_string")]
pub grandparent_rating_key: Option<u32>,
pub grandparent_guid: Option<String>,
pub grandparent_guid: Option<Guid>,

pub grandparent_title: Option<String>,
pub grandparent_studio: Option<String>,
Expand Down Expand Up @@ -558,7 +555,7 @@ pub struct Metadata {
pub key: String,
#[serde(deserialize_with = "deserialize_number_from_string")]
pub rating_key: u32,
pub guid: String,
pub guid: Guid,

#[serde(rename = "type")]
pub metadata_type: Option<MetadataType>,
Expand Down
5 changes: 3 additions & 2 deletions crates/plex-api/src/webhook/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//! The structs are implemented according to the [documentation](https://support.plex.tv/articles/115002267687-webhooks/),
//! please read it for further information.

use crate::media_container::server::library::Guid;
use serde::{Deserialize, Serialize};
use serde_aux::prelude::deserialize_option_number_from_string;
use time::OffsetDateTime;
Expand Down Expand Up @@ -66,7 +67,7 @@ pub struct Metadata {
pub rating_key: Option<String>,
pub parent_rating_key: Option<String>,
pub grandparent_rating_key: Option<String>,
pub guid: Option<String>,
pub guid: Option<Guid>,
#[serde(rename = "librarySectionID")]
#[serde(deserialize_with = "deserialize_option_number_from_string")]
pub library_section_id: Option<u32>,
Expand Down Expand Up @@ -145,7 +146,7 @@ mod test {
serde_json::from_str::<super::Webhook>(WEBHOOK_JSON).expect("failed to parse webhook");
}

const WEBHOOK_JSON: &str = r#"{
const WEBHOOK_JSON: &str = r#"{
"event": "media.play",
"user": true,
"owner": true,
Expand Down

0 comments on commit 332d07d

Please sign in to comment.