mastodon_async/
scopes.rs

1use std::{
2    cmp::{Ordering, PartialEq, PartialOrd},
3    collections::HashSet,
4    fmt,
5    ops::BitOr,
6    str::FromStr,
7};
8
9use serde::ser::{Serialize, Serializer};
10
11use crate::errors::Error;
12use derive_is_enum_variant::is_enum_variant;
13use serde::{
14    de::{self, Visitor},
15    Deserialize, Deserializer,
16};
17
18/// Represents a set of OAuth scopes
19///
20/// // Example
21///
22/// ```rust
23/// use mastodon_async::prelude::*;
24///
25/// let read = Scopes::read_all();
26/// let write = Scopes::write_all();
27/// let follow = Scopes::follow();
28/// let all = read | write | follow;
29/// ```
30#[derive(Clone)]
31pub struct Scopes {
32    scopes: HashSet<Scope>,
33}
34
35impl FromStr for Scopes {
36    type Err = Error;
37
38    fn from_str(s: &str) -> Result<Scopes, Self::Err> {
39        let mut set = HashSet::new();
40        for scope in s.split_whitespace() {
41            let scope = Scope::from_str(scope)?;
42            set.insert(scope);
43        }
44        Ok(Scopes { scopes: set })
45    }
46}
47
48impl Serialize for Scopes {
49    fn serialize<S>(&self, serializer: S) -> ::std::result::Result<S::Ok, S::Error>
50    where
51        S: Serializer,
52    {
53        let repr = format!("{self}");
54        serializer.serialize_str(&repr)
55    }
56}
57
58struct DeserializeScopesVisitor;
59
60impl<'de> Visitor<'de> for DeserializeScopesVisitor {
61    type Value = Scopes;
62
63    fn expecting(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
64        write!(formatter, "space separated scopes")
65    }
66
67    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
68    where
69        E: de::Error,
70    {
71        Scopes::from_str(v).map_err(de::Error::custom)
72    }
73}
74
75impl<'de> Deserialize<'de> for Scopes {
76    fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
77    where
78        D: Deserializer<'de>,
79    {
80        deserializer.deserialize_str(DeserializeScopesVisitor)
81    }
82}
83
84impl Scopes {
85    /// Represents all available oauth scopes: "read write follow push"
86    ///
87    /// ```
88    /// use mastodon_async::scopes::Scopes;
89    ///
90    /// let scope = Scopes::all();
91    /// assert_eq!(&format!("{}", scope), "read write follow push");
92    /// ```
93    pub fn all() -> Scopes {
94        Scopes::read_all() | Scopes::write_all() | Scopes::follow() | Scopes::push()
95    }
96
97    /// Represents the full "read" scope
98    ///
99    /// ```
100    /// use mastodon_async::scopes::Scopes;
101    ///
102    /// let scope = Scopes::read_all();
103    /// assert_eq!(&format!("{}", scope), "read");
104    /// ```
105    pub fn read_all() -> Scopes {
106        Scopes::_read(None)
107    }
108
109    /// Represents a specific "read:___" scope
110    ///
111    /// ```
112    /// use mastodon_async::scopes::{Read, Scopes};
113    ///
114    /// let scope = Scopes::read(Read::Accounts);
115    /// assert_eq!(&format!("{}", scope), "read:accounts");
116    /// ```
117    pub fn read(subscope: Read) -> Scopes {
118        Scopes::_read(Some(subscope))
119    }
120
121    /// Represents the full "write" scope
122    ///
123    /// ```
124    /// use mastodon_async::scopes::Scopes;
125    ///
126    /// let scope = Scopes::write_all();
127    /// assert_eq!(&format!("{}", scope), "write");
128    /// ```
129    pub fn write_all() -> Scopes {
130        Scopes::_write(None)
131    }
132
133    /// Represents a specific "write:___" scope
134    ///
135    /// ```
136    /// use mastodon_async::scopes::{Scopes, Write};
137    ///
138    /// let scope = Scopes::write(Write::Accounts);
139    /// assert_eq!(&format!("{}", scope), "write:accounts");
140    /// ```
141    pub fn write(subscope: Write) -> Scopes {
142        Scopes::_write(Some(subscope))
143    }
144
145    /// Represents the "follow" scope
146    ///
147    /// ```
148    /// use mastodon_async::scopes::Scopes;
149    ///
150    /// let scope = Scopes::follow();
151    /// assert_eq!(&format!("{}", scope), "follow");
152    /// ```
153    pub fn follow() -> Scopes {
154        Scopes::new(Scope::Follow)
155    }
156
157    /// Represents the full "push" scope
158    ///
159    /// ```
160    /// use mastodon_async::scopes::Scopes;
161    ///
162    /// let scope = Scopes::push();
163    /// assert_eq!(&format!("{}", scope), "push");
164    /// ```
165    pub fn push() -> Scopes {
166        Scopes::new(Scope::Push)
167    }
168
169    /// Combines 2 scopes together
170    ///
171    /// // Example
172    ///
173    /// ```rust
174    /// use mastodon_async::prelude::*;
175    ///
176    /// let read = Scopes::read_all();
177    /// let write = Scopes::write_all();
178    /// let read_write = read.and(write);
179    /// ```
180    pub fn and(self, other: Scopes) -> Scopes {
181        let new_set: HashSet<_> = self.scopes.union(&other.scopes).copied().collect();
182        Scopes { scopes: new_set }
183    }
184
185    fn _write(subscope: Option<Write>) -> Scopes {
186        Scopes::new(Scope::Write(subscope))
187    }
188
189    fn _read(subscope: Option<Read>) -> Scopes {
190        Scopes::new(Scope::Read(subscope))
191    }
192
193    fn new(scope: Scope) -> Scopes {
194        let mut set = HashSet::new();
195        set.insert(scope);
196        Scopes { scopes: set }
197    }
198}
199
200impl BitOr for Scopes {
201    type Output = Scopes;
202
203    fn bitor(self, other: Scopes) -> Self::Output {
204        self.and(other)
205    }
206}
207
208impl PartialEq for Scopes {
209    fn eq(&self, other: &Scopes) -> bool {
210        self.scopes
211            .symmetric_difference(&other.scopes)
212            .next()
213            .is_none()
214    }
215}
216
217impl Default for Scopes {
218    fn default() -> Scopes {
219        Scopes::read_all()
220    }
221}
222
223impl fmt::Debug for Scopes {
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        write!(f, "[")?;
226        for scope in &self.scopes {
227            write!(f, "{:?}", &scope)?;
228        }
229        Ok(write!(f, "]")?)
230    }
231}
232
233impl fmt::Display for Scopes {
234    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
235        let mut start = true;
236        let scopes = {
237            let mut scopes = self.scopes.iter().collect::<Vec<_>>();
238            scopes.sort();
239            scopes
240        };
241        for scope in &scopes {
242            if !start {
243                write!(f, " ")?;
244            } else {
245                start = false;
246            }
247            write!(f, "{}", &scope)?;
248        }
249        Ok(())
250    }
251}
252
253/// Permission scope of the application.
254/// [Details on what each permission provides][1]
255/// [1]: https://github.com/tootsuite/documentation/blob/master/Using-the-API/OAuth-details.md)
256#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, is_enum_variant)]
257#[serde(rename_all = "lowercase")]
258pub enum Scope {
259    /// Read only permissions.
260    Read(Option<Read>),
261    /// Write only permissions.
262    Write(Option<Write>),
263    /// Only permission to add and remove followers.
264    Follow,
265    /// Push permissions
266    Push,
267}
268
269impl FromStr for Scope {
270    type Err = Error;
271
272    fn from_str(s: &str) -> Result<Scope, Self::Err> {
273        Ok(match s {
274            "read" => Scope::Read(None),
275            "write" => Scope::Write(None),
276            "follow" => Scope::Follow,
277            "push" => Scope::Push,
278            read if read.starts_with("read:") => {
279                let r: Read = Read::from_str(&read[5..])?;
280                Scope::Read(Some(r))
281            }
282            write if write.starts_with("write:") => {
283                let w: Write = Write::from_str(&write[6..])?;
284                Scope::Write(Some(w))
285            }
286            _ => return Err(Error::Other("Unknown scope".to_string())),
287        })
288    }
289}
290
291impl PartialOrd for Scope {
292    fn partial_cmp(&self, other: &Scope) -> Option<Ordering> {
293        Some(self.cmp(other))
294    }
295}
296
297impl Ord for Scope {
298    fn cmp(&self, other: &Scope) -> Ordering {
299        match (*self, *other) {
300            (Scope::Read(None), Scope::Read(None)) => Ordering::Equal,
301            (Scope::Read(None), Scope::Read(Some(..))) => Ordering::Less,
302            (Scope::Read(Some(..)), Scope::Read(None)) => Ordering::Greater,
303            (Scope::Read(Some(ref a)), Scope::Read(Some(ref b))) => a.cmp(b),
304
305            (Scope::Write(None), Scope::Write(None)) => Ordering::Equal,
306            (Scope::Write(None), Scope::Write(Some(..))) => Ordering::Less,
307            (Scope::Write(Some(..)), Scope::Write(None)) => Ordering::Greater,
308            (Scope::Write(Some(ref a)), Scope::Write(Some(ref b))) => a.cmp(b),
309
310            (Scope::Read(..), Scope::Write(..)) => Ordering::Less,
311            (Scope::Read(..), Scope::Follow) => Ordering::Less,
312            (Scope::Read(..), Scope::Push) => Ordering::Less,
313
314            (Scope::Write(..), Scope::Read(..)) => Ordering::Greater,
315            (Scope::Write(..), Scope::Follow) => Ordering::Less,
316            (Scope::Write(..), Scope::Push) => Ordering::Less,
317
318            (Scope::Follow, Scope::Read(..)) => Ordering::Greater,
319            (Scope::Follow, Scope::Write(..)) => Ordering::Greater,
320            (Scope::Follow, Scope::Follow) => Ordering::Equal,
321            (Scope::Follow, Scope::Push) => Ordering::Less,
322
323            (Scope::Push, Scope::Push) => Ordering::Equal,
324            (Scope::Push, _) => Ordering::Greater,
325        }
326    }
327}
328
329impl fmt::Display for Scope {
330    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331        use self::Scope::*;
332        let s = match *self {
333            Read(Some(ref r)) => return fmt::Display::fmt(r, f),
334            Read(None) => "read",
335            Write(Some(ref w)) => return fmt::Display::fmt(w, f),
336            Write(None) => "write",
337            Follow => "follow",
338            Push => "push",
339        };
340        write!(f, "{s}")
341    }
342}
343
344impl Default for Scope {
345    fn default() -> Self {
346        Scope::Read(None)
347    }
348}
349
350/// Represents the granular "read:___" oauth scopes
351#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, is_enum_variant)]
352pub enum Read {
353    /// Accounts
354    #[serde(rename = "accounts")]
355    Accounts,
356    /// Blocks
357    #[serde(rename = "blocks")]
358    Blocks,
359    /// Favourites
360    #[serde(rename = "favourites")]
361    Favourites,
362    /// Filters
363    #[serde(rename = "filters")]
364    Filters,
365    /// Follows
366    #[serde(rename = "follows")]
367    Follows,
368    /// Lists
369    #[serde(rename = "lists")]
370    Lists,
371    /// Mutes
372    #[serde(rename = "mutes")]
373    Mutes,
374    /// Notifications
375    #[serde(rename = "notifications")]
376    Notifications,
377    /// Reports
378    #[serde(rename = "reports")]
379    Reports,
380    /// Search
381    #[serde(rename = "search")]
382    Search,
383    /// Statuses
384    #[serde(rename = "statuses")]
385    Statuses,
386}
387
388impl FromStr for Read {
389    type Err = Error;
390
391    fn from_str(s: &str) -> Result<Read, Self::Err> {
392        Ok(match s {
393            "accounts" => Read::Accounts,
394            "blocks" => Read::Blocks,
395            "favourites" => Read::Favourites,
396            "filters" => Read::Filters,
397            "follows" => Read::Follows,
398            "lists" => Read::Lists,
399            "mutes" => Read::Mutes,
400            "notifications" => Read::Notifications,
401            "reports" => Read::Reports,
402            "search" => Read::Search,
403            "statuses" => Read::Statuses,
404            _ => return Err(Error::Other("Unknown 'read' subcategory".to_string())),
405        })
406    }
407}
408
409impl PartialOrd for Read {
410    fn partial_cmp(&self, other: &Read) -> Option<Ordering> {
411        Some(self.cmp(other))
412    }
413}
414
415impl Ord for Read {
416    fn cmp(&self, other: &Read) -> Ordering {
417        let a = format!("{self:?}");
418        let b = format!("{other:?}");
419        a.cmp(&b)
420    }
421}
422
423impl fmt::Display for Read {
424    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
425        write!(
426            f,
427            "read:{}",
428            match *self {
429                Read::Accounts => "accounts",
430                Read::Blocks => "blocks",
431                Read::Favourites => "favourites",
432                Read::Filters => "filters",
433                Read::Follows => "follows",
434                Read::Lists => "lists",
435                Read::Mutes => "mutes",
436                Read::Notifications => "notifications",
437                Read::Reports => "reports",
438                Read::Search => "search",
439                Read::Statuses => "statuses",
440            }
441        )
442    }
443}
444
445/// Represents the granular "write:___" oauth scopes
446#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, is_enum_variant)]
447pub enum Write {
448    /// Accounts
449    #[serde(rename = "accounts")]
450    Accounts,
451    /// Blocks
452    #[serde(rename = "blocks")]
453    Blocks,
454    /// Favourites
455    #[serde(rename = "favourites")]
456    Favourites,
457    /// Filters
458    #[serde(rename = "filters")]
459    Filters,
460    /// Follows
461    #[serde(rename = "follows")]
462    Follows,
463    /// Lists
464    #[serde(rename = "lists")]
465    Lists,
466    /// Media
467    #[serde(rename = "media")]
468    Media,
469    /// Mutes
470    #[serde(rename = "mutes")]
471    Mutes,
472    /// Notifications
473    #[serde(rename = "notifications")]
474    Notifications,
475    /// Reports
476    #[serde(rename = "reports")]
477    Reports,
478    /// Statuses
479    #[serde(rename = "statuses")]
480    Statuses,
481}
482
483impl FromStr for Write {
484    type Err = Error;
485
486    fn from_str(s: &str) -> Result<Write, Self::Err> {
487        Ok(match s {
488            "accounts" => Write::Accounts,
489            "blocks" => Write::Blocks,
490            "favourites" => Write::Favourites,
491            "filters" => Write::Filters,
492            "follows" => Write::Follows,
493            "lists" => Write::Lists,
494            "media" => Write::Media,
495            "mutes" => Write::Mutes,
496            "notifications" => Write::Notifications,
497            "reports" => Write::Reports,
498            "statuses" => Write::Statuses,
499            _ => return Err(Error::Other("Unknown 'write' subcategory".to_string())),
500        })
501    }
502}
503
504impl PartialOrd for Write {
505    fn partial_cmp(&self, other: &Write) -> Option<Ordering> {
506        Some(self.cmp(other))
507    }
508}
509
510impl Ord for Write {
511    fn cmp(&self, other: &Write) -> Ordering {
512        let a = format!("{self:?}");
513        let b = format!("{other:?}");
514        a.cmp(&b)
515    }
516}
517
518impl fmt::Display for Write {
519    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
520        write!(
521            f,
522            "write:{}",
523            match *self {
524                Write::Accounts => "accounts",
525                Write::Blocks => "blocks",
526                Write::Favourites => "favourites",
527                Write::Filters => "filters",
528                Write::Follows => "follows",
529                Write::Lists => "lists",
530                Write::Media => "media",
531                Write::Mutes => "mutes",
532                Write::Notifications => "notifications",
533                Write::Reports => "reports",
534                Write::Statuses => "statuses",
535            }
536        )
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543    use serde_json;
544
545    #[test]
546    fn test_write_cmp() {
547        let tests = [
548            (Write::Accounts, Write::Blocks),
549            (Write::Blocks, Write::Favourites),
550            (Write::Favourites, Write::Filters),
551            (Write::Filters, Write::Follows),
552            (Write::Follows, Write::Lists),
553            (Write::Lists, Write::Media),
554            (Write::Media, Write::Mutes),
555            (Write::Mutes, Write::Notifications),
556            (Write::Notifications, Write::Reports),
557            (Write::Reports, Write::Statuses),
558        ];
559
560        for (a, b) in &tests {
561            assert!(a < b);
562            assert!(b > a);
563        }
564    }
565
566    #[test]
567    fn test_read_cmp() {
568        let tests = [
569            (Read::Accounts, Read::Blocks),
570            (Read::Blocks, Read::Favourites),
571            (Read::Favourites, Read::Filters),
572            (Read::Filters, Read::Follows),
573            (Read::Follows, Read::Lists),
574            (Read::Lists, Read::Mutes),
575            (Read::Mutes, Read::Notifications),
576            (Read::Notifications, Read::Reports),
577            (Read::Reports, Read::Search),
578            (Read::Search, Read::Statuses),
579        ];
580        for (a, b) in &tests {
581            assert!(a < b);
582            assert!(b > a);
583        }
584    }
585
586    #[test]
587    fn test_scope_cmp() {
588        let tests = [
589            (Scope::Read(None), Scope::Read(Some(Read::Accounts))),
590            (Scope::Read(None), Scope::Read(Some(Read::Blocks))),
591            (Scope::Read(None), Scope::Read(Some(Read::Favourites))),
592            (Scope::Read(None), Scope::Read(Some(Read::Filters))),
593            (Scope::Read(None), Scope::Read(Some(Read::Follows))),
594            (Scope::Read(None), Scope::Read(Some(Read::Lists))),
595            (Scope::Read(None), Scope::Read(Some(Read::Mutes))),
596            (Scope::Read(None), Scope::Read(Some(Read::Notifications))),
597            (Scope::Read(None), Scope::Read(Some(Read::Reports))),
598            (Scope::Read(None), Scope::Read(Some(Read::Search))),
599            (Scope::Read(None), Scope::Read(Some(Read::Statuses))),
600            (Scope::Read(Some(Read::Statuses)), Scope::Write(None)),
601            (Scope::Read(Some(Read::Mutes)), Scope::Follow),
602            (Scope::Read(None), Scope::Push),
603            (Scope::Write(None), Scope::Write(Some(Write::Accounts))),
604            (Scope::Write(None), Scope::Write(Some(Write::Blocks))),
605            (Scope::Write(None), Scope::Write(Some(Write::Favourites))),
606            (Scope::Write(None), Scope::Write(Some(Write::Filters))),
607            (Scope::Write(None), Scope::Write(Some(Write::Follows))),
608            (Scope::Write(None), Scope::Write(Some(Write::Lists))),
609            (Scope::Write(None), Scope::Write(Some(Write::Media))),
610            (Scope::Write(None), Scope::Write(Some(Write::Mutes))),
611            (Scope::Write(None), Scope::Write(Some(Write::Notifications))),
612            (Scope::Write(None), Scope::Write(Some(Write::Reports))),
613            (Scope::Write(None), Scope::Write(Some(Write::Statuses))),
614            (Scope::Write(Some(Write::Statuses)), Scope::Follow),
615            (Scope::Write(Some(Write::Follows)), Scope::Push),
616        ];
617
618        for (a, b) in &tests {
619            assert!(a < b);
620        }
621    }
622
623    #[test]
624    fn test_scope_display() {
625        let values = [
626            Scope::Read(None),
627            Scope::Read(Some(Read::Accounts)),
628            Scope::Read(Some(Read::Blocks)),
629            Scope::Read(Some(Read::Favourites)),
630            Scope::Read(Some(Read::Filters)),
631            Scope::Read(Some(Read::Follows)),
632            Scope::Read(Some(Read::Lists)),
633            Scope::Read(Some(Read::Mutes)),
634            Scope::Read(Some(Read::Notifications)),
635            Scope::Read(Some(Read::Reports)),
636            Scope::Read(Some(Read::Search)),
637            Scope::Read(Some(Read::Statuses)),
638            Scope::Write(None),
639            Scope::Write(Some(Write::Accounts)),
640            Scope::Write(Some(Write::Blocks)),
641            Scope::Write(Some(Write::Favourites)),
642            Scope::Write(Some(Write::Filters)),
643            Scope::Write(Some(Write::Follows)),
644            Scope::Write(Some(Write::Lists)),
645            Scope::Write(Some(Write::Media)),
646            Scope::Write(Some(Write::Mutes)),
647            Scope::Write(Some(Write::Notifications)),
648            Scope::Write(Some(Write::Reports)),
649            Scope::Write(Some(Write::Statuses)),
650            Scope::Follow,
651            Scope::Push,
652        ];
653
654        let expecteds = [
655            "read".to_string(),
656            "read:accounts".to_string(),
657            "read:blocks".to_string(),
658            "read:favourites".to_string(),
659            "read:filters".to_string(),
660            "read:follows".to_string(),
661            "read:lists".to_string(),
662            "read:mutes".to_string(),
663            "read:notifications".to_string(),
664            "read:reports".to_string(),
665            "read:search".to_string(),
666            "read:statuses".to_string(),
667            "write".to_string(),
668            "write:accounts".to_string(),
669            "write:blocks".to_string(),
670            "write:favourites".to_string(),
671            "write:filters".to_string(),
672            "write:follows".to_string(),
673            "write:lists".to_string(),
674            "write:media".to_string(),
675            "write:mutes".to_string(),
676            "write:notifications".to_string(),
677            "write:reports".to_string(),
678            "write:statuses".to_string(),
679            "follow".to_string(),
680            "push".to_string(),
681        ];
682
683        let tests = values.iter().zip(expecteds.iter());
684
685        for (value, expected) in tests {
686            let result = value.to_string();
687            assert_eq!(&result, expected);
688        }
689    }
690
691    #[test]
692    fn test_scopes_default() {
693        let default: Scope = Default::default();
694        assert_eq!(default, Scope::Read(None));
695    }
696
697    #[test]
698    fn test_scopes_display() {
699        let tests = [
700            (
701                Scopes::read(Read::Accounts) | Scopes::follow(),
702                "read:accounts follow",
703            ),
704            (
705                Scopes::read(Read::Follows) | Scopes::read(Read::Accounts) | Scopes::write_all(),
706                "read:accounts read:follows write",
707            ),
708        ];
709
710        for (a, b) in &tests {
711            assert_eq!(&format!("{}", a), b);
712        }
713    }
714
715    #[test]
716    fn test_scopes_serialize_deserialize() {
717        let tests = [
718            (
719                Scopes::read_all() | Scopes::write(Write::Notifications) | Scopes::follow(),
720                "read write:notifications follow",
721            ),
722            (Scopes::follow() | Scopes::push(), "follow push"),
723        ];
724
725        for (a, b) in &tests {
726            let ser = serde_json::to_string(&a).expect("Couldn't serialize Scopes");
727            let expected = format!("\"{}\"", b);
728            assert_eq!(&ser, &expected);
729
730            let des: Scopes = serde_json::from_str(&ser).expect("Couldn't deserialize Scopes");
731            assert_eq!(&des, a);
732        }
733    }
734
735    #[test]
736    fn test_scope_from_str() {
737        let tests = [
738            ("read", Scope::Read(None)),
739            ("read:accounts", Scope::Read(Some(Read::Accounts))),
740            ("read:blocks", Scope::Read(Some(Read::Blocks))),
741            ("read:favourites", Scope::Read(Some(Read::Favourites))),
742            ("read:filters", Scope::Read(Some(Read::Filters))),
743            ("read:follows", Scope::Read(Some(Read::Follows))),
744            ("read:lists", Scope::Read(Some(Read::Lists))),
745            ("read:mutes", Scope::Read(Some(Read::Mutes))),
746            ("read:notifications", Scope::Read(Some(Read::Notifications))),
747            ("read:reports", Scope::Read(Some(Read::Reports))),
748            ("read:search", Scope::Read(Some(Read::Search))),
749            ("read:statuses", Scope::Read(Some(Read::Statuses))),
750            ("write", Scope::Write(None)),
751            ("write:accounts", Scope::Write(Some(Write::Accounts))),
752            ("write:blocks", Scope::Write(Some(Write::Blocks))),
753            ("write:favourites", Scope::Write(Some(Write::Favourites))),
754            ("write:filters", Scope::Write(Some(Write::Filters))),
755            ("write:follows", Scope::Write(Some(Write::Follows))),
756            ("write:lists", Scope::Write(Some(Write::Lists))),
757            ("write:media", Scope::Write(Some(Write::Media))),
758            ("write:mutes", Scope::Write(Some(Write::Mutes))),
759            (
760                "write:notifications",
761                Scope::Write(Some(Write::Notifications)),
762            ),
763            ("write:reports", Scope::Write(Some(Write::Reports))),
764            ("write:statuses", Scope::Write(Some(Write::Statuses))),
765            ("follow", Scope::Follow),
766            ("push", Scope::Push),
767        ];
768        for (source, expected) in &tests {
769            let result =
770                Scope::from_str(source).unwrap_or_else(|_| panic!("Couldn't parse '{}'", &source));
771            assert_eq!(result, *expected);
772        }
773    }
774
775    #[test]
776    fn test_scopes_str_round_trip() {
777        let original = "read write follow push";
778        let scopes = Scopes::from_str(original).expect("Couldn't convert to Scopes");
779        let result = format!("{}", scopes);
780        assert_eq!(original, result);
781    }
782}