summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPaul Woolcock <paul@woolcock.us>2018-08-29 16:23:50 -0400
committerPaul Woolcock <paul@woolcock.us>2018-08-30 19:12:16 -0400
commite284894d40e206adf8089482e340d7a9d0046d79 (patch)
tree9aa48dceb987d100dd1cb6d7f0130e3514865c28
parentaf806b7856c974935046e18b627c6dd3e999b9fa (diff)
feat(scopes): Implement granular OAuth scopes
BREAKING CHANGE: Applications that use the `Scopes` data structure will have minor changes to make Closes #44
-rw-r--r--examples/register.rs2
-rw-r--r--src/apps.rs30
-rw-r--r--src/registration.rs10
-rw-r--r--src/scopes.rs668
4 files changed, 641 insertions, 69 deletions
diff --git a/examples/register.rs b/examples/register.rs
index f375921..7b581ee 100644
--- a/examples/register.rs
+++ b/examples/register.rs
@@ -31,7 +31,7 @@ pub fn register() -> Result<Mastodon, Box<Error>> {
let website = read_line("Please enter your mastodon instance url:")?;
let registration = Registration::new(website.trim())
.client_name("elefren-examples")
- .scopes(Scopes::All)
+ .scopes(Scopes::all())
.website("https://github.com/pwoolcoc/elefren")
.build()?;
let url = registration.authorize_url()?;
diff --git a/src/apps.rs b/src/apps.rs
index c1bd06e..f9e1585 100644
--- a/src/apps.rs
+++ b/src/apps.rs
@@ -2,8 +2,8 @@ use std::borrow::Cow;
use try_from::TryInto;
-use scopes::Scopes;
use errors::{Error, Result};
+use scopes::Scopes;
/// Represents an application that can be registered with a mastodon instance
#[derive(Clone, Debug, Default, Serialize, PartialEq)]
@@ -37,19 +37,19 @@ impl App {
/// ```
/// # extern crate elefren;
/// # use elefren::Error;
- /// use elefren::apps::{App, Scopes};
+ /// use elefren::{apps::App, scopes::Scopes};
///
/// # fn main() -> Result<(), Error> {
/// let mut builder = App::builder();
/// builder.client_name("elefren-test");
/// let app = builder.build()?;
/// let scopes = app.scopes();
- /// assert_eq!(scopes, Scopes::Read);
+ /// assert_eq!(scopes, &Scopes::read_all());
/// # Ok(())
/// # }
/// ```
- pub fn scopes(&self) -> Scopes {
- self.scopes
+ pub fn scopes(&self) -> &Scopes {
+ &self.scopes
}
}
@@ -98,7 +98,7 @@ impl<'a> AppBuilder<'a> {
/// Permission scope of the application.
///
- /// IF none is specified, the default is Scopes::Read
+ /// IF none is specified, the default is Scopes::read_all()
pub fn scopes(&mut self, scopes: Scopes) -> &mut Self {
self.scopes = Some(scopes);
self
@@ -123,7 +123,7 @@ impl<'a> AppBuilder<'a> {
.redirect_uris
.unwrap_or_else(|| "urn:ietf:wg:oauth:2.0:oob".into())
.into(),
- scopes: self.scopes.unwrap_or_else(|| Scopes::Read),
+ scopes: self.scopes.unwrap_or_else(|| Scopes::read_all()),
website: self.website.map(|s| s.into()),
})
}
@@ -158,9 +158,9 @@ mod tests {
#[test]
fn test_app_scopes() {
let mut builder = App::builder();
- builder.client_name("test").scopes(Scopes::All);
+ builder.client_name("test").scopes(Scopes::all());
let app = builder.build().expect("Couldn't build App");
- assert_eq!(app.scopes(), Scopes::All);
+ assert_eq!(app.scopes(), &Scopes::all());
}
#[test]
@@ -168,7 +168,7 @@ mod tests {
let mut builder = AppBuilder::new();
builder.client_name("foo-test");
builder.redirect_uris("http://example.com");
- builder.scopes(Scopes::ReadWrite);
+ builder.scopes(Scopes::read_all() | Scopes::write_all());
builder.website("https://example.com");
let app = builder.build().expect("Couldn't build App");
assert_eq!(
@@ -176,7 +176,7 @@ mod tests {
App {
client_name: "foo-test".to_string(),
redirect_uris: "http://example.com".to_string(),
- scopes: Scopes::ReadWrite,
+ scopes: Scopes::read_all() | Scopes::write_all(),
website: Some("https://example.com".to_string()),
}
);
@@ -195,7 +195,7 @@ mod tests {
builder
.website("https://example.com")
.redirect_uris("https://example.com")
- .scopes(Scopes::All);
+ .scopes(Scopes::all());
builder.build().expect("no client-name");
}
@@ -204,7 +204,7 @@ mod tests {
let app = App {
client_name: "foo-test".to_string(),
redirect_uris: "http://example.com".to_string(),
- scopes: Scopes::All,
+ scopes: Scopes::all(),
website: None,
};
let expected = app.clone();
@@ -218,11 +218,11 @@ mod tests {
builder
.client_name("foo-test")
.redirect_uris("http://example.com")
- .scopes(Scopes::All);
+ .scopes(Scopes::all());
let expected = App {
client_name: "foo-test".to_string(),
redirect_uris: "http://example.com".to_string(),
- scopes: Scopes::All,
+ scopes: Scopes::all(),
website: None,
};
let result = builder
diff --git a/src/registration.rs b/src/registration.rs
index 2a5f8d7..d09c180 100644
--- a/src/registration.rs
+++ b/src/registration.rs
@@ -4,8 +4,8 @@ use reqwest::{Client, RequestBuilder, Response};
use try_from::TryInto;
use apps::{App, AppBuilder};
-use scopes::Scopes;
use http_send::{HttpSend, HttpSender};
+use scopes::Scopes;
use Data;
use Error;
use Mastodon;
@@ -136,7 +136,7 @@ impl<'a, H: HttpSend> Registration<'a, H> {
client_id: oauth.client_id,
client_secret: oauth.client_secret,
redirect: oauth.redirect_uri,
- scopes: app.scopes(),
+ scopes: app.scopes().clone(),
http_sender: self.http_sender.clone(),
})
}
@@ -171,7 +171,7 @@ impl<'a, H: HttpSend> Registration<'a, H> {
client_id: oauth.client_id,
client_secret: oauth.client_secret,
redirect: oauth.redirect_uri,
- scopes: app.scopes(),
+ scopes: app.scopes().clone(),
http_sender: self.http_sender.clone(),
})
}
@@ -286,10 +286,10 @@ mod tests {
#[test]
fn test_set_scopes() {
let mut r = Registration::new("https://example.com");
- r.scopes(Scopes::All);
+ r.scopes(Scopes::all());
assert_eq!(r.base, "https://example.com".to_string());
- assert_eq!(&mut r.app_builder, AppBuilder::new().scopes(Scopes::All));
+ assert_eq!(&mut r.app_builder, AppBuilder::new().scopes(Scopes::all()));
}
#[test]
diff --git a/src/scopes.rs b/src/scopes.rs
index 282d1f9..07e9828 100644
--- a/src/scopes.rs
+++ b/src/scopes.rs
@@ -1,82 +1,619 @@
-use std::fmt;
+use std::{
+ cmp::{Ordering, PartialEq, PartialOrd},
+ collections::HashSet,
+ fmt,
+ ops::BitOr,
+};
+
+use serde::ser::{Serialize, Serializer};
+
+/// Represents a set of OAuth scopes
+///
+/// # Example
+///
+/// ```rust
+/// use elefren::prelude::*;
+///
+/// let read = Scopes::read_all();
+/// let write = Scopes::write_all();
+/// let follow = Scopes::follow();
+/// let all = read | write | follow;
+/// ```
+#[derive(Clone)]
+pub struct Scopes {
+ scopes: HashSet<Scope>,
+}
+
+impl Serialize for Scopes {
+ fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ let repr = format!("{}", self);
+ serializer.serialize_str(&repr)
+ }
+}
+
+impl Scopes {
+ /// Represents all available oauth scopes: "read write follow push"
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::Scopes;
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::all();
+ /// assert_eq!(&format!("{}", scope), "read write follow push");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn all() -> Scopes {
+ Scopes::read_all() | Scopes::write_all() | Scopes::follow() | Scopes::push()
+ }
+
+ /// Represents the full "read" scope
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::Scopes;
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::read_all();
+ /// assert_eq!(&format!("{}", scope), "read");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn read_all() -> Scopes {
+ Scopes::_read(None)
+ }
+
+ /// Represents a specific "read:___" scope
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::{Read, Scopes};
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::read(Read::Accounts);
+ /// assert_eq!(&format!("{}", scope), "read:accounts");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn read(subscope: Read) -> Scopes {
+ Scopes::_read(Some(subscope))
+ }
+
+ /// Represents the full "write" scope
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::Scopes;
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::write_all();
+ /// assert_eq!(&format!("{}", scope), "write");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn write_all() -> Scopes {
+ Scopes::_write(None)
+ }
+
+ /// Represents a specific "write:___" scope
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::{Scopes, Write};
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::write(Write::Accounts);
+ /// assert_eq!(&format!("{}", scope), "write:accounts");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn write(subscope: Write) -> Scopes {
+ Scopes::_write(Some(subscope))
+ }
+
+ /// Represents the "follow" scope
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::Scopes;
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::follow();
+ /// assert_eq!(&format!("{}", scope), "follow");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn follow() -> Scopes {
+ Scopes::new(Scope::Follow)
+ }
+
+ /// Represents the full "push" scope
+ ///
+ /// ```
+ /// # extern crate elefren;
+ /// # use std::error::Error;
+ /// use elefren::scopes::Scopes;
+ ///
+ /// # fn main() -> Result<(), Box<Error>> {
+ /// let scope = Scopes::push();
+ /// assert_eq!(&format!("{}", scope), "push");
+ /// # Ok(())
+ /// # }
+ /// ```
+ pub fn push() -> Scopes {
+ Scopes::new(Scope::Push)
+ }
+
+ /// Combines 2 scopes together
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use elefren::prelude::*;
+ ///
+ /// let read = Scopes::read_all();
+ /// let write = Scopes::write_all();
+ /// let read_write = read.and(write);
+ /// ```
+ pub fn and(self, other: Scopes) -> Scopes {
+ let newset: HashSet<_> = self
+ .scopes
+ .union(&other.scopes)
+ .into_iter()
+ .map(|s| *s)
+ .collect();
+ Scopes {
+ scopes: newset,
+ }
+ }
+
+ fn _write(subscope: Option<Write>) -> Scopes {
+ Scopes::new(Scope::Write(subscope))
+ }
+
+ fn _read(subscope: Option<Read>) -> Scopes {
+ Scopes::new(Scope::Read(subscope))
+ }
+
+ fn new(scope: Scope) -> Scopes {
+ let mut set = HashSet::new();
+ set.insert(scope);
+ Scopes {
+ scopes: set,
+ }
+ }
+}
+
+impl BitOr for Scopes {
+ type Output = Scopes;
+
+ fn bitor(self, other: Scopes) -> Self::Output {
+ self.and(other)
+ }
+}
+
+impl PartialEq for Scopes {
+ fn eq(&self, other: &Scopes) -> bool {
+ self.scopes
+ .symmetric_difference(&other.scopes)
+ .next()
+ .is_none()
+ }
+}
+
+impl Default for Scopes {
+ fn default() -> Scopes {
+ Scopes::read_all()
+ }
+}
+
+impl fmt::Debug for Scopes {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "[")?;
+ for scope in &self.scopes {
+ write!(f, "{:?}", &scope)?;
+ }
+ Ok(write!(f, "]")?)
+ }
+}
+
+impl fmt::Display for Scopes {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut start = true;
+ let scopes = {
+ let mut scopes = self.scopes.iter().collect::<Vec<_>>();
+ scopes.sort();
+ scopes
+ };
+ for scope in &scopes {
+ if !start {
+ write!(f, " ")?;
+ } else {
+ start = false;
+ }
+ write!(f, "{}", &scope)?;
+ }
+ Ok(())
+ }
+}
/// 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, Hash, Serialize)]
-pub enum Scopes {
- /// All Permissions, equivalent to `read write follow`
- #[serde(rename = "read write follow")]
- All,
- /// Only permission to add and remove followers.
- #[serde(rename = "follow")]
- Follow,
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
+enum Scope {
/// Read only permissions.
#[serde(rename = "read")]
- Read,
- /// Read & Follow permissions.
- #[serde(rename = "read follow")]
- ReadFollow,
- /// Read & Write permissions.
- #[serde(rename = "read write")]
- ReadWrite,
+ Read(Option<Read>),
/// Write only permissions.
#[serde(rename = "write")]
- Write,
- /// Write & Follow permissions.
- #[serde(rename = "write follow")]
- WriteFollow,
+ Write(Option<Write>),
+ /// Only permission to add and remove followers.
+ #[serde(rename = "follow")]
+ Follow,
+ /// Push permissions
+ #[serde(rename = "push")]
+ Push,
}
-impl fmt::Display for Scopes {
+impl PartialOrd for Scope {
+ fn partial_cmp(&self, other: &Scope) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Scope {
+ fn cmp(&self, other: &Scope) -> Ordering {
+ match (*self, *other) {
+ (Scope::Read(None), Scope::Read(None)) => Ordering::Equal,
+ (Scope::Read(None), Scope::Read(Some(..))) => Ordering::Less,
+ (Scope::Read(Some(..)), Scope::Read(None)) => Ordering::Greater,
+ (Scope::Read(Some(ref a)), Scope::Read(Some(ref b))) => a.cmp(b),
+
+ (Scope::Write(None), Scope::Write(None)) => Ordering::Equal,
+ (Scope::Write(None), Scope::Write(Some(..))) => Ordering::Less,
+ (Scope::Write(Some(..)), Scope::Write(None)) => Ordering::Greater,
+ (Scope::Write(Some(ref a)), Scope::Write(Some(ref b))) => a.cmp(b),
+
+ (Scope::Read(..), Scope::Write(..)) => Ordering::Less,
+ (Scope::Read(..), Scope::Follow) => Ordering::Less,
+ (Scope::Read(..), Scope::Push) => Ordering::Less,
+
+ (Scope::Write(..), Scope::Read(..)) => Ordering::Greater,
+ (Scope::Write(..), Scope::Follow) => Ordering::Less,
+ (Scope::Write(..), Scope::Push) => Ordering::Less,
+
+ (Scope::Follow, Scope::Read(..)) => Ordering::Greater,
+ (Scope::Follow, Scope::Write(..)) => Ordering::Greater,
+ (Scope::Follow, Scope::Follow) => Ordering::Equal,
+ (Scope::Follow, Scope::Push) => Ordering::Less,
+
+ (Scope::Push, Scope::Push) => Ordering::Equal,
+ (Scope::Push, _) => Ordering::Greater,
+ }
+ }
+}
+
+impl fmt::Display for Scope {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ use self::Scope::*;
+ let s = match *self {
+ Read(Some(ref r)) => return fmt::Display::fmt(r, f),
+ Read(None) => "read",
+ Write(Some(ref w)) => return fmt::Display::fmt(w, f),
+ Write(None) => "write",
+ Follow => "follow",
+ Push => "push",
+ };
+ write!(f, "{}", s)
+ }
+}
+
+impl Default for Scope {
+ fn default() -> Self {
+ Scope::Read(None)
+ }
+}
+
+/// Represents the granular "read:___" oauth scopes
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
+pub enum Read {
+ /// Accounts
+ #[serde(rename = "accounts")]
+ Accounts,
+ /// Blocks
+ #[serde(rename = "blocks")]
+ Blocks,
+ /// Favourites
+ #[serde(rename = "favourites")]
+ Favourites,
+ /// Filters
+ #[serde(rename = "filters")]
+ Filters,
+ /// Follows
+ #[serde(rename = "follows")]
+ Follows,
+ /// Lists
+ #[serde(rename = "lists")]
+ Lists,
+ /// Mutes
+ #[serde(rename = "mutes")]
+ Mutes,
+ /// Notifications
+ #[serde(rename = "notifications")]
+ Notifications,
+ /// Reports
+ #[serde(rename = "reports")]
+ Reports,
+ /// Search
+ #[serde(rename = "search")]
+ Search,
+ /// Statuses
+ #[serde(rename = "statuses")]
+ Statuses,
+}
+
+impl PartialOrd for Read {
+ fn partial_cmp(&self, other: &Read) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Read {
+ fn cmp(&self, other: &Read) -> Ordering {
+ let a = format!("{:?}", self);
+ let b = format!("{:?}", other);
+ a.cmp(&b)
+ }
+}
+
+impl fmt::Display for Read {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use self::Scopes::*;
write!(
f,
- "{}",
+ "read:{}",
match *self {
- All => "read%20write%20follow",
- Follow => "follow",
- Read => "read",
- ReadFollow => "read%20follow",
- ReadWrite => "read%20write",
- Write => "write",
- WriteFollow => "write%20follow",
+ Read::Accounts => "accounts",
+ Read::Blocks => "blocks",
+ Read::Favourites => "favourites",
+ Read::Filters => "filters",
+ Read::Follows => "follows",
+ Read::Lists => "lists",
+ Read::Mutes => "mutes",
+ Read::Notifications => "notifications",
+ Read::Reports => "reports",
+ Read::Search => "search",
+ Read::Statuses => "statuses",
}
)
}
}
-impl Default for Scopes {
- fn default() -> Self {
- Scopes::Read
+/// Represents the granular "write:___" oauth scopes
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
+pub enum Write {
+ /// Accounts
+ #[serde(rename = "accounts")]
+ Accounts,
+ /// Blocks
+ #[serde(rename = "blocks")]
+ Blocks,
+ /// Favourites
+ #[serde(rename = "favourites")]
+ Favourites,
+ /// Filters
+ #[serde(rename = "filters")]
+ Filters,
+ /// Follows
+ #[serde(rename = "follows")]
+ Follows,
+ /// Lists
+ #[serde(rename = "lists")]
+ Lists,
+ /// Media
+ #[serde(rename = "media")]
+ Media,
+ /// Mutes
+ #[serde(rename = "mutes")]
+ Mutes,
+ /// Notifications
+ #[serde(rename = "notifications")]
+ Notifications,
+ /// Reports
+ #[serde(rename = "reports")]
+ Reports,
+ /// Statuses
+ #[serde(rename = "statuses")]
+ Statuses,
+}
+
+impl PartialOrd for Write {
+ fn partial_cmp(&self, other: &Write) -> Option<Ordering> {
+ Some(self.cmp(other))
+ }
+}
+
+impl Ord for Write {
+ fn cmp(&self, other: &Write) -> Ordering {
+ let a = format!("{:?}", self);
+ let b = format!("{:?}", other);
+ a.cmp(&b)
+ }
+}
+
+impl fmt::Display for Write {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "write:{}",
+ match *self {
+ Write::Accounts => "accounts",
+ Write::Blocks => "blocks",
+ Write::Favourites => "favourites",
+ Write::Filters => "filters",
+ Write::Follows => "follows",
+ Write::Lists => "lists",
+ Write::Media => "media",
+ Write::Mutes => "mutes",
+ Write::Notifications => "notifications",
+ Write::Reports => "reports",
+ Write::Statuses => "statuses",
+ }
+ )
}
}
#[cfg(test)]
mod tests {
use super::*;
+ use serde_json;
#[test]
- fn test_scopes_display() {
+ fn test_write_cmp() {
+ let tests = [
+ (Write::Accounts, Write::Blocks),
+ (Write::Blocks, Write::Favourites),
+ (Write::Favourites, Write::Filters),
+ (Write::Filters, Write::Follows),
+ (Write::Follows, Write::Lists),
+ (Write::Lists, Write::Media),
+ (Write::Media, Write::Mutes),
+ (Write::Mutes, Write::Notifications),
+ (Write::Notifications, Write::Reports),
+ (Write::Reports, Write::Statuses),
+ ];
+
+ for (a, b) in &tests {
+ assert!(a < b);
+ assert!(b > a);
+ }
+ }
+
+ #[test]
+ fn test_read_cmp() {
+ let tests = [
+ (Read::Accounts, Read::Blocks),
+ (Read::Blocks, Read::Favourites),
+ (Read::Favourites, Read::Filters),
+ (Read::Filters, Read::Follows),
+ (Read::Follows, Read::Lists),
+ (Read::Lists, Read::Mutes),
+ (Read::Mutes, Read::Notifications),
+ (Read::Notifications, Read::Reports),
+ (Read::Reports, Read::Search),
+ (Read::Search, Read::Statuses),
+ ];
+ for (a, b) in &tests {
+ assert!(a < b);
+ assert!(b > a);
+ }
+ }
+
+ #[test]
+ fn test_scope_cmp() {
+ let tests = [
+ (Scope::Read(None), Scope::Read(Some(Read::Accounts))),
+ (Scope::Read(None), Scope::Read(Some(Read::Blocks))),
+ (Scope::Read(None), Scope::Read(Some(Read::Favourites))),
+ (Scope::Read(None), Scope::Read(Some(Read::Filters))),
+ (Scope::Read(None), Scope::Read(Some(Read::Follows))),
+ (Scope::Read(None), Scope::Read(Some(Read::Lists))),
+ (Scope::Read(None), Scope::Read(Some(Read::Mutes))),
+ (Scope::Read(None), Scope::Read(Some(Read::Notifications))),
+ (Scope::Read(None), Scope::Read(Some(Read::Reports))),
+ (Scope::Read(None), Scope::Read(Some(Read::Search))),
+ (Scope::Read(None), Scope::Read(Some(Read::Statuses))),
+ (Scope::Read(Some(Read::Statuses)), Scope::Write(None)),
+ (Scope::Read(Some(Read::Mutes)), Scope::Follow),
+ (Scope::Read(None), Scope::Push),
+ (Scope::Write(None), Scope::Write(Some(Write::Accounts))),
+ (Scope::Write(None), Scope::Write(Some(Write::Blocks))),
+ (Scope::Write(None), Scope::Write(Some(Write::Favourites))),
+ (Scope::Write(None), Scope::Write(Some(Write::Filters))),
+ (Scope::Write(None), Scope::Write(Some(Write::Follows))),
+ (Scope::Write(None), Scope::Write(Some(Write::Lists))),
+ (Scope::Write(None), Scope::Write(Some(Write::Media))),
+ (Scope::Write(None), Scope::Write(Some(Write::Mutes))),
+ (Scope::Write(None), Scope::Write(Some(Write::Notifications))),
+ (Scope::Write(None), Scope::Write(Some(Write::Reports))),
+ (Scope::Write(None), Scope::Write(Some(Write::Statuses))),
+ (Scope::Write(Some(Write::Statuses)), Scope::Follow),
+ (Scope::Write(Some(Write::Follows)), Scope::Push),
+ ];
+
+ for (a, b) in &tests {
+ assert!(a < b);
+ }
+ }
+
+ #[test]
+ fn test_scope_display() {
let values = [
- Scopes::All,
- Scopes::Follow,
- Scopes::Read,
- Scopes::ReadFollow,
- Scopes::ReadWrite,
- Scopes::Write,
- Scopes::WriteFollow,
+ Scope::Read(None),
+ Scope::Read(Some(Read::Accounts)),
+ Scope::Read(Some(Read::Blocks)),
+ Scope::Read(Some(Read::Favourites)),
+ Scope::Read(Some(Read::Filters)),
+ Scope::Read(Some(Read::Follows)),
+ Scope::Read(Some(Read::Lists)),
+ Scope::Read(Some(Read::Mutes)),
+ Scope::Read(Some(Read::Notifications)),
+ Scope::Read(Some(Read::Reports)),
+ Scope::Read(Some(Read::Search)),
+ Scope::Read(Some(Read::Statuses)),
+ Scope::Write(None),
+ Scope::Write(Some(Write::Accounts)),
+ Scope::Write(Some(Write::Blocks)),
+ Scope::Write(Some(Write::Favourites)),
+ Scope::Write(Some(Write::Filters)),
+ Scope::Write(Some(Write::Follows)),
+ Scope::Write(Some(Write::Lists)),
+ Scope::Write(Some(Write::Media)),
+ Scope::Write(Some(Write::Mutes)),
+ Scope::Write(Some(Write::Notifications)),
+ Scope::Write(Some(Write::Reports)),
+ Scope::Write(Some(Write::Statuses)),
+ Scope::Follow,
+ Scope::Push,
];
let expecteds = [
- "read%20write%20follow".to_string(),
- "follow".to_string(),
"read".to_string(),
- "read%20follow".to_string(),
- "read%20write".to_string(),
+ "read:accounts".to_string(),
+ "read:blocks".to_string(),
+ "read:favourites".to_string(),
+ "read:filters".to_string(),
+ "read:follows".to_string(),
+ "read:lists".to_string(),
+ "read:mutes".to_string(),
+ "read:notifications".to_string(),
+ "read:reports".to_string(),
+ "read:search".to_string(),
+ "read:statuses".to_string(),
"write".to_string(),
- "write%20follow".to_string(),
+ "write:accounts".to_string(),
+ "write:blocks".to_string(),
+ "write:favourites".to_string(),
+ "write:filters".to_string(),
+ "write:follows".to_string(),
+ "write:lists".to_string(),
+ "write:media".to_string(),
+ "write:mutes".to_string(),
+ "write:notifications".to_string(),
+ "write:reports".to_string(),
+ "write:statuses".to_string(),
+ "follow".to_string(),
+ "push".to_string(),
];
let tests = values.into_iter().zip(expecteds.into_iter());
@@ -89,7 +626,42 @@ mod tests {
#[test]
fn test_scopes_default() {
- let default: Scopes = Default::default();
- assert_eq!(default, Scopes::Read);
+ let default: Scope = Default::default();
+ assert_eq!(default, Scope::Read(None));
+ }
+
+ #[test]
+ fn test_scopes_display() {
+ let tests = [
+ (
+ Scopes::read(Read::Accounts) | Scopes::follow(),
+ "read:accounts follow",
+ ),
+ (
+ Scopes::read(Read::Follows) | Scopes::read(Read::Accounts) | Scopes::write_all(),
+ "read:accounts read:follows write",
+ ),
+ ];
+
+ for (a, b) in &tests {
+ assert_eq!(&format!("{}", a), b);
+ }
+ }
+
+ #[test]
+ fn test_scopes_serialize() {
+ let tests = [
+ (
+ Scopes::read_all() | Scopes::write(Write::Notifications) | Scopes::follow(),
+ "read write:notifications follow",
+ ),
+ (Scopes::follow() | Scopes::push(), "follow push"),
+ ];
+
+ for (a, b) in &tests {
+ let ser = serde_json::to_string(&a).expect("Couldn't serialize Scopes");
+ let expected = format!("\"{}\"", b);
+ assert_eq!(&ser, &expected);
+ }
}
}