1use jacquard_common::session::SessionStoreError;
2use miette::Diagnostic;
3#[cfg(feature = "scope-check")]
4use smol_str::SmolStr;
5
6use crate::request::RequestError;
7use crate::resolver::ResolverError;
8
9#[derive(Debug, thiserror::Error, Diagnostic)]
11#[non_exhaustive]
12pub enum OAuthError {
13 #[error(transparent)]
15 #[diagnostic(code(jacquard_oauth::resolver))]
16 Resolver(#[from] ResolverError),
17
18 #[error(transparent)]
20 #[diagnostic(code(jacquard_oauth::request))]
21 Request(#[from] RequestError),
22
23 #[error(transparent)]
25 #[diagnostic(code(jacquard_oauth::storage))]
26 Storage(#[from] SessionStoreError),
27
28 #[error(transparent)]
30 #[diagnostic(code(jacquard_oauth::dpop))]
31 Dpop(#[from] crate::dpop::DpopError),
32
33 #[error(transparent)]
35 #[diagnostic(code(jacquard_oauth::keyset))]
36 Keyset(#[from] crate::keyset::Error),
37
38 #[error(transparent)]
40 #[diagnostic(code(jacquard_oauth::atproto))]
41 Atproto(#[from] crate::atproto::Error),
42
43 #[error(transparent)]
45 #[diagnostic(code(jacquard_oauth::session))]
46 Session(#[from] crate::session::Error),
47
48 #[error(transparent)]
50 #[diagnostic(code(jacquard_oauth::serde_json))]
51 SerdeJson(#[from] serde_json::Error),
52
53 #[error(transparent)]
55 #[diagnostic(code(jacquard_oauth::url))]
56 Url(#[from] jacquard_common::deps::fluent_uri::ParseError),
57
58 #[error(transparent)]
60 #[diagnostic(code(jacquard_oauth::form))]
61 Form(#[from] serde_html_form::ser::Error),
62
63 #[error("invalid OAuth request: {0}")]
65 #[diagnostic(code(jacquard_oauth::invalid_request))]
66 InvalidRequest(String),
67
68 #[error(transparent)]
70 #[diagnostic(code(jacquard_oauth::callback))]
71 Callback(#[from] CallbackError),
72
73 #[cfg(feature = "scope-check")]
75 #[error(transparent)]
76 #[diagnostic(transparent)]
77 ScopeCheck(#[from] ScopeError),
78}
79
80#[derive(Debug, thiserror::Error, Diagnostic)]
82#[non_exhaustive]
83pub enum CallbackError {
84 #[error("missing state parameter in callback")]
88 #[diagnostic(code(jacquard_oauth::callback::missing_state))]
89 MissingState,
90 #[error("missing `iss` parameter")]
95 #[diagnostic(code(jacquard_oauth::callback::missing_iss))]
96 MissingIssuer,
97 #[error("issuer mismatch: expected {expected}, got {got}")]
99 #[diagnostic(code(jacquard_oauth::callback::issuer_mismatch))]
100 IssuerMismatch {
101 expected: String,
103 got: String,
105 },
106 #[error("timeout")]
108 #[diagnostic(code(jacquard_oauth::callback::timeout))]
109 Timeout,
110 #[error("loopback callback server error: {0}")]
112 #[diagnostic(code(jacquard_oauth::callback::loopback_server))]
113 LoopbackServer(String),
114 #[cfg(feature = "scope-check")]
116 #[error("scope resolution failed: {detail}")]
117 #[diagnostic(code(jacquard_oauth::callback::scope_resolution))]
118 ScopeResolution {
119 detail: String,
121 },
122}
123
124#[cfg(feature = "scope-check")]
126#[derive(Debug, thiserror::Error, Diagnostic)]
127#[error("request to `{nsid}` not permitted: no granted scope covers this endpoint")]
128#[diagnostic(
129 code(jacquard_oauth::scope_check),
130 help("granted scopes: {granted}. The endpoint requires an `rpc:` scope covering `{nsid}`.")
131)]
132pub struct ScopeError {
133 pub nsid: SmolStr,
135 pub granted: SmolStr,
137}
138
139pub type Result<T> = core::result::Result<T, OAuthError>;