1use std::borrow::Cow;
2
3use serde::Serialize;
4use crate::{
7 errors::{Error, Result},
8 scopes::Scopes,
9};
10
11#[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 pub fn builder<'a>() -> AppBuilder<'a> {
32 AppBuilder::new()
33 }
34
35 pub fn scopes(&self) -> &Scopes {
49 &self.scopes
50 }
51}
52
53#[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 pub fn new() -> Self {
72 Default::default()
73 }
74
75 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 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 pub fn scopes(&mut self, scopes: Scopes) -> &mut Self {
96 self.scopes = Some(scopes);
97 self
98 }
99
100 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 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}