···232 - `atproto-client` - AT Protocol client functionality
233 - `atproto-record` - Record management
234 - `atproto-jetstream` - Firehose event streaming
235- - `atproto-xrpcs` - XRPC server implementation0000000000000000
···232 - `atproto-client` - AT Protocol client functionality
233 - `atproto-record` - Record management
234 - `atproto-jetstream` - Firehose event streaming
235+ - `atproto-xrpcs` - XRPC server implementation
236+237+## Error Handling
238+239+All error strings must use this format:
240+241+ error-smokesignal-<domain>-<number> <message>: <details>
242+243+Example errors:
244+245+* error-smokesignal-resolve-1 Multiple DIDs resolved for method
246+* error-smokesignal-plc-1 HTTP request failed: https://google.com/ Not Found
247+* error-smokesignal-key-1 Error decoding key: invalid
248+249+Errors should be represented as enums using the `thiserror` library when possible using `src/errors.rs` as a reference and example.
250+251+Avoid creating new errors with the `anyhow!(...)` macro.
+2-4
src/config.rs
···184 OAuthBackendConfig::ATProtocol { signing_keys } => {
185 let item = signing_keys.as_ref().first();
186 item.map(|(key, value)| (key.clone(), value.clone()))
187- .ok_or(anyhow::anyhow!("signing keys is empty"))
188 }
189- OAuthBackendConfig::AIP { .. } => Err(anyhow::anyhow!(
190- "signing keys not available for AIP OAuth backend"
191- )),
192 }
193 }
194
···10 ///
11 /// This error occurs when the application starts up and a required
12 /// environment variable is missing from the execution environment.
13- #[error("error-config-1 {0} must be set")]
14 EnvVarRequired(String),
1516 /// Error when the signing keys file cannot be read.
···18 /// This error occurs when the application fails to read the file
19 /// containing signing keys, typically due to file system permissions
20 /// or missing file issues.
21- #[error("error-config-2 Unable to read signing keys file: {0:?}")]
22 ReadSigningKeysFailed(std::io::Error),
2324 /// Error when the signing keys file cannot be parsed.
25 ///
26 /// This error occurs when the signing keys file contains malformed JSON
27 /// that cannot be properly deserialized.
28- #[error("error-config-3 Unable to parse signing keys file: {0:?}")]
29 ParseSigningKeysFailed(serde_json::Error),
3031 /// Error when no valid signing keys are found.
32 ///
33 /// This error occurs when the signing keys file does not contain any
34 /// valid keys that the application can use for signing operations.
35- #[error("error-config-4 Signing keys must contain at least one valid key")]
36 EmptySigningKeys,
3738 /// Error when no valid OAuth active keys are found.
39 ///
40 /// This error occurs when the configuration does not include any
41 /// valid keys that can be used for OAuth operations.
42- #[error("error-config-6 OAuth active keys must contain at least one valid key")]
43 EmptyOAuthActiveKeys,
4445 /// Error when no valid invitation active keys are found.
46 ///
47 /// This error occurs when the configuration does not include any
48 /// valid keys that can be used for invitation operations.
49- #[error("error-config-7 Invitation active keys must contain at least one valid key")]
50 EmptyInvitationActiveKeys,
5152 /// Error when the PORT environment variable cannot be parsed.
53 ///
54 /// This error occurs when the PORT environment variable contains a value
55 /// that cannot be parsed as a valid u16 integer.
56- #[error("error-config-8 Parsing PORT into u16 failed: {0:?}")]
57 PortParsingFailed(std::num::ParseIntError),
5859 /// Error when the HTTP_COOKIE_KEY cannot be decoded.
60 ///
61 /// This error occurs when the HTTP_COOKIE_KEY environment variable
62 /// contains a value that is not valid base64-encoded data.
63- #[error("error-config-9 Unable to base64 decode HTTP_COOKIE_KEY: {0:?}")]
64 CookieKeyDecodeFailed(base64::DecodeSliceError),
6566 /// Error when the decoded HTTP_COOKIE_KEY cannot be processed.
67 ///
68 /// This error occurs when the decoded HTTP_COOKIE_KEY has an invalid
69 /// format or length that prevents it from being used.
70- #[error("error-config-10 Unable to process decoded HTTP_COOKIE_KEY")]
71 CookieKeyProcessFailed,
7273 /// Error when version information is not available.
74 ///
75 /// This error occurs when neither GIT_HASH nor CARGO_PKG_VERSION
76 /// environment variables are set, preventing version identification.
77- #[error("error-config-11 One of GIT_HASH or CARGO_PKG_VERSION must be set")]
78 VersionNotSet,
7980 /// Error when a referenced signing key is not found.
81 ///
82 /// This error occurs when attempting to use a signing key that
83 /// does not exist in the loaded signing keys configuration.
84- #[error("error-config-12 Signing key not found")]
85 SigningKeyNotFound,
8687 /// Error when a DNS nameserver IP cannot be parsed.
88 ///
89 /// This error occurs when the DNS_NAMESERVERS environment variable contains
90 /// an IP address that cannot be parsed as a valid IpAddr.
91- #[error("error-config-13 Unable to parse nameserver IP '{0}': {1}")]
92 NameserverParsingFailed(String, std::net::AddrParseError),
9394 /// Error when the signing keys file is not found.
95 ///
96 /// This error occurs when the file specified in the SIGNING_KEYS environment
97 /// variable does not exist on the file system.
98- #[error("error-config-14 Signing keys file not found: {0}")]
99 SigningKeysFileNotFound(String),
100101 /// Error when the signing keys file is empty.
102 ///
103 /// This error occurs when the file specified in the SIGNING_KEYS environment
104 /// variable exists but contains no data.
105- #[error("error-config-15 Signing keys file is empty")]
106 EmptySigningKeysFile,
107108 /// Error when the JWKS structure doesn't contain any keys.
109 ///
110 /// This error occurs when the signing keys file contains a valid JWKS structure,
111 /// but the 'keys' array is empty.
112- #[error("error-config-16 No keys found in JWKS")]
113 MissingKeysInJWKS,
114115 /// Error when signing keys fail validation.
116 ///
117 /// This error occurs when the signing keys file contains keys
118 /// that fail validation checks (such as having invalid format).
119- #[error("error-config-17 Signing keys validation failed: {0:?}")]
120 SigningKeysValidationFailed(Vec<String>),
121122 /// Error when AIP OAuth configuration is incomplete.
···124 /// This error occurs when oauth_backend is set to "aip" but
125 /// required AIP configuration values are missing.
126 #[error(
127- "error-config-18 When oauth_backend is 'aip', AIP_HOSTNAME, AIP_CLIENT_ID, and AIP_CLIENT_SECRET must all be set"
128 )]
129 AipConfigurationIncomplete,
130···132 ///
133 /// This error occurs when the OAUTH_BACKEND environment variable
134 /// contains a value other than "aip" or "pds".
135- #[error("error-config-19 oauth_backend must be either 'aip' or 'pds', got: {0}")]
136 InvalidOAuthBackend(String),
00000000000000137}
···10 ///
11 /// This error occurs when the application starts up and a required
12 /// environment variable is missing from the execution environment.
13+ #[error("error-smokesignal-config-1 {0} must be set")]
14 EnvVarRequired(String),
1516 /// Error when the signing keys file cannot be read.
···18 /// This error occurs when the application fails to read the file
19 /// containing signing keys, typically due to file system permissions
20 /// or missing file issues.
21+ #[error("error-smokesignal-config-2 Unable to read signing keys file: {0:?}")]
22 ReadSigningKeysFailed(std::io::Error),
2324 /// Error when the signing keys file cannot be parsed.
25 ///
26 /// This error occurs when the signing keys file contains malformed JSON
27 /// that cannot be properly deserialized.
28+ #[error("error-smokesignal-config-3 Unable to parse signing keys file: {0:?}")]
29 ParseSigningKeysFailed(serde_json::Error),
3031 /// Error when no valid signing keys are found.
32 ///
33 /// This error occurs when the signing keys file does not contain any
34 /// valid keys that the application can use for signing operations.
35+ #[error("error-smokesignal-config-4 Signing keys must contain at least one valid key")]
36 EmptySigningKeys,
3738 /// Error when no valid OAuth active keys are found.
39 ///
40 /// This error occurs when the configuration does not include any
41 /// valid keys that can be used for OAuth operations.
42+ #[error("error-smokesignal-config-5 OAuth active keys must contain at least one valid key")]
43 EmptyOAuthActiveKeys,
4445 /// Error when no valid invitation active keys are found.
46 ///
47 /// This error occurs when the configuration does not include any
48 /// valid keys that can be used for invitation operations.
49+ #[error("error-smokesignal-config-6 Invitation active keys must contain at least one valid key")]
50 EmptyInvitationActiveKeys,
5152 /// Error when the PORT environment variable cannot be parsed.
53 ///
54 /// This error occurs when the PORT environment variable contains a value
55 /// that cannot be parsed as a valid u16 integer.
56+ #[error("error-smokesignal-config-7 Parsing PORT into u16 failed: {0:?}")]
57 PortParsingFailed(std::num::ParseIntError),
5859 /// Error when the HTTP_COOKIE_KEY cannot be decoded.
60 ///
61 /// This error occurs when the HTTP_COOKIE_KEY environment variable
62 /// contains a value that is not valid base64-encoded data.
63+ #[error("error-smokesignal-config-8 Unable to base64 decode HTTP_COOKIE_KEY: {0:?}")]
64 CookieKeyDecodeFailed(base64::DecodeSliceError),
6566 /// Error when the decoded HTTP_COOKIE_KEY cannot be processed.
67 ///
68 /// This error occurs when the decoded HTTP_COOKIE_KEY has an invalid
69 /// format or length that prevents it from being used.
70+ #[error("error-smokesignal-config-9 Unable to process decoded HTTP_COOKIE_KEY")]
71 CookieKeyProcessFailed,
7273 /// Error when version information is not available.
74 ///
75 /// This error occurs when neither GIT_HASH nor CARGO_PKG_VERSION
76 /// environment variables are set, preventing version identification.
77+ #[error("error-smokesignal-config-10 One of GIT_HASH or CARGO_PKG_VERSION must be set")]
78 VersionNotSet,
7980 /// Error when a referenced signing key is not found.
81 ///
82 /// This error occurs when attempting to use a signing key that
83 /// does not exist in the loaded signing keys configuration.
84+ #[error("error-smokesignal-config-11 Signing key not found")]
85 SigningKeyNotFound,
8687 /// Error when a DNS nameserver IP cannot be parsed.
88 ///
89 /// This error occurs when the DNS_NAMESERVERS environment variable contains
90 /// an IP address that cannot be parsed as a valid IpAddr.
91+ #[error("error-smokesignal-config-12 Unable to parse nameserver IP '{0}': {1}")]
92 NameserverParsingFailed(String, std::net::AddrParseError),
9394 /// Error when the signing keys file is not found.
95 ///
96 /// This error occurs when the file specified in the SIGNING_KEYS environment
97 /// variable does not exist on the file system.
98+ #[error("error-smokesignal-config-13 Signing keys file not found: {0}")]
99 SigningKeysFileNotFound(String),
100101 /// Error when the signing keys file is empty.
102 ///
103 /// This error occurs when the file specified in the SIGNING_KEYS environment
104 /// variable exists but contains no data.
105+ #[error("error-smokesignal-config-14 Signing keys file is empty")]
106 EmptySigningKeysFile,
107108 /// Error when the JWKS structure doesn't contain any keys.
109 ///
110 /// This error occurs when the signing keys file contains a valid JWKS structure,
111 /// but the 'keys' array is empty.
112+ #[error("error-smokesignal-config-15 No keys found in JWKS")]
113 MissingKeysInJWKS,
114115 /// Error when signing keys fail validation.
116 ///
117 /// This error occurs when the signing keys file contains keys
118 /// that fail validation checks (such as having invalid format).
119+ #[error("error-smokesignal-config-16 Signing keys validation failed: {0:?}")]
120 SigningKeysValidationFailed(Vec<String>),
121122 /// Error when AIP OAuth configuration is incomplete.
···124 /// This error occurs when oauth_backend is set to "aip" but
125 /// required AIP configuration values are missing.
126 #[error(
127+ "error-smokesignal-config-17 When oauth_backend is 'aip', AIP_HOSTNAME, AIP_CLIENT_ID, and AIP_CLIENT_SECRET must all be set"
128 )]
129 AipConfigurationIncomplete,
130···132 ///
133 /// This error occurs when the OAUTH_BACKEND environment variable
134 /// contains a value other than "aip" or "pds".
135+ #[error("error-smokesignal-config-18 oauth_backend must be either 'aip' or 'pds', got: {0}")]
136 InvalidOAuthBackend(String),
137+138+ /// Error when signing keys list is empty.
139+ ///
140+ /// This error occurs when attempting to select an OAuth signing key
141+ /// but the signing keys list contains no entries.
142+ #[error("error-smokesignal-config-19 signing keys is empty")]
143+ SigningKeysEmpty,
144+145+ /// Error when signing keys are not available for AIP OAuth backend.
146+ ///
147+ /// This error occurs when attempting to access signing keys
148+ /// while using the AIP OAuth backend, which doesn't support signing keys.
149+ #[error("error-smokesignal-config-20 signing keys not available for AIP OAuth backend")]
150+ SigningKeysNotAvailableForAIP,
151}
+1-1
src/errors.rs
···2//!
3//! All errors are represented as a string in this format:
4//!
5-//! "error-<domain>-<number> <message>: <details>"
6//!
7//! The first part containing the "error-" prefix, domain, and number, is used
8//! to uniquely identify the error. This standard code format is used to convey
···2//!
3//! All errors are represented as a string in this format:
4//!
5+//! "error-smokesignal-<domain>-<number> <message>: <details>"
6//!
7//! The first part containing the "error-" prefix, domain, and number, is used
8//! to uniquely identify the error. This standard code format is used to convey
+4-4
src/http/errors/common_error.rs
···7 ///
8 /// This error occurs when a URL contains a handle slug that doesn't conform
9 /// to the expected format or contains invalid characters.
10- #[error("error-common-1 Invalid handle slug")]
11 InvalidHandleSlug,
1213 /// Error when a user lacks permission for an action.
14 ///
15 /// This error occurs when a user attempts to perform an action they
16 /// are not authorized to do, such as modifying another user's data.
17- #[error("error-common-2 Not authorized to perform this action")]
18 NotAuthorized,
1920 /// Error when a required field is missing.
21 ///
22 /// This error occurs when a form or request is missing a mandatory field
23 /// that is needed to complete the operation.
24- #[error("error-common-4 Required field not provided")]
25 FieldRequired,
2627 /// Error when event data has an invalid format or is corrupted.
28 ///
29 /// This error occurs when event data doesn't match the expected format
30 /// or appears to be corrupted or tampered with.
31- #[error("error-common-9 Invalid event format or corrupted data")]
32 InvalidEventFormat,
33}
···7 ///
8 /// This error occurs when a URL contains a handle slug that doesn't conform
9 /// to the expected format or contains invalid characters.
10+ #[error("error-smokesignal-common-1 Invalid handle slug")]
11 InvalidHandleSlug,
1213 /// Error when a user lacks permission for an action.
14 ///
15 /// This error occurs when a user attempts to perform an action they
16 /// are not authorized to do, such as modifying another user's data.
17+ #[error("error-smokesignal-common-2 Not authorized to perform this action")]
18 NotAuthorized,
1920 /// Error when a required field is missing.
21 ///
22 /// This error occurs when a form or request is missing a mandatory field
23 /// that is needed to complete the operation.
24+ #[error("error-smokesignal-common-3 Required field not provided")]
25 FieldRequired,
2627 /// Error when event data has an invalid format or is corrupted.
28 ///
29 /// This error occurs when event data doesn't match the expected format
30 /// or appears to be corrupted or tampered with.
31+ #[error("error-smokesignal-common-4 Invalid event format or corrupted data")]
32 InvalidEventFormat,
33}
+9-2
src/http/errors/create_event_errors.rs
···10 ///
11 /// This error occurs when a user attempts to create an event without
12 /// specifying a name, which is a required field.
13- #[error("error-create-event-1 Name not set")]
14 NameNotSet,
1516 /// Error when the event description is not provided.
17 ///
18 /// This error occurs when a user attempts to create an event without
19 /// specifying a description, which is a required field.
20- #[error("error-create-event-2 Description not set")]
21 DescriptionNotSet,
000000022}
···10 ///
11 /// This error occurs when a user attempts to create an event without
12 /// specifying a name, which is a required field.
13+ #[error("error-smokesignal-create-event-1 Name not set")]
14 NameNotSet,
1516 /// Error when the event description is not provided.
17 ///
18 /// This error occurs when a user attempts to create an event without
19 /// specifying a description, which is a required field.
20+ #[error("error-smokesignal-create-event-2 Description not set")]
21 DescriptionNotSet,
22+23+ /// Error when the AT Protocol server returns an error response.
24+ ///
25+ /// This error occurs when the PDS or other AT Protocol service
26+ /// returns an error response during event record creation.
27+ #[error("error-smokesignal-create-event-3 Server error: {message}")]
28+ ServerError { message: String },
29}
+15
src/http/errors/create_rsvp_errors.rs
···000000000000000
···1+use thiserror::Error;
2+3+/// Represents errors that can occur during RSVP creation.
4+///
5+/// These errors are typically triggered during the process of creating
6+/// and storing RSVP records in the AT Protocol ecosystem.
7+#[derive(Debug, Error)]
8+pub(crate) enum CreateRsvpError {
9+ /// Error when the AT Protocol server returns an error response.
10+ ///
11+ /// This error occurs when the PDS or other AT Protocol service
12+ /// returns an error response during RSVP record creation.
13+ #[error("error-smokesignal-create-rsvp-1 Server error: {message}")]
14+ ServerError { message: String },
15+}
+22
src/http/errors/delete_event_errors.rs
···0000000000000000000000
···1+use thiserror::Error;
2+3+/// Represents errors that can occur during event deletion.
4+///
5+/// These errors are typically triggered during validation or authorization
6+/// checks when a user attempts to delete an event.
7+#[derive(Debug, Error)]
8+pub(crate) enum DeleteEventError {
9+ /// Error when the identity does not have an event with the specified AT-URI.
10+ ///
11+ /// This error occurs when a user attempts to delete an event that doesn't
12+ /// exist in their collection or when the AT-URI is invalid.
13+ #[error("error-smokesignal-delete-event-1 identity does not have event with that AT-URI: {aturi}")]
14+ EventNotFound { aturi: String },
15+16+ /// Error when there is an authentication configuration mismatch.
17+ ///
18+ /// This error occurs when the authentication type doesn't match the
19+ /// configured OAuth backend, indicating a system configuration issue.
20+ #[error("error-smokesignal-delete-event-2 Authentication configuration mismatch")]
21+ AuthenticationConfigurationMismatch,
22+}
+11-4
src/http/errors/edit_event_error.rs
···11 /// This error occurs when a user attempts to edit an event that they
12 /// do not have permission to modify, typically because they are not
13 /// the event creator or an administrator.
14- #[error("error-edit-event-2 Not authorized to edit this event")]
15 NotAuthorized,
1617 /// Error when attempting to edit an unsupported event type.
···20 /// does not support editing, as only community calendar events can be
21 /// modified after creation.
22 #[error(
23- "error-edit-event-3 Unsupported event type. Only community calendar events can be edited"
24 )]
25 UnsupportedEventType,
26···28 ///
29 /// This error occurs when a user attempts to modify location information for an event
30 /// that has multiple locations defined. Such events can only be edited through the API.
31- #[error("error-edit-event-4 Cannot edit locations: Event has multiple locations")]
32 MultipleLocationsPresent,
3334 /// Error when attempting to edit location data on an event that has an unsupported location type.
35 ///
36 /// This error occurs when a user attempts to modify location information for an event
37 /// that has a location type that is not supported for editing through the web interface.
38- #[error("error-edit-event-5 Cannot edit locations: Event has unsupported location type")]
39 UnsupportedLocationType,
000000040}
···11 /// This error occurs when a user attempts to edit an event that they
12 /// do not have permission to modify, typically because they are not
13 /// the event creator or an administrator.
14+ #[error("error-smokesignal-edit-event-1 Not authorized to edit this event")]
15 NotAuthorized,
1617 /// Error when attempting to edit an unsupported event type.
···20 /// does not support editing, as only community calendar events can be
21 /// modified after creation.
22 #[error(
23+ "error-smokesignal-edit-event-2 Unsupported event type. Only community calendar events can be edited"
24 )]
25 UnsupportedEventType,
26···28 ///
29 /// This error occurs when a user attempts to modify location information for an event
30 /// that has multiple locations defined. Such events can only be edited through the API.
31+ #[error("error-smokesignal-edit-event-3 Cannot edit locations: Event has multiple locations")]
32 MultipleLocationsPresent,
3334 /// Error when attempting to edit location data on an event that has an unsupported location type.
35 ///
36 /// This error occurs when a user attempts to modify location information for an event
37 /// that has a location type that is not supported for editing through the web interface.
38+ #[error("error-smokesignal-edit-event-4 Cannot edit locations: Event has unsupported location type")]
39 UnsupportedLocationType,
40+41+ /// Error when the AT Protocol server returns an error response.
42+ ///
43+ /// This error occurs when the PDS or other AT Protocol service
44+ /// returns an error response during event record editing.
45+ #[error("error-smokesignal-edit-event-5 Server error: {message}")]
46+ ServerError { message: String },
47}
+3-3
src/http/errors/event_view_errors.rs
···10 ///
11 /// This error occurs when an event view request specifies a collection
12 /// name that doesn't exist or isn't supported by the system.
13- #[error("error-event-view-1 Invalid collection: {0}")]
14 InvalidCollection(String),
1516 /// Error when an event name is missing.
17 ///
18 /// This error occurs when attempting to view an event that is missing
19 /// a required name field, which is necessary for display.
20- #[error("error-event-view-2 Event name is missing")]
21 MissingEventName,
2223 /// Error when RSVP count calculation fails.
24 ///
25 /// This error occurs when the system fails to retrieve or calculate
26 /// the RSVP counts (going, interested, not going) for an event.
27- #[error("error-event-view-3 Failed to hydrate event RSVP counts: {0}")]
28 FailedToHydrateRsvpCounts(String),
29}
···10 ///
11 /// This error occurs when an event view request specifies a collection
12 /// name that doesn't exist or isn't supported by the system.
13+ #[error("error-smokesignal-event-view-1 Invalid collection: {0}")]
14 InvalidCollection(String),
1516 /// Error when an event name is missing.
17 ///
18 /// This error occurs when attempting to view an event that is missing
19 /// a required name field, which is necessary for display.
20+ #[error("error-smokesignal-event-view-2 Event name is missing")]
21 MissingEventName,
2223 /// Error when RSVP count calculation fails.
24 ///
25 /// This error occurs when the system fails to retrieve or calculate
26 /// the RSVP counts (going, interested, not going) for an event.
27+ #[error("error-smokesignal-event-view-3 Failed to hydrate event RSVP counts: {0}")]
28 FailedToHydrateRsvpCounts(String),
29}
+1-1
src/http/errors/import_error.rs
···10 ///
11 /// This error occurs when attempting to retrieve a list of Smokesignal
12 /// events during an import operation fails, preventing the import.
13- #[error("error-import-1 Failed to list Smokesignal events: {0}")]
14 FailedToListSmokesignalEvents(String),
15}
···10 ///
11 /// This error occurs when attempting to retrieve a list of Smokesignal
12 /// events during an import operation fails, preventing the import.
13+ #[error("error-smokesignal-import-1 Failed to list Smokesignal events: {0}")]
14 FailedToListSmokesignalEvents(String),
15}
+34-5
src/http/errors/login_error.rs
···6/// are logging in to the application, including OAuth flows and DID validation.
7#[derive(Debug, Error)]
8pub(crate) enum LoginError {
9- #[error("error-login-1 DID document does not contain a handle identifier")]
10 NoHandle,
1112 /// Error when a DID document does not contain a PDS endpoint.
···14 /// This error occurs during authentication when the user's DID document
15 /// is retrieved but does not contain a required AT Protocol Personal
16 /// Data Server (PDS) endpoint.
17- #[error("error-login-2 DID document does not contain an AT Protocol PDS endpoint")]
18 NoPDS,
1920 /// Error when an OAuth callback is incomplete.
21 ///
22 /// This error occurs when the OAuth authentication flow callback
23 /// returns with incomplete information, preventing successful authentication.
24- #[error("error-login-100 OAuth callback incomplete")]
25 OAuthCallbackIncomplete,
2627 /// Error when there is an OAuth issuer mismatch.
28 ///
29 /// This error occurs when the issuer in the OAuth response does not
30 /// match the expected issuer, which could indicate a security issue.
31- #[error("error-login-101 OAuth issuer mismatch")]
32 OAuthIssuerMismatch,
3334 /// Error when the login input appears to be an incomplete AT-handle.
···36 /// This error occurs when a user enters what appears to be a partial
37 /// AT-handle (alphanumeric without a domain). The user should append
38 /// ".bsky.social" and resubmit the form.
39- #[error("error-login-102 Please add '.bsky.social' to your handle and try again")]
40 IncompleteHandle,
0000000000000000000000000000041}
···6/// are logging in to the application, including OAuth flows and DID validation.
7#[derive(Debug, Error)]
8pub(crate) enum LoginError {
9+ #[error("error-smokesignal-login-1 DID document does not contain a handle identifier")]
10 NoHandle,
1112 /// Error when a DID document does not contain a PDS endpoint.
···14 /// This error occurs during authentication when the user's DID document
15 /// is retrieved but does not contain a required AT Protocol Personal
16 /// Data Server (PDS) endpoint.
17+ #[error("error-smokesignal-login-2 DID document does not contain an AT Protocol PDS endpoint")]
18 NoPDS,
1920 /// Error when an OAuth callback is incomplete.
21 ///
22 /// This error occurs when the OAuth authentication flow callback
23 /// returns with incomplete information, preventing successful authentication.
24+ #[error("error-smokesignal-login-3 OAuth callback incomplete")]
25 OAuthCallbackIncomplete,
2627 /// Error when there is an OAuth issuer mismatch.
28 ///
29 /// This error occurs when the issuer in the OAuth response does not
30 /// match the expected issuer, which could indicate a security issue.
31+ #[error("error-smokesignal-login-4 OAuth issuer mismatch")]
32 OAuthIssuerMismatch,
3334 /// Error when the login input appears to be an incomplete AT-handle.
···36 /// This error occurs when a user enters what appears to be a partial
37 /// AT-handle (alphanumeric without a domain). The user should append
38 /// ".bsky.social" and resubmit the form.
39+ #[error("error-smokesignal-login-5 Please add '.bsky.social' to your handle and try again")]
40 IncompleteHandle,
41+42+ /// Error when an OAuth request is not found in storage.
43+ ///
44+ /// This error occurs during the OAuth callback when the system cannot
45+ /// find the corresponding OAuth request that was initiated, possibly
46+ /// due to expiration or invalid state parameter.
47+ #[error("error-smokesignal-login-6 oauth request not found in storage")]
48+ OAuthRequestNotFound,
49+50+ /// Error when HTTP request for AIP OAuth userinfo fails.
51+ ///
52+ /// This error occurs during AIP OAuth flow when the HTTP request
53+ /// to retrieve user information from the AIP server fails.
54+ #[error("error-smokesignal-login-7 HTTP request for userinfo failed")]
55+ AipUserinfoRequestFailed,
56+57+ /// Error when parsing AIP OAuth userinfo response fails.
58+ ///
59+ /// This error occurs during AIP OAuth flow when the JSON response
60+ /// from the AIP userinfo endpoint cannot be parsed.
61+ #[error("error-smokesignal-login-8 Parsing HTTP response for userinfo failed")]
62+ AipUserinfoParsingFailed,
63+64+ /// Error when AIP OAuth server returns an error response.
65+ ///
66+ /// This error occurs when the AIP OAuth server returns an error
67+ /// in the userinfo response instead of valid user claims.
68+ #[error("error-smokesignal-login-9 AIP OAuth server error: {message}")]
69+ AipServerError { message: String },
70}
+5-5
src/http/errors/middleware_errors.rs
···14 ///
15 /// This error occurs when attempting to deserialize a web session from JSON
16 /// format, typically when retrieving a session from storage or a cookie.
17- #[error("error-websession-1 Unable to deserialize WebSession: {0:?}")]
18 DeserializeFailed(serde_json::Error),
1920 /// Error when web session serialization fails.
21 ///
22 /// This error occurs when attempting to serialize a web session to JSON
23 /// format, typically when storing a session in storage or a cookie.
24- #[error("error-websession-2 Unable to serialize WebSession: {0:?}")]
25 SerializeFailed(serde_json::Error),
26}
2728#[derive(Debug, Error)]
29pub(crate) enum MiddlewareAuthError {
30- #[error("error-middleware-auth-1 Access Denied: {0}")]
31 AccessDenied(String),
3233- #[error("error-middleware-auth-2 Not Found")]
34 NotFound,
3536- #[error("error-middleware-auth-3 Unhandled Auth Error: {0:?}")]
37 Anyhow(#[from] anyhow::Error),
38}
39
···14 ///
15 /// This error occurs when attempting to deserialize a web session from JSON
16 /// format, typically when retrieving a session from storage or a cookie.
17+ #[error("error-smokesignal-websession-1 Unable to deserialize WebSession: {0:?}")]
18 DeserializeFailed(serde_json::Error),
1920 /// Error when web session serialization fails.
21 ///
22 /// This error occurs when attempting to serialize a web session to JSON
23 /// format, typically when storing a session in storage or a cookie.
24+ #[error("error-smokesignal-websession-2 Unable to serialize WebSession: {0:?}")]
25 SerializeFailed(serde_json::Error),
26}
2728#[derive(Debug, Error)]
29pub(crate) enum MiddlewareAuthError {
30+ #[error("error-smokesignal-middleware-auth-1 Access Denied: {0}")]
31 AccessDenied(String),
3233+ #[error("error-smokesignal-middleware-auth-2 Not Found")]
34 NotFound,
3536+ #[error("error-smokesignal-middleware-auth-3 Unhandled Auth Error: {0:?}")]
37 Anyhow(#[from] anyhow::Error),
38}
39
+4-4
src/http/errors/migrate_event_error.rs
···11 /// This error occurs when a user attempts to migrate an event that they
12 /// do not have permission to modify, typically because they are not
13 /// the event creator or an administrator.
14- #[error("error-migrate-event-2 Not authorized to migrate this event")]
15 NotAuthorized,
1617 /// Error when attempting to migrate an unsupported event type.
···20 /// cannot be migrated, as only smokesignal events can be converted to
21 /// the community event format.
22 #[error(
23- "error-migrate-event-3 Unsupported event type. Only smokesignal events can be migrated"
24 )]
25 UnsupportedEventType,
26···28 ///
29 /// This error occurs when attempting to migrate an event that is already
30 /// in the community event format, which would be redundant.
31- #[error("error-migrate-event-4 Event is already a community event")]
32 AlreadyMigrated,
3334 /// Error when a destination URI conflict exists.
35 ///
36 /// This error occurs when attempting to migrate an event to a URI that
37 /// already has an event associated with it, which would cause a conflict.
38- #[error("error-migrate-event-5 An event already exists at the destination URI")]
39 DestinationExists,
40}
···11 /// This error occurs when a user attempts to migrate an event that they
12 /// do not have permission to modify, typically because they are not
13 /// the event creator or an administrator.
14+ #[error("error-smokesignal-migrate-event-1 Not authorized to migrate this event")]
15 NotAuthorized,
1617 /// Error when attempting to migrate an unsupported event type.
···20 /// cannot be migrated, as only smokesignal events can be converted to
21 /// the community event format.
22 #[error(
23+ "error-smokesignal-migrate-event-2 Unsupported event type. Only smokesignal events can be migrated"
24 )]
25 UnsupportedEventType,
26···28 ///
29 /// This error occurs when attempting to migrate an event that is already
30 /// in the community event format, which would be redundant.
31+ #[error("error-smokesignal-migrate-event-3 Event is already a community event")]
32 AlreadyMigrated,
3334 /// Error when a destination URI conflict exists.
35 ///
36 /// This error occurs when attempting to migrate an event to a URI that
37 /// already has an event associated with it, which would cause a conflict.
38+ #[error("error-smokesignal-migrate-event-4 An event already exists at the destination URI")]
39 DestinationExists,
40}
+2-2
src/http/errors/migrate_rsvp_error.rs
···12 /// that doesn't match one of the expected values ('going', 'interested',
13 /// or 'notgoing').
14 #[error(
15- "error-migrate-rsvp-1 Invalid RSVP status: {0}. Expected 'going', 'interested', or 'notgoing'."
16 )]
17 InvalidRsvpStatus(String),
18···21 /// This error occurs when a user attempts to migrate an RSVP that they
22 /// do not have permission to modify, typically because they are not
23 /// the RSVP owner or an administrator.
24- #[error("error-migrate-rsvp-2 Not authorized to migrate this RSVP")]
25 NotAuthorized,
26}
···12 /// that doesn't match one of the expected values ('going', 'interested',
13 /// or 'notgoing').
14 #[error(
15+ "error-smokesignal-migrate-rsvp-1 Invalid RSVP status: {0}. Expected 'going', 'interested', or 'notgoing'."
16 )]
17 InvalidRsvpStatus(String),
18···21 /// This error occurs when a user attempts to migrate an RSVP that they
22 /// do not have permission to modify, typically because they are not
23 /// the RSVP owner or an administrator.
24+ #[error("error-smokesignal-migrate-rsvp-2 Not authorized to migrate this RSVP")]
25 NotAuthorized,
26}
+4
src/http/errors/mod.rs
···2pub mod admin_errors;
3pub mod common_error;
4pub mod create_event_errors;
005pub mod edit_event_error;
6pub mod event_view_errors;
7pub mod import_error;
···1516pub(crate) use common_error::CommonError;
17pub(crate) use create_event_errors::CreateEventError;
0018pub(crate) use edit_event_error::EditEventError;
19pub(crate) use event_view_errors::EventViewError;
20pub(crate) use import_error::ImportError;
···2pub mod admin_errors;
3pub mod common_error;
4pub mod create_event_errors;
5+pub mod create_rsvp_errors;
6+pub mod delete_event_errors;
7pub mod edit_event_error;
8pub mod event_view_errors;
9pub mod import_error;
···1718pub(crate) use common_error::CommonError;
19pub(crate) use create_event_errors::CreateEventError;
20+pub(crate) use create_rsvp_errors::CreateRsvpError;
21+pub(crate) use delete_event_errors::DeleteEventError;
22pub(crate) use edit_event_error::EditEventError;
23pub(crate) use event_view_errors::EventViewError;
24pub(crate) use import_error::ImportError;
+1-1
src/http/errors/url_error.rs
···10 ///
11 /// This error occurs when a URL contains a collection type that is not
12 /// supported by the system, typically in an AT Protocol URI path.
13- #[error("error-url-1 Unsupported collection type")]
14 UnsupportedCollection,
15}
···10 ///
11 /// This error occurs when a URL contains a collection type that is not
12 /// supported by the system, typically in an AT Protocol URI path.
13+ #[error("error-smokesignal-url-1 Unsupported collection type")]
14 UnsupportedCollection,
15}
+2-2
src/http/errors/view_event_error.rs
···10 ///
11 /// This error occurs when attempting to view an event that doesn't
12 /// exist in the system, typically due to an invalid identifier.
13- #[error("error-view-event-1 Event not found: {0}")]
14 EventNotFound(String),
1516 /// Error when a fallback retrieval method fails.
17 ///
18 /// This error occurs when the primary method of retrieving an event fails,
19 /// and the fallback method also fails to retrieve the event.
20- #[error("error-view-event-2 Failed to get event from fallback: {0}")]
21 FallbackFailed(String),
22}
···10 ///
11 /// This error occurs when attempting to view an event that doesn't
12 /// exist in the system, typically due to an invalid identifier.
13+ #[error("error-smokesignal-view-event-1 Event not found: {0}")]
14 EventNotFound(String),
1516 /// Error when a fallback retrieval method fails.
17 ///
18 /// This error occurs when the primary method of retrieving an event fails,
19 /// and the fallback method also fails to retrieve the event.
20+ #[error("error-smokesignal-view-event-2 Failed to get event from fallback: {0}")]
21 FallbackFailed(String),
22}
+6-6
src/http/errors/web_error.rs
···5//! uniformly at the HTTP boundary and converted into appropriate HTTP responses.
6//!
7//! Specific error variants use their own error codes, while general errors use the
8-//! format: `error-web-<number> <message>: <details>`
910use axum::http::StatusCode;
11use axum::response::IntoResponse;
···30///
31/// Most variants use transparent error forwarding to preserve the original error message
32/// and error code, while a few web-specific errors have their own error code format:
33-/// `error-web-<number> <message>: <details>`
34#[derive(Debug, Error)]
35pub(crate) enum WebError {
36 /// Error when authentication middleware fails.
···39 /// through the authentication middleware, such as invalid credentials or
40 /// expired sessions.
41 ///
42- /// **Error Code:** `error-web-1`
43- #[error("error-web-1 Middleware Auth Error: {0:?}")]
44 MiddlewareAuthError(#[from] MiddlewareAuthError),
4546 /// Error when an unexpected error occurs that isn't covered by other error types.
···48 /// This error is a fallback for any unhandled errors in the system. In production,
49 /// these should be rare as most errors should be properly typed.
50 ///
51- /// **Error Code:** `error-web-2`
52 ///
53 /// Note: This should be replaced with more specific error types as part of
54 /// the ongoing effort to use typed errors throughout the codebase.
55- #[error("error-web-2 Unhandled web error: {0:?}")]
56 Anyhow(#[from] anyhow::Error),
5758 /// Common HTTP errors.
···5//! uniformly at the HTTP boundary and converted into appropriate HTTP responses.
6//!
7//! Specific error variants use their own error codes, while general errors use the
8+//! format: `error-smokesignal-web-<number> <message>: <details>`
910use axum::http::StatusCode;
11use axum::response::IntoResponse;
···30///
31/// Most variants use transparent error forwarding to preserve the original error message
32/// and error code, while a few web-specific errors have their own error code format:
33+/// `error-smokesignal-web-<number> <message>: <details>`
34#[derive(Debug, Error)]
35pub(crate) enum WebError {
36 /// Error when authentication middleware fails.
···39 /// through the authentication middleware, such as invalid credentials or
40 /// expired sessions.
41 ///
42+ /// **Error Code:** `error-smokesignal-web-1`
43+ #[error("error-smokesignal-web-1 Middleware Auth Error: {0:?}")]
44 MiddlewareAuthError(#[from] MiddlewareAuthError),
4546 /// Error when an unexpected error occurs that isn't covered by other error types.
···48 /// This error is a fallback for any unhandled errors in the system. In production,
49 /// these should be rare as most errors should be properly typed.
50 ///
51+ /// **Error Code:** `error-smokesignal-web-2`
52 ///
53 /// Note: This should be replaced with more specific error types as part of
54 /// the ongoing effort to use typed errors throughout the codebase.
55+ #[error("error-smokesignal-web-2 Unhandled web error: {0:?}")]
56 Anyhow(#[from] anyhow::Error),
5758 /// Common HTTP errors.
···47pub mod server;
48pub mod tab_selector;
49pub mod templates;
050pub mod timezones;
51pub mod utils;
···47pub mod server;
48pub mod tab_selector;
49pub mod templates;
50+pub mod timezone_errors;
51pub mod timezones;
52pub mod utils;
+22
src/http/timezone_errors.rs
···0000000000000000000000
···1+use thiserror::Error;
2+3+/// Represents errors that can occur during timezone and datetime operations.
4+///
5+/// These errors typically occur when parsing date/time strings or converting
6+/// between timezones, especially during daylight saving time transitions.
7+#[derive(Debug, Error)]
8+pub(crate) enum TimezoneError {
9+ /// Error when parsing date and time strings fails.
10+ ///
11+ /// This error occurs when the provided date or time string cannot be parsed
12+ /// into a valid datetime format, typically due to incorrect format or invalid values.
13+ #[error("error-smokesignal-timezone-1 Failed to parse date and time: {error}")]
14+ DateTimeParsingFailed { error: String },
15+16+ /// Error when local time is ambiguous or non-existent.
17+ ///
18+ /// This error occurs during daylight saving time transitions when a local time
19+ /// either doesn't exist (spring forward) or is ambiguous (fall back).
20+ #[error("error-smokesignal-timezone-2 Ambiguous or non-existent local time")]
21+ AmbiguousOrNonExistentLocalTime,
22+}
+4-3
src/http/timezones.rs
···1-use anyhow::{Result, anyhow};
2use chrono::{DateTime, NaiveDateTime, Utc};
3use itertools::Itertools;
405use crate::storage::identity_profile::model::IdentityProfile;
67pub(crate) fn supported_timezones(handle: Option<&IdentityProfile>) -> (&str, Vec<&str>) {
···7980 // Parse the combined string into a NaiveDateTime
81 let naive_dt = NaiveDateTime::parse_from_str(&datetime_str, "%Y-%m-%dT%H:%M")
82- .map_err(|e| anyhow!("Failed to parse date and time: {}", e))?;
8384 // Convert to timezone-aware datetime in the specified timezone
85 let local_dt = naive_dt
86 .and_local_timezone(timezone)
87 .single()
88- .ok_or_else(|| anyhow!("Ambiguous or non-existent local time"))?;
8990 // Convert to UTC
91 Ok(local_dt.with_timezone(&Utc))
···1+use anyhow::Result;
2use chrono::{DateTime, NaiveDateTime, Utc};
3use itertools::Itertools;
45+use crate::http::timezone_errors::TimezoneError;
6use crate::storage::identity_profile::model::IdentityProfile;
78pub(crate) fn supported_timezones(handle: Option<&IdentityProfile>) -> (&str, Vec<&str>) {
···8081 // Parse the combined string into a NaiveDateTime
82 let naive_dt = NaiveDateTime::parse_from_str(&datetime_str, "%Y-%m-%dT%H:%M")
83+ .map_err(|e| TimezoneError::DateTimeParsingFailed { error: e.to_string() })?;
8485 // Convert to timezone-aware datetime in the specified timezone
86 let local_dt = naive_dt
87 .and_local_timezone(timezone)
88 .single()
89+ .ok_or(TimezoneError::AmbiguousOrNonExistentLocalTime)?;
9091 // Convert to UTC
92 Ok(local_dt.with_timezone(&Utc))
···7pub mod i18n;
8pub mod key_provider;
9pub mod processor;
010pub mod refresh_tokens_errors;
11pub mod service;
12pub mod storage;
···14pub mod task_oauth_requests_cleanup;
15pub mod task_refresh_tokens;
16pub mod task_search_indexer;
017pub mod task_webhooks;
018pub mod webhooks;
···7pub mod i18n;
8pub mod key_provider;
9pub mod processor;
10+pub mod processor_errors;
11pub mod refresh_tokens_errors;
12pub mod service;
13pub mod storage;
···15pub mod task_oauth_requests_cleanup;
16pub mod task_refresh_tokens;
17pub mod task_search_indexer;
18+pub mod task_search_indexer_errors;
19pub mod task_webhooks;
20+pub mod task_webhooks_errors;
21pub mod webhooks;
+3-2
src/processor.rs
···1-use anyhow::{Result, anyhow};
2use atproto_client::com::atproto::repo::get_blob;
3use atproto_identity::model::Document;
4use atproto_identity::storage::DidDocumentStorage;
···8use std::sync::Arc;
910use crate::atproto::lexicon::community::lexicon::calendar::event::Event;
011use crate::atproto::lexicon::community::lexicon::calendar::event::Media;
12use crate::atproto::lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID;
13use crate::atproto::lexicon::community::lexicon::calendar::rsvp::NSID as LexiconCommunityRSVPNSID;
···144 let pds_endpoints = document.pds_endpoints();
145 let pds_endpoint = pds_endpoints
146 .first()
147- .ok_or_else(|| anyhow!("no PDS in DID"))?;
148149 let name = match &event_record {
150 Event::Current { name, .. } => name.clone(),
···1+use anyhow::Result;
2use atproto_client::com::atproto::repo::get_blob;
3use atproto_identity::model::Document;
4use atproto_identity::storage::DidDocumentStorage;
···8use std::sync::Arc;
910use crate::atproto::lexicon::community::lexicon::calendar::event::Event;
11+use crate::processor_errors::ProcessorError;
12use crate::atproto::lexicon::community::lexicon::calendar::event::Media;
13use crate::atproto::lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID;
14use crate::atproto::lexicon::community::lexicon::calendar::rsvp::NSID as LexiconCommunityRSVPNSID;
···145 let pds_endpoints = document.pds_endpoints();
146 let pds_endpoint = pds_endpoints
147 .first()
148+ .ok_or(ProcessorError::NoPdsInDid)?;
149150 let name = match &event_record {
151 Event::Current { name, .. } => name.clone(),
+15
src/processor_errors.rs
···000000000000000
···1+use thiserror::Error;
2+3+/// Represents errors that can occur during event processing.
4+///
5+/// These errors typically occur when processing events from the Jetstream
6+/// consumer, including DID resolution and data validation issues.
7+#[derive(Debug, Error)]
8+pub(crate) enum ProcessorError {
9+ /// Error when a DID document does not contain any PDS endpoints.
10+ ///
11+ /// This error occurs when attempting to process an event from a DID
12+ /// that has no Personal Data Server (PDS) endpoints configured.
13+ #[error("error-smokesignal-processor-1 no PDS in DID")]
14+ NoPdsInDid,
15+}
+4-4
src/refresh_tokens_errors.rs
···10 ///
11 /// This error occurs when attempting to refresh a token but the necessary
12 /// secret key for signing the request is not available in the configuration.
13- #[error("error-refresh-1 Secret signing key not found")]
14 SecretSigningKeyNotFound,
1516 /// Error when creating a DPoP proof for token refresh fails.
17 ///
18 /// This error occurs when there is an issue with the cryptographic operations
19 /// required to generate a DPoP (Demonstrating Proof-of-Possession) proof.
20- #[error("error-refresh-2 Failed to create DPoP proof: {0:?}")]
21 DpopProofCreationFailed(elliptic_curve::Error),
2223 /// Error when a session cannot be placed in the refresh queue.
24 ///
25 /// This error occurs when there is an issue with the Redis-backed queue
26 /// used to manage session refresh operations.
27- #[error("error-refresh-3 Failed to place session group into refresh queue: {0:?}")]
28 PlaceInRefreshQueueFailed(deadpool_redis::redis::RedisError),
2930 /// Error when the identity document cannot be found.
31 ///
32 /// This error occurs when attempting to refresh a token but the necessary
33 /// identity document for the user is not available in storage.
34- #[error("error-refresh-4 Identity document not found")]
35 IdentityDocumentNotFound,
36}
···10 ///
11 /// This error occurs when attempting to refresh a token but the necessary
12 /// secret key for signing the request is not available in the configuration.
13+ #[error("error-smokesignal-refresh-1 Secret signing key not found")]
14 SecretSigningKeyNotFound,
1516 /// Error when creating a DPoP proof for token refresh fails.
17 ///
18 /// This error occurs when there is an issue with the cryptographic operations
19 /// required to generate a DPoP (Demonstrating Proof-of-Possession) proof.
20+ #[error("error-smokesignal-refresh-2 Failed to create DPoP proof: {0:?}")]
21 DpopProofCreationFailed(elliptic_curve::Error),
2223 /// Error when a session cannot be placed in the refresh queue.
24 ///
25 /// This error occurs when there is an issue with the Redis-backed queue
26 /// used to manage session refresh operations.
27+ #[error("error-smokesignal-refresh-3 Failed to place session group into refresh queue: {0:?}")]
28 PlaceInRefreshQueueFailed(deadpool_redis::redis::RedisError),
2930 /// Error when the identity document cannot be found.
31 ///
32 /// This error occurs when attempting to refresh a token but the necessary
33 /// identity document for the user is not available in storage.
34+ #[error("error-smokesignal-refresh-4 Identity document not found")]
35 IdentityDocumentNotFound,
36}
+4-4
src/storage/atproto.rs
···44impl DidDocumentStorage for PostgresDidDocumentStorage {
45 async fn get_document_by_did(&self, did: &str) -> Result<Option<Document>, anyhow::Error> {
46 if did.trim().is_empty() {
47- return Err(anyhow::anyhow!("DID cannot be empty"));
48 }
4950 let mut tx = self.pool.begin().await?;
···9798 async fn delete_document_by_did(&self, did: &str) -> Result<(), anyhow::Error> {
99 if did.trim().is_empty() {
100- return Err(anyhow::anyhow!("DID cannot be empty"));
101 }
102103 let mut tx = self.pool.begin().await?;
···226 state: &str,
227 ) -> Result<Option<OAuthRequest>, anyhow::Error> {
228 if state.trim().is_empty() {
229- return Err(anyhow::anyhow!("OAuth state cannot be empty"));
230 }
231232 let mut tx = self.pool.begin().await?;
···263264 async fn delete_oauth_request_by_state(&self, state: &str) -> Result<(), anyhow::Error> {
265 if state.trim().is_empty() {
266- return Err(anyhow::anyhow!("OAuth state cannot be empty"));
267 }
268269 let mut tx = self.pool.begin().await?;
···447 Err(e) => {
448 // If it's a not-found error, add to the not-found filter
449 if e.to_string()
450- .starts_with("error-content-2 File storage operation failed:")
451 {
452 self.add_to_not_found_filter(cid).await;
453 }
···447 Err(e) => {
448 // If it's a not-found error, add to the not-found filter
449 if e.to_string()
450+ .starts_with("error-smokesignal-content-2 File storage operation failed:")
451 {
452 self.add_to_not_found_filter(cid).await;
453 }
+33-19
src/storage/errors.rs
···11 /// This error occurs when attempting to convert a JSON Web Key (JWK)
12 /// into a secret key for DPoP (Demonstrating Proof-of-Possession) operations,
13 /// typically due to invalid key format or cryptographic errors.
14- #[error("error-oauth-model-1 Failed to create DPoP secret from JWK: {0:?}")]
15 DpopSecretFromJwkFailed(elliptic_curve::Error),
1617 /// Error when the OAuth flow state is invalid.
···19 /// This error occurs when the state parameter in an OAuth flow
20 /// does not match the expected value or cannot be verified,
21 /// which could indicate a potential CSRF attack or session mismatch.
22- #[error("error-oauth-model-2 Invalid OAuth flow state")]
23 InvalidOAuthFlowState(),
2425 /// Error when deserializing DPoP JWK from string fails.
26 ///
27 /// This error occurs when attempting to deserialize a string-encoded
28 /// JSON Web Key (JWK) for DPoP operations, typically due to invalid JSON format.
29- #[error("error-oauth-model-5 Failed to deserialize DPoP JWK: {0:?}")]
30 DpopJwkDeserializationFailed(serde_json::Error),
3132 /// Error when required OAuth session data is missing.
···34 /// This error occurs when attempting to use an OAuth session
35 /// that is missing critical data needed for authentication or
36 /// authorization operations, such as tokens or session identifiers.
37- #[error("error-oauth-model-3 Missing required OAuth session data")]
38 MissingRequiredOAuthSessionData(),
3940 /// Error when an OAuth session has expired.
···42 /// This error occurs when attempting to use an OAuth session
43 /// that has exceeded its validity period and is no longer usable
44 /// for authentication or authorization purposes.
45- #[error("error-oauth-model-4 OAuth session has expired")]
46 OAuthSessionExpired(),
47}
48···53 ///
54 /// This error occurs when attempting to retrieve a web session using an
55 /// invalid or expired session ID.
56- #[error("error-storage-1 Web session not found")]
57 WebSessionNotFound,
5859 /// Error when a handle cannot be found in the database.
60 ///
61 /// This error occurs when attempting to retrieve a user handle that
62 /// doesn't exist in the system.
63- #[error("error-storage-2 Handle not found")]
64 HandleNotFound,
6566 /// Error when a database record cannot be found.
67 ///
68 /// This error occurs when attempting to retrieve a specific record
69 /// using an ID or other identifier that doesn't exist in the database.
70- #[error("error-storage-3 Record not found: {0} {1:?}")]
71 RowNotFound(String, sqlx::Error),
7273 /// Error when a database transaction cannot be committed.
74 ///
75 /// This error occurs when there is an issue finalizing a database
76 /// transaction, potentially causing data inconsistency.
77- #[error("error-storage-4 Cannot commit database transaction: {0:?}")]
78 CannotCommitDatabaseTransaction(sqlx::Error),
7980 /// Error when a database transaction cannot be started.
81 ///
82 /// This error occurs when there is an issue initiating a database
83 /// transaction, typically due to connection issues or database constraints.
84- #[error("error-storage-5 Cannot begin database transaction: {0:?}")]
85 CannotBeginDatabaseTransaction(sqlx::Error),
8687 /// Error when a database query cannot be executed.
88 ///
89 /// This error occurs when a SQL query fails to execute, typically due to
90 /// syntax errors, constraint violations, or database connectivity issues.
91- #[error("error-storage-6 Unable to execute query: {0:?}")]
92 UnableToExecuteQuery(sqlx::Error),
9394 /// Error when an OAuth request cannot be found.
95 ///
96 /// This error occurs when attempting to retrieve an OAuth request
97 /// that doesn't exist or has expired.
98- #[error("error-storage-7 OAuth request not found")]
99 OAuthRequestNotFound,
100101 /// Error when an RSVP cannot be found.
102 ///
103 /// This error occurs when attempting to retrieve an RSVP record
104 /// that doesn't exist in the database.
105- #[error("error-storage-8 RSVP not found")]
106 RSVPNotFound,
107108 /// Error when an OAuth model operation fails.
109 ///
110 /// This error occurs when there's an issue with OAuth model operations,
111 /// such as token generation, validation, or storage.
112- #[error("error-storage-9 OAuth model error: {0}")]
113 OAuthModelError(#[from] OAuthModelError),
00000000000000114}
115116/// Represents errors that can occur during cache operations.
···120 ///
121 /// This error occurs when the system fails to initialize the Redis
122 /// connection pool, typically due to configuration or connectivity issues.
123- #[error("error-cache-1 Failed to create cache pool: {0:?}")]
124 FailedToCreatePool(deadpool_redis::CreatePoolError),
125126 /// Error when a cache connection cannot be obtained.
127 ///
128 /// This error occurs when the system fails to get a connection from
129 /// the Redis connection pool, typically due to pool exhaustion or connectivity issues.
130- #[error("error-cache-2 Failed to get connection: {0:?}")]
131 FailedToGetConnection(deadpool_redis::PoolError),
132133 /// Error when a session cannot be placed in the refresh queue.
134 ///
135 /// This error occurs when the system fails to add a session to the
136 /// Redis-backed refresh queue, typically due to Redis errors or connectivity issues.
137- #[error("error-cache-3 Failed to place session group into refresh queue: {0:?}")]
138 FailedToPlaceInRefreshQueue(deadpool_redis::redis::RedisError),
139}
140141#[derive(Debug, Error)]
142pub enum ContentError {
143- #[error("error-content-1 Invalid S3 URL format: {details}")]
144 /// Failed to parse S3 URL format from environment variable.
145 ConfigS3UrlInvalid {
146 /// Details about the S3 URL parsing error.
147 details: String,
148 },
149150- #[error("error-content-2 File storage operation failed: {operation}")]
151 /// File storage operation failed.
152 StorageFileOperationFailed {
153 /// Description of the failed file operation.
···11 /// This error occurs when attempting to convert a JSON Web Key (JWK)
12 /// into a secret key for DPoP (Demonstrating Proof-of-Possession) operations,
13 /// typically due to invalid key format or cryptographic errors.
14+ #[error("error-smokesignal-oauth-model-1 Failed to create DPoP secret from JWK: {0:?}")]
15 DpopSecretFromJwkFailed(elliptic_curve::Error),
1617 /// Error when the OAuth flow state is invalid.
···19 /// This error occurs when the state parameter in an OAuth flow
20 /// does not match the expected value or cannot be verified,
21 /// which could indicate a potential CSRF attack or session mismatch.
22+ #[error("error-smokesignal-oauth-model-2 Invalid OAuth flow state")]
23 InvalidOAuthFlowState(),
2425 /// Error when deserializing DPoP JWK from string fails.
26 ///
27 /// This error occurs when attempting to deserialize a string-encoded
28 /// JSON Web Key (JWK) for DPoP operations, typically due to invalid JSON format.
29+ #[error("error-smokesignal-oauth-model-3 Failed to deserialize DPoP JWK: {0:?}")]
30 DpopJwkDeserializationFailed(serde_json::Error),
3132 /// Error when required OAuth session data is missing.
···34 /// This error occurs when attempting to use an OAuth session
35 /// that is missing critical data needed for authentication or
36 /// authorization operations, such as tokens or session identifiers.
37+ #[error("error-smokesignal-oauth-model-4 Missing required OAuth session data")]
38 MissingRequiredOAuthSessionData(),
3940 /// Error when an OAuth session has expired.
···42 /// This error occurs when attempting to use an OAuth session
43 /// that has exceeded its validity period and is no longer usable
44 /// for authentication or authorization purposes.
45+ #[error("error-smokesignal-oauth-model-5 OAuth session has expired")]
46 OAuthSessionExpired(),
47}
48···53 ///
54 /// This error occurs when attempting to retrieve a web session using an
55 /// invalid or expired session ID.
56+ #[error("error-smokesignal-storage-1 Web session not found")]
57 WebSessionNotFound,
5859 /// Error when a handle cannot be found in the database.
60 ///
61 /// This error occurs when attempting to retrieve a user handle that
62 /// doesn't exist in the system.
63+ #[error("error-smokesignal-storage-2 Handle not found")]
64 HandleNotFound,
6566 /// Error when a database record cannot be found.
67 ///
68 /// This error occurs when attempting to retrieve a specific record
69 /// using an ID or other identifier that doesn't exist in the database.
70+ #[error("error-smokesignal-storage-3 Record not found: {0} {1:?}")]
71 RowNotFound(String, sqlx::Error),
7273 /// Error when a database transaction cannot be committed.
74 ///
75 /// This error occurs when there is an issue finalizing a database
76 /// transaction, potentially causing data inconsistency.
77+ #[error("error-smokesignal-storage-4 Cannot commit database transaction: {0:?}")]
78 CannotCommitDatabaseTransaction(sqlx::Error),
7980 /// Error when a database transaction cannot be started.
81 ///
82 /// This error occurs when there is an issue initiating a database
83 /// transaction, typically due to connection issues or database constraints.
84+ #[error("error-smokesignal-storage-5 Cannot begin database transaction: {0:?}")]
85 CannotBeginDatabaseTransaction(sqlx::Error),
8687 /// Error when a database query cannot be executed.
88 ///
89 /// This error occurs when a SQL query fails to execute, typically due to
90 /// syntax errors, constraint violations, or database connectivity issues.
91+ #[error("error-smokesignal-storage-6 Unable to execute query: {0:?}")]
92 UnableToExecuteQuery(sqlx::Error),
9394 /// Error when an OAuth request cannot be found.
95 ///
96 /// This error occurs when attempting to retrieve an OAuth request
97 /// that doesn't exist or has expired.
98+ #[error("error-smokesignal-storage-7 OAuth request not found")]
99 OAuthRequestNotFound,
100101 /// Error when an RSVP cannot be found.
102 ///
103 /// This error occurs when attempting to retrieve an RSVP record
104 /// that doesn't exist in the database.
105+ #[error("error-smokesignal-storage-8 RSVP not found")]
106 RSVPNotFound,
107108 /// Error when an OAuth model operation fails.
109 ///
110 /// This error occurs when there's an issue with OAuth model operations,
111 /// such as token generation, validation, or storage.
112+ #[error("error-smokesignal-storage-9 OAuth model error: {0}")]
113 OAuthModelError(#[from] OAuthModelError),
114+115+ /// Error when a DID parameter is empty or invalid.
116+ ///
117+ /// This error occurs when a DID string is empty, which is not valid
118+ /// for DID-based operations.
119+ #[error("error-smokesignal-storage-10 DID cannot be empty")]
120+ DidCannotBeEmpty,
121+122+ /// Error when an OAuth state parameter is empty or invalid.
123+ ///
124+ /// This error occurs when an OAuth state string is empty, which is not valid
125+ /// for OAuth operations.
126+ #[error("error-smokesignal-storage-11 OAuth state cannot be empty")]
127+ OAuthStateCannotBeEmpty,
128}
129130/// Represents errors that can occur during cache operations.
···134 ///
135 /// This error occurs when the system fails to initialize the Redis
136 /// connection pool, typically due to configuration or connectivity issues.
137+ #[error("error-smokesignal-cache-1 Failed to create cache pool: {0:?}")]
138 FailedToCreatePool(deadpool_redis::CreatePoolError),
139140 /// Error when a cache connection cannot be obtained.
141 ///
142 /// This error occurs when the system fails to get a connection from
143 /// the Redis connection pool, typically due to pool exhaustion or connectivity issues.
144+ #[error("error-smokesignal-cache-2 Failed to get connection: {0:?}")]
145 FailedToGetConnection(deadpool_redis::PoolError),
146147 /// Error when a session cannot be placed in the refresh queue.
148 ///
149 /// This error occurs when the system fails to add a session to the
150 /// Redis-backed refresh queue, typically due to Redis errors or connectivity issues.
151+ #[error("error-smokesignal-cache-3 Failed to place session group into refresh queue: {0:?}")]
152 FailedToPlaceInRefreshQueue(deadpool_redis::redis::RedisError),
153}
154155#[derive(Debug, Error)]
156pub enum ContentError {
157+ #[error("error-smokesignal-content-1 Invalid S3 URL format: {details}")]
158 /// Failed to parse S3 URL format from environment variable.
159 ConfigS3UrlInvalid {
160 /// Details about the S3 URL parsing error.
161 details: String,
162 },
163164+ #[error("error-smokesignal-content-2 File storage operation failed: {operation}")]
165 /// File storage operation failed.
166 StorageFileOperationFailed {
167 /// Description of the failed file operation.
+2-1
src/task_search_indexer.rs
···10use tokio_util::sync::CancellationToken;
1112use crate::atproto::lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID;
013use crate::{
14 atproto::lexicon::community::lexicon::calendar::event::Event,
15 consumer::{SmokeSignalEvent, SmokeSignalEventReceiver},
···89 tracing::info!("Created OpenSearch index {}", INDEX_NAME);
90 } else {
91 let error_body = response.text().await?;
92- return Err(anyhow::anyhow!("Failed to create index: {}", error_body));
93 }
9495 Ok(())
···1+use thiserror::Error;
2+3+/// Represents errors that can occur during search indexing operations.
4+///
5+/// These errors typically occur when interacting with OpenSearch
6+/// during the indexing process.
7+#[derive(Debug, Error)]
8+pub(crate) enum SearchIndexerError {
9+ /// Error when OpenSearch index creation fails.
10+ ///
11+ /// This error occurs when attempting to create an OpenSearch index
12+ /// and the operation fails with a server error response.
13+ #[error("error-smokesignal-search-indexer-1 Failed to create index: {error_body}")]
14+ IndexCreationFailed { error_body: String },
15+}
+17-10
src/task_webhooks.rs
···14use crate::{
15 service::{ServiceDID, ServiceKey},
16 storage::webhook::webhook_failed,
017 webhooks::{
18 EVENT_CREATED_EVENT, RSVP_CREATED_EVENT, SMOKE_SIGNAL_AUTOMATION_SERVICE, TEST_EVENT,
19 },
···153 ) -> Result<()> {
154 // Remove the suffix from service
155 if !service.ends_with(SMOKE_SIGNAL_AUTOMATION_SERVICE) {
156- return Err(anyhow::anyhow!(
157- "Service must end with {}",
158- SMOKE_SIGNAL_AUTOMATION_SERVICE
159- ));
160 }
161162 let service_did = service
···168 .document_storage
169 .get_document_by_did(service_did)
170 .await
171- .map_err(|e| anyhow::anyhow!("Failed to get DID document for {}: {}", service_did, e))?
172- .ok_or_else(|| anyhow::anyhow!("DID document not found for {}", service_did))?;
00000173174 // Extract the service endpoint
175 let automation_service = document
176 .service
177 .iter()
178 .find(|service| service.id.ends_with(SMOKE_SIGNAL_AUTOMATION_SERVICE))
179- .ok_or_else(|| anyhow::anyhow!("service not found in DID document"))?;
180181 // Get the service endpoint - it should be a string URL
182 let endpoint_url = &automation_service.service_endpoint;
···206 );
207208 let token = mint(&self.service_key.0, &header, &claims)
209- .map_err(|e| anyhow::anyhow!("Failed to create JWT: {}", e))?;
00210211 // Prepare headers with JWT authorization
212 let mut headers = reqwest::header::HeaderMap::new();
···241 // Update database to mark webhook as failed
242 webhook_failed(&self.pool, identity, service, &error_msg).await?;
243244- Err(anyhow::anyhow!("Webhook failed: {}", error_msg))
245 }
246 Err(e) => {
247 let error_msg = format!("Request failed: {}", e);
···250 // Update database to mark webhook as failed
251 webhook_failed(&self.pool, identity, service, &error_msg).await?;
252253- Err(anyhow::anyhow!("Webhook request failed: {}", e))
254 }
255 }
256 }
···14use crate::{
15 service::{ServiceDID, ServiceKey},
16 storage::webhook::webhook_failed,
17+ task_webhooks_errors::WebhookError,
18 webhooks::{
19 EVENT_CREATED_EVENT, RSVP_CREATED_EVENT, SMOKE_SIGNAL_AUTOMATION_SERVICE, TEST_EVENT,
20 },
···154 ) -> Result<()> {
155 // Remove the suffix from service
156 if !service.ends_with(SMOKE_SIGNAL_AUTOMATION_SERVICE) {
157+ return Err(WebhookError::InvalidServiceSuffix {
158+ suffix: SMOKE_SIGNAL_AUTOMATION_SERVICE.to_string()
159+ }.into());
0160 }
161162 let service_did = service
···168 .document_storage
169 .get_document_by_did(service_did)
170 .await
171+ .map_err(|e| WebhookError::DidDocumentRetrievalFailed {
172+ did: service_did.to_string(),
173+ error: e.to_string()
174+ })?
175+ .ok_or_else(|| WebhookError::DidDocumentNotFound {
176+ did: service_did.to_string()
177+ })?;
178179 // Extract the service endpoint
180 let automation_service = document
181 .service
182 .iter()
183 .find(|service| service.id.ends_with(SMOKE_SIGNAL_AUTOMATION_SERVICE))
184+ .ok_or(WebhookError::ServiceNotFoundInDidDocument)?;
185186 // Get the service endpoint - it should be a string URL
187 let endpoint_url = &automation_service.service_endpoint;
···211 );
212213 let token = mint(&self.service_key.0, &header, &claims)
214+ .map_err(|e| WebhookError::JwtCreationFailed {
215+ error: e.to_string()
216+ })?;
217218 // Prepare headers with JWT authorization
219 let mut headers = reqwest::header::HeaderMap::new();
···248 // Update database to mark webhook as failed
249 webhook_failed(&self.pool, identity, service, &error_msg).await?;
250251+ Err(WebhookError::WebhookRequestFailed { error: error_msg }.into())
252 }
253 Err(e) => {
254 let error_msg = format!("Request failed: {}", e);
···257 // Update database to mark webhook as failed
258 webhook_failed(&self.pool, identity, service, &error_msg).await?;
259260+ Err(WebhookError::WebhookTransportFailed { error: e.to_string() }.into())
261 }
262 }
263 }
···1+use thiserror::Error;
2+3+/// Represents errors that can occur during webhook processing.
4+///
5+/// These errors typically occur when processing webhook notifications,
6+/// including service validation, DID resolution, and HTTP request failures.
7+#[derive(Debug, Error)]
8+pub(crate) enum WebhookError {
9+ /// Error when a service doesn't end with the required suffix.
10+ ///
11+ /// This error occurs when validating a webhook service URL that doesn't
12+ /// end with the expected automation service suffix.
13+ #[error("error-smokesignal-webhook-1 Service must end with {suffix}")]
14+ InvalidServiceSuffix { suffix: String },
15+16+ /// Error when failing to get a DID document.
17+ ///
18+ /// This error occurs when attempting to retrieve a DID document
19+ /// for service validation fails.
20+ #[error("error-smokesignal-webhook-2 Failed to get DID document for {did}: {error}")]
21+ DidDocumentRetrievalFailed { did: String, error: String },
22+23+ /// Error when a DID document is not found.
24+ ///
25+ /// This error occurs when a required DID document cannot be found
26+ /// in the system.
27+ #[error("error-smokesignal-webhook-3 DID document not found for {did}")]
28+ DidDocumentNotFound { did: String },
29+30+ /// Error when a service is not found in a DID document.
31+ ///
32+ /// This error occurs when a DID document doesn't contain the expected
33+ /// automation service entry.
34+ #[error("error-smokesignal-webhook-4 service not found in DID document")]
35+ ServiceNotFoundInDidDocument,
36+37+ /// Error when JWT creation fails.
38+ ///
39+ /// This error occurs when attempting to create a JWT token for
40+ /// webhook authentication fails.
41+ #[error("error-smokesignal-webhook-5 Failed to create JWT: {error}")]
42+ JwtCreationFailed { error: String },
43+44+ /// Error when a webhook request fails with an error response.
45+ ///
46+ /// This error occurs when the webhook endpoint returns an error
47+ /// status code or the request fails.
48+ #[error("error-smokesignal-webhook-6 Webhook failed: {error}")]
49+ WebhookRequestFailed { error: String },
50+51+ /// Error when a webhook request fails due to network or other issues.
52+ ///
53+ /// This error occurs when the HTTP request to the webhook endpoint
54+ /// fails due to network issues or other transport problems.
55+ #[error("error-smokesignal-webhook-7 Webhook request failed: {error}")]
56+ WebhookTransportFailed { error: String },
57+}