diff --git a/src/scopes.rs b/src/scopes.rs index 07e9828..884119c 100644 --- a/src/scopes.rs +++ b/src/scopes.rs @@ -3,10 +3,13 @@ use std::{ collections::HashSet, fmt, ops::BitOr, + str::FromStr, }; use serde::ser::{Serialize, Serializer}; +use errors::Error; + /// Represents a set of OAuth scopes /// /// # Example @@ -24,6 +27,21 @@ pub struct Scopes { scopes: HashSet, } +impl FromStr for Scopes { + type Err = Error; + + fn from_str(s: &str) -> Result { + let mut set = HashSet::new(); + for scope in s.split_whitespace() { + let scope = Scope::from_str(&scope)?; + set.insert(scope); + } + Ok(Scopes { + scopes: set, + }) + } +} + impl Serialize for Scopes { fn serialize(&self, serializer: S) -> ::std::result::Result where @@ -250,7 +268,7 @@ impl fmt::Display for Scopes { /// Permission scope of the application. /// [Details on what each permission provides][1] /// [1]: https://github.com/tootsuite/documentation/blob/master/Using-the-API/OAuth-details.md) -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Hash, Serialize)] enum Scope { /// Read only permissions. #[serde(rename = "read")] @@ -266,9 +284,25 @@ enum Scope { Push, } -impl PartialOrd for Scope { - fn partial_cmp(&self, other: &Scope) -> Option { - Some(self.cmp(other)) +impl FromStr for Scope { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "read" => Scope::Read(None), + "write" => Scope::Write(None), + "follow" => Scope::Follow, + "push" => Scope::Push, + read if read.starts_with("read:") => { + let r: Read = Read::from_str(&read[5..])?; + Scope::Read(Some(r)) + }, + write if write.starts_with("write:") => { + let w: Write = Write::from_str(&write[6..])?; + Scope::Write(Some(w)) + }, + _ => return Err(Error::Other("Unknown scope".to_string())), + }) } } @@ -363,6 +397,27 @@ pub enum Read { Statuses, } +impl FromStr for Read { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "accounts" => Read::Accounts, + "blocks" => Read::Blocks, + "favourites" => Read::Favourites, + "filters" => Read::Filters, + "follows" => Read::Follows, + "lists" => Read::Lists, + "mutes" => Read::Mutes, + "notifications" => Read::Notifications, + "reports" => Read::Reports, + "search" => Read::Search, + "statuses" => Read::Statuses, + _ => return Err(Error::Other("Unknown 'read' subcategory".to_string())), + }) + } +} + impl PartialOrd for Read { fn partial_cmp(&self, other: &Read) -> Option { Some(self.cmp(other)) @@ -437,6 +492,27 @@ pub enum Write { Statuses, } +impl FromStr for Write { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "accounts" => Write::Accounts, + "blocks" => Write::Blocks, + "favourites" => Write::Favourites, + "filters" => Write::Filters, + "follows" => Write::Follows, + "lists" => Write::Lists, + "media" => Write::Media, + "mutes" => Write::Mutes, + "notifications" => Write::Notifications, + "reports" => Write::Reports, + "statuses" => Write::Statuses, + _ => return Err(Error::Other("Unknown 'write' subcategory".to_string())), + }) + } +} + impl PartialOrd for Write { fn partial_cmp(&self, other: &Write) -> Option { Some(self.cmp(other)) @@ -664,4 +740,51 @@ mod tests { assert_eq!(&ser, &expected); } } + + #[test] + fn test_scope_from_str() { + let tests = [ + ("read", Scope::Read(None)), + ("read:accounts", Scope::Read(Some(Read::Accounts))), + ("read:blocks", Scope::Read(Some(Read::Blocks))), + ("read:favourites", Scope::Read(Some(Read::Favourites))), + ("read:filters", Scope::Read(Some(Read::Filters))), + ("read:follows", Scope::Read(Some(Read::Follows))), + ("read:lists", Scope::Read(Some(Read::Lists))), + ("read:mutes", Scope::Read(Some(Read::Mutes))), + ("read:notifications", Scope::Read(Some(Read::Notifications))), + ("read:reports", Scope::Read(Some(Read::Reports))), + ("read:search", Scope::Read(Some(Read::Search))), + ("read:statuses", Scope::Read(Some(Read::Statuses))), + ("write", Scope::Write(None)), + ("write:accounts", Scope::Write(Some(Write::Accounts))), + ("write:blocks", Scope::Write(Some(Write::Blocks))), + ("write:favourites", Scope::Write(Some(Write::Favourites))), + ("write:filters", Scope::Write(Some(Write::Filters))), + ("write:follows", Scope::Write(Some(Write::Follows))), + ("write:lists", Scope::Write(Some(Write::Lists))), + ("write:media", Scope::Write(Some(Write::Media))), + ("write:mutes", Scope::Write(Some(Write::Mutes))), + ( + "write:notifications", + Scope::Write(Some(Write::Notifications)), + ), + ("write:reports", Scope::Write(Some(Write::Reports))), + ("write:statuses", Scope::Write(Some(Write::Statuses))), + ("follow", Scope::Follow), + ("push", Scope::Push), + ]; + for (source, expected) in &tests { + let result = Scope::from_str(source).expect(&format!("Couldn't parse '{}'", &source)); + assert_eq!(result, *expected); + } + } + + #[test] + fn test_scopes_str_round_trip() { + let original = "read write follow push"; + let scopes = Scopes::from_str(original).expect("Couldn't convert to Scopes"); + let result = format!("{}", scopes); + assert_eq!(original, result); + } }