1mod client_metadata;
2mod metadata;
3mod request;
4mod response;
5mod token;
6
7use crate::scopes::{ParseError, Scope, Scopes};
8
9pub use self::client_metadata::*;
10pub use self::metadata::*;
11pub use self::request::*;
12pub use self::response::*;
13pub use self::token::*;
14use jacquard_common::CowStr;
15use jacquard_common::IntoStatic;
16use jacquard_common::bos::{BosStr, DefaultStr};
17use jacquard_common::deps::fluent_uri::Uri;
18use serde::Deserialize;
19use smol_str::SmolStr;
20
21#[derive(Debug, Deserialize, Clone, Copy)]
26pub enum AuthorizeOptionPrompt {
27 Login,
29 None,
31 Consent,
33 SelectAccount,
35}
36
37impl From<AuthorizeOptionPrompt> for CowStr<'static> {
38 fn from(value: AuthorizeOptionPrompt) -> Self {
39 CowStr::new_static(value.into())
40 }
41}
42
43impl From<AuthorizeOptionPrompt> for SmolStr {
44 fn from(value: AuthorizeOptionPrompt) -> Self {
45 SmolStr::new_static(value.into())
46 }
47}
48
49impl From<AuthorizeOptionPrompt> for &'static str {
50 fn from(value: AuthorizeOptionPrompt) -> Self {
51 match value {
52 AuthorizeOptionPrompt::Login => "login",
53 AuthorizeOptionPrompt::None => "none",
54 AuthorizeOptionPrompt::Consent => "consent",
55 AuthorizeOptionPrompt::SelectAccount => "select_account",
56 }
57 }
58}
59
60#[derive(Debug)]
62pub struct AuthorizeOptions<S: BosStr = DefaultStr>
63where
64 S: AsRef<str>,
65{
66 pub redirect_uri: Option<Uri<String>>,
68 pub scopes: Scopes<S>,
70 pub prompt: Option<AuthorizeOptionPrompt>,
72 pub state: Option<S>,
74}
75
76impl Default for AuthorizeOptions<DefaultStr> {
77 fn default() -> Self {
78 Self {
79 redirect_uri: None,
80 scopes: Scopes::empty(),
81 prompt: None,
82 state: None,
83 }
84 }
85}
86
87impl<S: BosStr + AsRef<str>> AuthorizeOptions<S> {
88 pub fn with_prompt(mut self, prompt: AuthorizeOptionPrompt) -> Self {
90 self.prompt = Some(prompt);
91 self
92 }
93
94 pub fn with_state(mut self, state: S) -> Self {
96 self.state = Some(state);
97 self
98 }
99
100 pub fn with_redirect_uri(mut self, redirect_uri: Uri<String>) -> Self {
102 self.redirect_uri = Some(redirect_uri);
103 self
104 }
105
106 pub fn with_scopes(mut self, scopes: Scopes<S>) -> Self {
108 self.scopes = scopes;
109 self
110 }
111}
112
113impl AuthorizeOptions<DefaultStr> {
114 pub fn with_scope_str(mut self, scopes: impl AsRef<str>) -> Result<Self, ParseError> {
116 self.scopes = Scopes::new(SmolStr::new(scopes.as_ref()))?;
117 Ok(self)
118 }
119
120 pub fn with_scope(self, scope: Scope<SmolStr>) -> Result<Self, ParseError> {
122 self.with_scope_iter([scope])
123 }
124
125 pub fn with_scope_iter<I>(mut self, scopes: I) -> Result<Self, ParseError>
127 where
128 I: IntoIterator<Item = Scope<SmolStr>>,
129 {
130 self.scopes = Scopes::from_scopes(scopes)?;
131 Ok(self)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[test]
140 fn authorize_options_accept_scope_string() {
141 let opts = AuthorizeOptions::default()
142 .with_scope_str("rpc:* atproto")
143 .unwrap();
144
145 assert_eq!(opts.scopes.to_normalized_string(), "atproto rpc:*");
146 }
147
148 #[test]
149 fn authorize_options_accept_typed_scopes() {
150 let opts = AuthorizeOptions::default()
151 .with_scope_iter([
152 Scope::atproto(),
153 Scope::rpc("app.bsky.feed.getTimeline").unwrap(),
154 Scope::repo_create("app.bsky.feed.post").unwrap(),
155 ])
156 .unwrap();
157
158 assert_eq!(
159 opts.scopes.to_normalized_string(),
160 "atproto repo:app.bsky.feed.post?action=create rpc:app.bsky.feed.getTimeline"
161 );
162 }
163
164 #[test]
165 fn authorize_options_accept_built_scopes() {
166 let scopes = Scopes::builder()
167 .atproto()
168 .transition_generic()
169 .rpc("app.bsky.feed.getTimeline")
170 .unwrap()
171 .build()
172 .unwrap();
173 let opts = AuthorizeOptions::default().with_scopes(scopes);
174
175 assert_eq!(
176 opts.scopes.to_normalized_string(),
177 "atproto rpc:app.bsky.feed.getTimeline transition:generic"
178 );
179 }
180}
181
182#[derive(Debug, Deserialize)]
184#[serde(bound(deserialize = "S: serde::Deserialize<'de> + BosStr"))]
185pub struct CallbackParams<S: BosStr = DefaultStr> {
186 pub code: S,
188 pub state: Option<S>,
191 pub iss: Option<S>,
193}
194
195impl<S: BosStr + IntoStatic> IntoStatic for CallbackParams<S>
196where
197 S::Output: BosStr,
198{
199 type Output = CallbackParams<S::Output>;
200
201 fn into_static(self) -> Self::Output {
202 CallbackParams {
203 code: self.code.into_static(),
204 state: self.state.into_static(),
205 iss: self.iss.into_static(),
206 }
207 }
208}