diff options
Diffstat (limited to 'kittybox-rs/indieauth/src/scopes.rs')
-rw-r--r-- | kittybox-rs/indieauth/src/scopes.rs | 169 |
1 files changed, 169 insertions, 0 deletions
diff --git a/kittybox-rs/indieauth/src/scopes.rs b/kittybox-rs/indieauth/src/scopes.rs new file mode 100644 index 0000000..6d1ed7e --- /dev/null +++ b/kittybox-rs/indieauth/src/scopes.rs @@ -0,0 +1,169 @@ +use serde::{ + Serialize, Serializer, + Deserialize, + de::{ + Deserializer, Visitor, + Error as DeserializeError + } +}; + +#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum Scope { + /// Allows to create posts using Micropub. + Create, + /// Allows to edit posts using Micropub. + Update, + /// Allows to delete posts using Micropub. + Delete, + /// Allows to upload blobs to the media endpoint. + Media, + /// Allows to read feeds via Microsub. + Read, + /// Allows to manage follows via Microsub. + Follow, + /// Allows to mute and unmute users in feeds via Microsub. + Mute, + /// Allows to block and unblock users. + Block, + /// Allows to create and manage feeds via Microsub. + Channels, + /// Allows to request profile information (except email, see Email) + Profile, + /// Allows to receive email in the profile information. + Email, + /// Custom scope not included above. + Custom(String) +} +impl Scope { + pub fn custom(scope: &str) -> Scope { + Scope::Custom(scope.to_string()) + } +} +// TODO consider relying on serde for these conversions +impl AsRef<str> for Scope { + fn as_ref(&self) -> &str { + use Scope::*; + match self { + Create => "create", + Update => "update", + Delete => "delete", + Media => "media", + Read => "read", + Follow => "follow", + Mute => "mute", + Block => "block", + Channels => "channels", + Profile => "profile", + Email => "email", + Custom(s) => s.as_ref() + } + } +} +impl From<&str> for Scope { + fn from(scope: &str) -> Self { + match scope { + "create" => Scope::Create, + "update" => Scope::Update, + "delete" => Scope::Delete, + "media" => Scope::Media, + "read" => Scope::Read, + "follow" => Scope::Follow, + "mute" => Scope::Mute, + "block" => Scope::Block, + "channels" => Scope::Channels, + "profile" => Scope::Profile, + "email" => Scope::Email, + other => Scope::custom(other) + } + } +} +#[derive(Debug, Clone)] +pub struct Scopes(Vec<Scope>); +impl Scopes { + pub fn new(scopes: Vec<Scope>) -> Self { + Self(scopes) + } + pub fn has(&self, scope: &Scope) -> bool { + self.0.iter().any(|s| s == scope) + } + pub fn has_all(&self, scopes: &[Scope]) -> bool { + scopes.iter() + .map(|s1| scopes.iter().any(|s2| s1 == s2)) + .all(|s| s) + } +} +impl AsRef<[Scope]> for Scopes { + fn as_ref(&self) -> &[Scope] { + self.0.as_ref() + } +} +impl ToString for Scopes { + fn to_string(&self) -> String { + self.0.iter() + .map(|s| s.as_ref()) + .fold(String::new(), |a, s| if a.is_empty() { + s.to_string() + } else { + a + " " + s + }) + } +} +impl Serialize for Scopes { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer + { + serializer.serialize_str(&self.to_string()) + } +} +struct ScopeVisitor; +impl<'de> Visitor<'de> for ScopeVisitor { + type Value = Scopes; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a string of space-separated OAuth2 scopes") + } + + fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> + where + E: DeserializeError + { + Ok(Scopes(value.split_ascii_whitespace() + .map(Scope::from) + .collect::<Vec<Scope>>())) + } +} +impl<'de> Deserialize<'de> for Scopes { + + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de> + { + deserializer.deserialize_str(ScopeVisitor) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_serde_vec_scope() { + let scopes = vec![ + Scope::Create, Scope::Update, Scope::Delete, + Scope::Media, + Scope::custom("kittybox_internal_access") + ]; + + let scope_serialized = serde_json::to_value( + Scopes::new(scopes.clone()) + ).unwrap(); + let scope_str = scope_serialized.as_str().unwrap(); + assert_eq!(scope_str, "create update delete media kittybox_internal_access"); + + assert!(serde_json::from_value::<Scopes>(scope_serialized).unwrap().has_all(&scopes)) + + } + +} |