mastodon_async/
apps.rs

1use std::borrow::Cow;
2
3use serde::Serialize;
4// use try_from::TryInto;
5
6use crate::{
7    errors::{Error, Result},
8    scopes::Scopes,
9};
10
11/// Represents an application that can be registered with a mastodon instance
12#[derive(Clone, Debug, Default, Serialize, PartialEq)]
13pub struct App {
14    client_name: String,
15    redirect_uris: String,
16    scopes: Scopes,
17    #[serde(skip_serializing_if = "Option::is_none")]
18    website: Option<String>,
19}
20
21impl App {
22    /// Get an AppBuilder object
23    ///
24    /// // Example
25    ///
26    /// ```
27    /// use mastodon_async::apps::App;
28    ///
29    /// let mut builder = App::builder();
30    /// ```
31    pub fn builder<'a>() -> AppBuilder<'a> {
32        AppBuilder::new()
33    }
34
35    /// Retrieve the list of scopes that apply to this App
36    ///
37    /// // Example
38    ///
39    /// ```
40    /// use mastodon_async::{apps::App, scopes::Scopes};
41    ///
42    /// let mut builder = App::builder();
43    /// builder.client_name("mastodon-async-test");
44    /// let app = builder.build().unwrap();
45    /// let scopes = app.scopes();
46    /// assert_eq!(scopes, &Scopes::read_all());
47    /// ```
48    pub fn scopes(&self) -> &Scopes {
49        &self.scopes
50    }
51}
52
53/// Builder struct for defining your application.
54/// ```
55/// use mastodon_async::{apps::App};
56///
57/// let mut builder = App::builder();
58/// builder.client_name("mastodon-async_test");
59/// let app = builder.build().unwrap();
60/// ```
61#[derive(Clone, Debug, Default, PartialEq, Serialize)]
62pub struct AppBuilder<'a> {
63    client_name: Option<Cow<'a, str>>,
64    redirect_uris: Option<Cow<'a, str>>,
65    scopes: Option<Scopes>,
66    website: Option<Cow<'a, str>>,
67}
68
69impl<'a> AppBuilder<'a> {
70    /// Creates a new AppBuilder object
71    pub fn new() -> Self {
72        Default::default()
73    }
74
75    /// Name of the application. Will be displayed when the user is deciding to
76    /// grant permission.
77    ///
78    /// In order to turn this builder into an App, this needs to be provided
79    pub fn client_name<I: Into<Cow<'a, str>>>(&mut self, name: I) -> &mut Self {
80        self.client_name = Some(name.into());
81        self
82    }
83
84    /// Where the user should be redirected after authorization
85    ///
86    /// If none is specified, the default is `urn:ietf:wg:oauth:2.0:oob`
87    pub fn redirect_uris<I: Into<Cow<'a, str>>>(&mut self, uris: I) -> &mut Self {
88        self.redirect_uris = Some(uris.into());
89        self
90    }
91
92    /// Permission scope of the application.
93    ///
94    /// IF none is specified, the default is Scopes::read_all()
95    pub fn scopes(&mut self, scopes: Scopes) -> &mut Self {
96        self.scopes = Some(scopes);
97        self
98    }
99
100    /// URL to the homepage of your application.
101    pub fn website<I: Into<Cow<'a, str>>>(&mut self, website: I) -> &mut Self {
102        self.website = Some(website.into());
103        self
104    }
105
106    /// Attempts to convert this build into an `App`
107    ///
108    /// Will fail if no `client_name` was provided
109    pub fn build(self) -> Result<App> {
110        Ok(App {
111            client_name: self
112                .client_name
113                .ok_or(Error::MissingField("client_name"))?
114                .into(),
115            redirect_uris: self
116                .redirect_uris
117                .unwrap_or_else(|| "urn:ietf:wg:oauth:2.0:oob".into())
118                .into(),
119            scopes: self.scopes.unwrap_or_else(Scopes::read_all),
120            website: self.website.map(|s| s.into()),
121        })
122    }
123}
124
125impl<'a> TryInto<App> for AppBuilder<'a> {
126    type Error = Error;
127
128    fn try_into(self) -> Result<App> {
129        self.build()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_app_builder() {
139        let builder = App::builder();
140        assert_eq!(builder, AppBuilder::new());
141    }
142
143    #[test]
144    fn test_app_scopes() {
145        let mut builder = App::builder();
146        builder.client_name("test").scopes(Scopes::all());
147        let app = builder.build().expect("Couldn't build App");
148        assert_eq!(app.scopes(), &Scopes::all());
149    }
150
151    #[test]
152    fn test_app_builder_all_methods() {
153        let mut builder = AppBuilder::new();
154        builder.client_name("foo-test");
155        builder.redirect_uris("http://example.com");
156        builder.scopes(Scopes::read_all() | Scopes::write_all());
157        builder.website("https://example.com");
158        let app = builder.build().expect("Couldn't build App");
159        assert_eq!(
160            app,
161            App {
162                client_name: "foo-test".to_string(),
163                redirect_uris: "http://example.com".to_string(),
164                scopes: Scopes::read_all() | Scopes::write_all(),
165                website: Some("https://example.com".to_string()),
166            }
167        );
168    }
169
170    #[test]
171    #[should_panic]
172    fn test_app_builder_build_fails_if_no_client_name_1() {
173        App::builder().build().expect("no client-name");
174    }
175
176    #[test]
177    #[should_panic]
178    fn test_app_builder_build_fails_if_no_client_name_2() {
179        let mut builder = App::builder();
180        builder
181            .website("https://example.com")
182            .redirect_uris("https://example.com")
183            .scopes(Scopes::all());
184        builder.build().expect("no client-name");
185    }
186
187    #[test]
188    fn test_app_try_into_app() {
189        let app = App {
190            client_name: "foo-test".to_string(),
191            redirect_uris: "http://example.com".to_string(),
192            scopes: Scopes::all(),
193            website: None,
194        };
195        let expected = app.clone();
196        #[allow(clippy::useless_conversion)]
197        let result = app.try_into().expect("Couldn't make App into App");
198        assert_eq!(expected, result);
199    }
200
201    #[test]
202    fn test_app_builder_try_into_app() {
203        let mut builder = App::builder();
204        builder
205            .client_name("foo-test")
206            .redirect_uris("http://example.com")
207            .scopes(Scopes::all());
208        let expected = App {
209            client_name: "foo-test".to_string(),
210            redirect_uris: "http://example.com".to_string(),
211            scopes: Scopes::all(),
212            website: None,
213        };
214        let result = builder
215            .try_into()
216            .expect("Couldn't make AppBuilder into App");
217        assert_eq!(expected, result);
218    }
219}