feat(scopes): Implement granular OAuth scopes
BREAKING CHANGE: Applications that use the `Scopes` data structure will have minor changes to make Closes #44
This commit is contained in:
@@ -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()?;
|
||||
|
||||
+15
-15
@@ -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
|
||||
|
||||
+5
-5
@@ -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]
|
||||
|
||||
+626
-54
@@ -1,82 +1,619 @@
|
||||
use std::fmt;
|
||||
use std::{
|
||||
cmp::{Ordering, PartialEq, PartialOrd},
|
||||
collections::HashSet,
|
||||
fmt,
|
||||
ops::BitOr,
|
||||
};
|
||||
|
||||
/// 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,
|
||||
/// Read only permissions.
|
||||
#[serde(rename = "read")]
|
||||
Read,
|
||||
/// Read & Follow permissions.
|
||||
#[serde(rename = "read follow")]
|
||||
ReadFollow,
|
||||
/// Read & Write permissions.
|
||||
#[serde(rename = "read write")]
|
||||
ReadWrite,
|
||||
/// Write only permissions.
|
||||
#[serde(rename = "write")]
|
||||
Write,
|
||||
/// Write & Follow permissions.
|
||||
#[serde(rename = "write follow")]
|
||||
WriteFollow,
|
||||
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 {
|
||||
use self::Scopes::*;
|
||||
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, Eq, Hash, Serialize)]
|
||||
enum Scope {
|
||||
/// Read only permissions.
|
||||
#[serde(rename = "read")]
|
||||
Read(Option<Read>),
|
||||
/// Write only permissions.
|
||||
#[serde(rename = "write")]
|
||||
Write(Option<Write>),
|
||||
/// Only permission to add and remove followers.
|
||||
#[serde(rename = "follow")]
|
||||
Follow,
|
||||
/// Push permissions
|
||||
#[serde(rename = "push")]
|
||||
Push,
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user