···232232 - `atproto-client` - AT Protocol client functionality
233233 - `atproto-record` - Record management
234234 - `atproto-jetstream` - Firehose event streaming
235235- - `atproto-xrpcs` - XRPC server implementation235235+ - `atproto-xrpcs` - XRPC server implementation
236236+237237+## Error Handling
238238+239239+All error strings must use this format:
240240+241241+ error-smokesignal-<domain>-<number> <message>: <details>
242242+243243+Example errors:
244244+245245+* error-smokesignal-resolve-1 Multiple DIDs resolved for method
246246+* error-smokesignal-plc-1 HTTP request failed: https://google.com/ Not Found
247247+* error-smokesignal-key-1 Error decoding key: invalid
248248+249249+Errors should be represented as enums using the `thiserror` library when possible using `src/errors.rs` as a reference and example.
250250+251251+Avoid creating new errors with the `anyhow!(...)` macro.
···1010 ///
1111 /// This error occurs when the application starts up and a required
1212 /// environment variable is missing from the execution environment.
1313- #[error("error-config-1 {0} must be set")]
1313+ #[error("error-smokesignal-config-1 {0} must be set")]
1414 EnvVarRequired(String),
15151616 /// Error when the signing keys file cannot be read.
···1818 /// This error occurs when the application fails to read the file
1919 /// containing signing keys, typically due to file system permissions
2020 /// or missing file issues.
2121- #[error("error-config-2 Unable to read signing keys file: {0:?}")]
2121+ #[error("error-smokesignal-config-2 Unable to read signing keys file: {0:?}")]
2222 ReadSigningKeysFailed(std::io::Error),
23232424 /// Error when the signing keys file cannot be parsed.
2525 ///
2626 /// This error occurs when the signing keys file contains malformed JSON
2727 /// that cannot be properly deserialized.
2828- #[error("error-config-3 Unable to parse signing keys file: {0:?}")]
2828+ #[error("error-smokesignal-config-3 Unable to parse signing keys file: {0:?}")]
2929 ParseSigningKeysFailed(serde_json::Error),
30303131 /// Error when no valid signing keys are found.
3232 ///
3333 /// This error occurs when the signing keys file does not contain any
3434 /// valid keys that the application can use for signing operations.
3535- #[error("error-config-4 Signing keys must contain at least one valid key")]
3535+ #[error("error-smokesignal-config-4 Signing keys must contain at least one valid key")]
3636 EmptySigningKeys,
37373838 /// Error when no valid OAuth active keys are found.
3939 ///
4040 /// This error occurs when the configuration does not include any
4141 /// valid keys that can be used for OAuth operations.
4242- #[error("error-config-6 OAuth active keys must contain at least one valid key")]
4242+ #[error("error-smokesignal-config-5 OAuth active keys must contain at least one valid key")]
4343 EmptyOAuthActiveKeys,
44444545 /// Error when no valid invitation active keys are found.
4646 ///
4747 /// This error occurs when the configuration does not include any
4848 /// valid keys that can be used for invitation operations.
4949- #[error("error-config-7 Invitation active keys must contain at least one valid key")]
4949+ #[error("error-smokesignal-config-6 Invitation active keys must contain at least one valid key")]
5050 EmptyInvitationActiveKeys,
51515252 /// Error when the PORT environment variable cannot be parsed.
5353 ///
5454 /// This error occurs when the PORT environment variable contains a value
5555 /// that cannot be parsed as a valid u16 integer.
5656- #[error("error-config-8 Parsing PORT into u16 failed: {0:?}")]
5656+ #[error("error-smokesignal-config-7 Parsing PORT into u16 failed: {0:?}")]
5757 PortParsingFailed(std::num::ParseIntError),
58585959 /// Error when the HTTP_COOKIE_KEY cannot be decoded.
6060 ///
6161 /// This error occurs when the HTTP_COOKIE_KEY environment variable
6262 /// contains a value that is not valid base64-encoded data.
6363- #[error("error-config-9 Unable to base64 decode HTTP_COOKIE_KEY: {0:?}")]
6363+ #[error("error-smokesignal-config-8 Unable to base64 decode HTTP_COOKIE_KEY: {0:?}")]
6464 CookieKeyDecodeFailed(base64::DecodeSliceError),
65656666 /// Error when the decoded HTTP_COOKIE_KEY cannot be processed.
6767 ///
6868 /// This error occurs when the decoded HTTP_COOKIE_KEY has an invalid
6969 /// format or length that prevents it from being used.
7070- #[error("error-config-10 Unable to process decoded HTTP_COOKIE_KEY")]
7070+ #[error("error-smokesignal-config-9 Unable to process decoded HTTP_COOKIE_KEY")]
7171 CookieKeyProcessFailed,
72727373 /// Error when version information is not available.
7474 ///
7575 /// This error occurs when neither GIT_HASH nor CARGO_PKG_VERSION
7676 /// environment variables are set, preventing version identification.
7777- #[error("error-config-11 One of GIT_HASH or CARGO_PKG_VERSION must be set")]
7777+ #[error("error-smokesignal-config-10 One of GIT_HASH or CARGO_PKG_VERSION must be set")]
7878 VersionNotSet,
79798080 /// Error when a referenced signing key is not found.
8181 ///
8282 /// This error occurs when attempting to use a signing key that
8383 /// does not exist in the loaded signing keys configuration.
8484- #[error("error-config-12 Signing key not found")]
8484+ #[error("error-smokesignal-config-11 Signing key not found")]
8585 SigningKeyNotFound,
86868787 /// Error when a DNS nameserver IP cannot be parsed.
8888 ///
8989 /// This error occurs when the DNS_NAMESERVERS environment variable contains
9090 /// an IP address that cannot be parsed as a valid IpAddr.
9191- #[error("error-config-13 Unable to parse nameserver IP '{0}': {1}")]
9191+ #[error("error-smokesignal-config-12 Unable to parse nameserver IP '{0}': {1}")]
9292 NameserverParsingFailed(String, std::net::AddrParseError),
93939494 /// Error when the signing keys file is not found.
9595 ///
9696 /// This error occurs when the file specified in the SIGNING_KEYS environment
9797 /// variable does not exist on the file system.
9898- #[error("error-config-14 Signing keys file not found: {0}")]
9898+ #[error("error-smokesignal-config-13 Signing keys file not found: {0}")]
9999 SigningKeysFileNotFound(String),
100100101101 /// Error when the signing keys file is empty.
102102 ///
103103 /// This error occurs when the file specified in the SIGNING_KEYS environment
104104 /// variable exists but contains no data.
105105- #[error("error-config-15 Signing keys file is empty")]
105105+ #[error("error-smokesignal-config-14 Signing keys file is empty")]
106106 EmptySigningKeysFile,
107107108108 /// Error when the JWKS structure doesn't contain any keys.
109109 ///
110110 /// This error occurs when the signing keys file contains a valid JWKS structure,
111111 /// but the 'keys' array is empty.
112112- #[error("error-config-16 No keys found in JWKS")]
112112+ #[error("error-smokesignal-config-15 No keys found in JWKS")]
113113 MissingKeysInJWKS,
114114115115 /// Error when signing keys fail validation.
116116 ///
117117 /// This error occurs when the signing keys file contains keys
118118 /// that fail validation checks (such as having invalid format).
119119- #[error("error-config-17 Signing keys validation failed: {0:?}")]
119119+ #[error("error-smokesignal-config-16 Signing keys validation failed: {0:?}")]
120120 SigningKeysValidationFailed(Vec<String>),
121121122122 /// Error when AIP OAuth configuration is incomplete.
···124124 /// This error occurs when oauth_backend is set to "aip" but
125125 /// required AIP configuration values are missing.
126126 #[error(
127127- "error-config-18 When oauth_backend is 'aip', AIP_HOSTNAME, AIP_CLIENT_ID, and AIP_CLIENT_SECRET must all be set"
127127+ "error-smokesignal-config-17 When oauth_backend is 'aip', AIP_HOSTNAME, AIP_CLIENT_ID, and AIP_CLIENT_SECRET must all be set"
128128 )]
129129 AipConfigurationIncomplete,
130130···132132 ///
133133 /// This error occurs when the OAUTH_BACKEND environment variable
134134 /// contains a value other than "aip" or "pds".
135135- #[error("error-config-19 oauth_backend must be either 'aip' or 'pds', got: {0}")]
135135+ #[error("error-smokesignal-config-18 oauth_backend must be either 'aip' or 'pds', got: {0}")]
136136 InvalidOAuthBackend(String),
137137+138138+ /// Error when signing keys list is empty.
139139+ ///
140140+ /// This error occurs when attempting to select an OAuth signing key
141141+ /// but the signing keys list contains no entries.
142142+ #[error("error-smokesignal-config-19 signing keys is empty")]
143143+ SigningKeysEmpty,
144144+145145+ /// Error when signing keys are not available for AIP OAuth backend.
146146+ ///
147147+ /// This error occurs when attempting to access signing keys
148148+ /// while using the AIP OAuth backend, which doesn't support signing keys.
149149+ #[error("error-smokesignal-config-20 signing keys not available for AIP OAuth backend")]
150150+ SigningKeysNotAvailableForAIP,
137151}
+1-1
src/errors.rs
···22//!
33//! All errors are represented as a string in this format:
44//!
55-//! "error-<domain>-<number> <message>: <details>"
55+//! "error-smokesignal-<domain>-<number> <message>: <details>"
66//!
77//! The first part containing the "error-" prefix, domain, and number, is used
88//! to uniquely identify the error. This standard code format is used to convey
+4-4
src/http/errors/common_error.rs
···77 ///
88 /// This error occurs when a URL contains a handle slug that doesn't conform
99 /// to the expected format or contains invalid characters.
1010- #[error("error-common-1 Invalid handle slug")]
1010+ #[error("error-smokesignal-common-1 Invalid handle slug")]
1111 InvalidHandleSlug,
12121313 /// Error when a user lacks permission for an action.
1414 ///
1515 /// This error occurs when a user attempts to perform an action they
1616 /// are not authorized to do, such as modifying another user's data.
1717- #[error("error-common-2 Not authorized to perform this action")]
1717+ #[error("error-smokesignal-common-2 Not authorized to perform this action")]
1818 NotAuthorized,
19192020 /// Error when a required field is missing.
2121 ///
2222 /// This error occurs when a form or request is missing a mandatory field
2323 /// that is needed to complete the operation.
2424- #[error("error-common-4 Required field not provided")]
2424+ #[error("error-smokesignal-common-3 Required field not provided")]
2525 FieldRequired,
26262727 /// Error when event data has an invalid format or is corrupted.
2828 ///
2929 /// This error occurs when event data doesn't match the expected format
3030 /// or appears to be corrupted or tampered with.
3131- #[error("error-common-9 Invalid event format or corrupted data")]
3131+ #[error("error-smokesignal-common-4 Invalid event format or corrupted data")]
3232 InvalidEventFormat,
3333}
+9-2
src/http/errors/create_event_errors.rs
···1010 ///
1111 /// This error occurs when a user attempts to create an event without
1212 /// specifying a name, which is a required field.
1313- #[error("error-create-event-1 Name not set")]
1313+ #[error("error-smokesignal-create-event-1 Name not set")]
1414 NameNotSet,
15151616 /// Error when the event description is not provided.
1717 ///
1818 /// This error occurs when a user attempts to create an event without
1919 /// specifying a description, which is a required field.
2020- #[error("error-create-event-2 Description not set")]
2020+ #[error("error-smokesignal-create-event-2 Description not set")]
2121 DescriptionNotSet,
2222+2323+ /// Error when the AT Protocol server returns an error response.
2424+ ///
2525+ /// This error occurs when the PDS or other AT Protocol service
2626+ /// returns an error response during event record creation.
2727+ #[error("error-smokesignal-create-event-3 Server error: {message}")]
2828+ ServerError { message: String },
2229}
+15
src/http/errors/create_rsvp_errors.rs
···11+use thiserror::Error;
22+33+/// Represents errors that can occur during RSVP creation.
44+///
55+/// These errors are typically triggered during the process of creating
66+/// and storing RSVP records in the AT Protocol ecosystem.
77+#[derive(Debug, Error)]
88+pub(crate) enum CreateRsvpError {
99+ /// Error when the AT Protocol server returns an error response.
1010+ ///
1111+ /// This error occurs when the PDS or other AT Protocol service
1212+ /// returns an error response during RSVP record creation.
1313+ #[error("error-smokesignal-create-rsvp-1 Server error: {message}")]
1414+ ServerError { message: String },
1515+}
+22
src/http/errors/delete_event_errors.rs
···11+use thiserror::Error;
22+33+/// Represents errors that can occur during event deletion.
44+///
55+/// These errors are typically triggered during validation or authorization
66+/// checks when a user attempts to delete an event.
77+#[derive(Debug, Error)]
88+pub(crate) enum DeleteEventError {
99+ /// Error when the identity does not have an event with the specified AT-URI.
1010+ ///
1111+ /// This error occurs when a user attempts to delete an event that doesn't
1212+ /// exist in their collection or when the AT-URI is invalid.
1313+ #[error("error-smokesignal-delete-event-1 identity does not have event with that AT-URI: {aturi}")]
1414+ EventNotFound { aturi: String },
1515+1616+ /// Error when there is an authentication configuration mismatch.
1717+ ///
1818+ /// This error occurs when the authentication type doesn't match the
1919+ /// configured OAuth backend, indicating a system configuration issue.
2020+ #[error("error-smokesignal-delete-event-2 Authentication configuration mismatch")]
2121+ AuthenticationConfigurationMismatch,
2222+}
+11-4
src/http/errors/edit_event_error.rs
···1111 /// This error occurs when a user attempts to edit an event that they
1212 /// do not have permission to modify, typically because they are not
1313 /// the event creator or an administrator.
1414- #[error("error-edit-event-2 Not authorized to edit this event")]
1414+ #[error("error-smokesignal-edit-event-1 Not authorized to edit this event")]
1515 NotAuthorized,
16161717 /// Error when attempting to edit an unsupported event type.
···2020 /// does not support editing, as only community calendar events can be
2121 /// modified after creation.
2222 #[error(
2323- "error-edit-event-3 Unsupported event type. Only community calendar events can be edited"
2323+ "error-smokesignal-edit-event-2 Unsupported event type. Only community calendar events can be edited"
2424 )]
2525 UnsupportedEventType,
2626···2828 ///
2929 /// This error occurs when a user attempts to modify location information for an event
3030 /// that has multiple locations defined. Such events can only be edited through the API.
3131- #[error("error-edit-event-4 Cannot edit locations: Event has multiple locations")]
3131+ #[error("error-smokesignal-edit-event-3 Cannot edit locations: Event has multiple locations")]
3232 MultipleLocationsPresent,
33333434 /// Error when attempting to edit location data on an event that has an unsupported location type.
3535 ///
3636 /// This error occurs when a user attempts to modify location information for an event
3737 /// that has a location type that is not supported for editing through the web interface.
3838- #[error("error-edit-event-5 Cannot edit locations: Event has unsupported location type")]
3838+ #[error("error-smokesignal-edit-event-4 Cannot edit locations: Event has unsupported location type")]
3939 UnsupportedLocationType,
4040+4141+ /// Error when the AT Protocol server returns an error response.
4242+ ///
4343+ /// This error occurs when the PDS or other AT Protocol service
4444+ /// returns an error response during event record editing.
4545+ #[error("error-smokesignal-edit-event-5 Server error: {message}")]
4646+ ServerError { message: String },
4047}
+3-3
src/http/errors/event_view_errors.rs
···1010 ///
1111 /// This error occurs when an event view request specifies a collection
1212 /// name that doesn't exist or isn't supported by the system.
1313- #[error("error-event-view-1 Invalid collection: {0}")]
1313+ #[error("error-smokesignal-event-view-1 Invalid collection: {0}")]
1414 InvalidCollection(String),
15151616 /// Error when an event name is missing.
1717 ///
1818 /// This error occurs when attempting to view an event that is missing
1919 /// a required name field, which is necessary for display.
2020- #[error("error-event-view-2 Event name is missing")]
2020+ #[error("error-smokesignal-event-view-2 Event name is missing")]
2121 MissingEventName,
22222323 /// Error when RSVP count calculation fails.
2424 ///
2525 /// This error occurs when the system fails to retrieve or calculate
2626 /// the RSVP counts (going, interested, not going) for an event.
2727- #[error("error-event-view-3 Failed to hydrate event RSVP counts: {0}")]
2727+ #[error("error-smokesignal-event-view-3 Failed to hydrate event RSVP counts: {0}")]
2828 FailedToHydrateRsvpCounts(String),
2929}
+1-1
src/http/errors/import_error.rs
···1010 ///
1111 /// This error occurs when attempting to retrieve a list of Smokesignal
1212 /// events during an import operation fails, preventing the import.
1313- #[error("error-import-1 Failed to list Smokesignal events: {0}")]
1313+ #[error("error-smokesignal-import-1 Failed to list Smokesignal events: {0}")]
1414 FailedToListSmokesignalEvents(String),
1515}
+34-5
src/http/errors/login_error.rs
···66/// are logging in to the application, including OAuth flows and DID validation.
77#[derive(Debug, Error)]
88pub(crate) enum LoginError {
99- #[error("error-login-1 DID document does not contain a handle identifier")]
99+ #[error("error-smokesignal-login-1 DID document does not contain a handle identifier")]
1010 NoHandle,
11111212 /// Error when a DID document does not contain a PDS endpoint.
···1414 /// This error occurs during authentication when the user's DID document
1515 /// is retrieved but does not contain a required AT Protocol Personal
1616 /// Data Server (PDS) endpoint.
1717- #[error("error-login-2 DID document does not contain an AT Protocol PDS endpoint")]
1717+ #[error("error-smokesignal-login-2 DID document does not contain an AT Protocol PDS endpoint")]
1818 NoPDS,
19192020 /// Error when an OAuth callback is incomplete.
2121 ///
2222 /// This error occurs when the OAuth authentication flow callback
2323 /// returns with incomplete information, preventing successful authentication.
2424- #[error("error-login-100 OAuth callback incomplete")]
2424+ #[error("error-smokesignal-login-3 OAuth callback incomplete")]
2525 OAuthCallbackIncomplete,
26262727 /// Error when there is an OAuth issuer mismatch.
2828 ///
2929 /// This error occurs when the issuer in the OAuth response does not
3030 /// match the expected issuer, which could indicate a security issue.
3131- #[error("error-login-101 OAuth issuer mismatch")]
3131+ #[error("error-smokesignal-login-4 OAuth issuer mismatch")]
3232 OAuthIssuerMismatch,
33333434 /// Error when the login input appears to be an incomplete AT-handle.
···3636 /// This error occurs when a user enters what appears to be a partial
3737 /// AT-handle (alphanumeric without a domain). The user should append
3838 /// ".bsky.social" and resubmit the form.
3939- #[error("error-login-102 Please add '.bsky.social' to your handle and try again")]
3939+ #[error("error-smokesignal-login-5 Please add '.bsky.social' to your handle and try again")]
4040 IncompleteHandle,
4141+4242+ /// Error when an OAuth request is not found in storage.
4343+ ///
4444+ /// This error occurs during the OAuth callback when the system cannot
4545+ /// find the corresponding OAuth request that was initiated, possibly
4646+ /// due to expiration or invalid state parameter.
4747+ #[error("error-smokesignal-login-6 oauth request not found in storage")]
4848+ OAuthRequestNotFound,
4949+5050+ /// Error when HTTP request for AIP OAuth userinfo fails.
5151+ ///
5252+ /// This error occurs during AIP OAuth flow when the HTTP request
5353+ /// to retrieve user information from the AIP server fails.
5454+ #[error("error-smokesignal-login-7 HTTP request for userinfo failed")]
5555+ AipUserinfoRequestFailed,
5656+5757+ /// Error when parsing AIP OAuth userinfo response fails.
5858+ ///
5959+ /// This error occurs during AIP OAuth flow when the JSON response
6060+ /// from the AIP userinfo endpoint cannot be parsed.
6161+ #[error("error-smokesignal-login-8 Parsing HTTP response for userinfo failed")]
6262+ AipUserinfoParsingFailed,
6363+6464+ /// Error when AIP OAuth server returns an error response.
6565+ ///
6666+ /// This error occurs when the AIP OAuth server returns an error
6767+ /// in the userinfo response instead of valid user claims.
6868+ #[error("error-smokesignal-login-9 AIP OAuth server error: {message}")]
6969+ AipServerError { message: String },
4170}
+5-5
src/http/errors/middleware_errors.rs
···1414 ///
1515 /// This error occurs when attempting to deserialize a web session from JSON
1616 /// format, typically when retrieving a session from storage or a cookie.
1717- #[error("error-websession-1 Unable to deserialize WebSession: {0:?}")]
1717+ #[error("error-smokesignal-websession-1 Unable to deserialize WebSession: {0:?}")]
1818 DeserializeFailed(serde_json::Error),
19192020 /// Error when web session serialization fails.
2121 ///
2222 /// This error occurs when attempting to serialize a web session to JSON
2323 /// format, typically when storing a session in storage or a cookie.
2424- #[error("error-websession-2 Unable to serialize WebSession: {0:?}")]
2424+ #[error("error-smokesignal-websession-2 Unable to serialize WebSession: {0:?}")]
2525 SerializeFailed(serde_json::Error),
2626}
27272828#[derive(Debug, Error)]
2929pub(crate) enum MiddlewareAuthError {
3030- #[error("error-middleware-auth-1 Access Denied: {0}")]
3030+ #[error("error-smokesignal-middleware-auth-1 Access Denied: {0}")]
3131 AccessDenied(String),
32323333- #[error("error-middleware-auth-2 Not Found")]
3333+ #[error("error-smokesignal-middleware-auth-2 Not Found")]
3434 NotFound,
35353636- #[error("error-middleware-auth-3 Unhandled Auth Error: {0:?}")]
3636+ #[error("error-smokesignal-middleware-auth-3 Unhandled Auth Error: {0:?}")]
3737 Anyhow(#[from] anyhow::Error),
3838}
3939
+4-4
src/http/errors/migrate_event_error.rs
···1111 /// This error occurs when a user attempts to migrate an event that they
1212 /// do not have permission to modify, typically because they are not
1313 /// the event creator or an administrator.
1414- #[error("error-migrate-event-2 Not authorized to migrate this event")]
1414+ #[error("error-smokesignal-migrate-event-1 Not authorized to migrate this event")]
1515 NotAuthorized,
16161717 /// Error when attempting to migrate an unsupported event type.
···2020 /// cannot be migrated, as only smokesignal events can be converted to
2121 /// the community event format.
2222 #[error(
2323- "error-migrate-event-3 Unsupported event type. Only smokesignal events can be migrated"
2323+ "error-smokesignal-migrate-event-2 Unsupported event type. Only smokesignal events can be migrated"
2424 )]
2525 UnsupportedEventType,
2626···2828 ///
2929 /// This error occurs when attempting to migrate an event that is already
3030 /// in the community event format, which would be redundant.
3131- #[error("error-migrate-event-4 Event is already a community event")]
3131+ #[error("error-smokesignal-migrate-event-3 Event is already a community event")]
3232 AlreadyMigrated,
33333434 /// Error when a destination URI conflict exists.
3535 ///
3636 /// This error occurs when attempting to migrate an event to a URI that
3737 /// already has an event associated with it, which would cause a conflict.
3838- #[error("error-migrate-event-5 An event already exists at the destination URI")]
3838+ #[error("error-smokesignal-migrate-event-4 An event already exists at the destination URI")]
3939 DestinationExists,
4040}
+2-2
src/http/errors/migrate_rsvp_error.rs
···1212 /// that doesn't match one of the expected values ('going', 'interested',
1313 /// or 'notgoing').
1414 #[error(
1515- "error-migrate-rsvp-1 Invalid RSVP status: {0}. Expected 'going', 'interested', or 'notgoing'."
1515+ "error-smokesignal-migrate-rsvp-1 Invalid RSVP status: {0}. Expected 'going', 'interested', or 'notgoing'."
1616 )]
1717 InvalidRsvpStatus(String),
1818···2121 /// This error occurs when a user attempts to migrate an RSVP that they
2222 /// do not have permission to modify, typically because they are not
2323 /// the RSVP owner or an administrator.
2424- #[error("error-migrate-rsvp-2 Not authorized to migrate this RSVP")]
2424+ #[error("error-smokesignal-migrate-rsvp-2 Not authorized to migrate this RSVP")]
2525 NotAuthorized,
2626}
+4
src/http/errors/mod.rs
···22pub mod admin_errors;
33pub mod common_error;
44pub mod create_event_errors;
55+pub mod create_rsvp_errors;
66+pub mod delete_event_errors;
57pub mod edit_event_error;
68pub mod event_view_errors;
79pub mod import_error;
···15171618pub(crate) use common_error::CommonError;
1719pub(crate) use create_event_errors::CreateEventError;
2020+pub(crate) use create_rsvp_errors::CreateRsvpError;
2121+pub(crate) use delete_event_errors::DeleteEventError;
1822pub(crate) use edit_event_error::EditEventError;
1923pub(crate) use event_view_errors::EventViewError;
2024pub(crate) use import_error::ImportError;
+1-1
src/http/errors/url_error.rs
···1010 ///
1111 /// This error occurs when a URL contains a collection type that is not
1212 /// supported by the system, typically in an AT Protocol URI path.
1313- #[error("error-url-1 Unsupported collection type")]
1313+ #[error("error-smokesignal-url-1 Unsupported collection type")]
1414 UnsupportedCollection,
1515}
+2-2
src/http/errors/view_event_error.rs
···1010 ///
1111 /// This error occurs when attempting to view an event that doesn't
1212 /// exist in the system, typically due to an invalid identifier.
1313- #[error("error-view-event-1 Event not found: {0}")]
1313+ #[error("error-smokesignal-view-event-1 Event not found: {0}")]
1414 EventNotFound(String),
15151616 /// Error when a fallback retrieval method fails.
1717 ///
1818 /// This error occurs when the primary method of retrieving an event fails,
1919 /// and the fallback method also fails to retrieve the event.
2020- #[error("error-view-event-2 Failed to get event from fallback: {0}")]
2020+ #[error("error-smokesignal-view-event-2 Failed to get event from fallback: {0}")]
2121 FallbackFailed(String),
2222}
+6-6
src/http/errors/web_error.rs
···55//! uniformly at the HTTP boundary and converted into appropriate HTTP responses.
66//!
77//! Specific error variants use their own error codes, while general errors use the
88-//! format: `error-web-<number> <message>: <details>`
88+//! format: `error-smokesignal-web-<number> <message>: <details>`
991010use axum::http::StatusCode;
1111use axum::response::IntoResponse;
···3030///
3131/// Most variants use transparent error forwarding to preserve the original error message
3232/// and error code, while a few web-specific errors have their own error code format:
3333-/// `error-web-<number> <message>: <details>`
3333+/// `error-smokesignal-web-<number> <message>: <details>`
3434#[derive(Debug, Error)]
3535pub(crate) enum WebError {
3636 /// Error when authentication middleware fails.
···3939 /// through the authentication middleware, such as invalid credentials or
4040 /// expired sessions.
4141 ///
4242- /// **Error Code:** `error-web-1`
4343- #[error("error-web-1 Middleware Auth Error: {0:?}")]
4242+ /// **Error Code:** `error-smokesignal-web-1`
4343+ #[error("error-smokesignal-web-1 Middleware Auth Error: {0:?}")]
4444 MiddlewareAuthError(#[from] MiddlewareAuthError),
45454646 /// Error when an unexpected error occurs that isn't covered by other error types.
···4848 /// This error is a fallback for any unhandled errors in the system. In production,
4949 /// these should be rare as most errors should be properly typed.
5050 ///
5151- /// **Error Code:** `error-web-2`
5151+ /// **Error Code:** `error-smokesignal-web-2`
5252 ///
5353 /// Note: This should be replaced with more specific error types as part of
5454 /// the ongoing effort to use typed errors throughout the codebase.
5555- #[error("error-web-2 Unhandled web error: {0:?}")]
5555+ #[error("error-smokesignal-web-2 Unhandled web error: {0:?}")]
5656 Anyhow(#[from] anyhow::Error),
57575858 /// Common HTTP errors.
···4747pub mod server;
4848pub mod tab_selector;
4949pub mod templates;
5050+pub mod timezone_errors;
5051pub mod timezones;
5152pub mod utils;
+22
src/http/timezone_errors.rs
···11+use thiserror::Error;
22+33+/// Represents errors that can occur during timezone and datetime operations.
44+///
55+/// These errors typically occur when parsing date/time strings or converting
66+/// between timezones, especially during daylight saving time transitions.
77+#[derive(Debug, Error)]
88+pub(crate) enum TimezoneError {
99+ /// Error when parsing date and time strings fails.
1010+ ///
1111+ /// This error occurs when the provided date or time string cannot be parsed
1212+ /// into a valid datetime format, typically due to incorrect format or invalid values.
1313+ #[error("error-smokesignal-timezone-1 Failed to parse date and time: {error}")]
1414+ DateTimeParsingFailed { error: String },
1515+1616+ /// Error when local time is ambiguous or non-existent.
1717+ ///
1818+ /// This error occurs during daylight saving time transitions when a local time
1919+ /// either doesn't exist (spring forward) or is ambiguous (fall back).
2020+ #[error("error-smokesignal-timezone-2 Ambiguous or non-existent local time")]
2121+ AmbiguousOrNonExistentLocalTime,
2222+}
+4-3
src/http/timezones.rs
···11-use anyhow::{Result, anyhow};
11+use anyhow::Result;
22use chrono::{DateTime, NaiveDateTime, Utc};
33use itertools::Itertools;
4455+use crate::http::timezone_errors::TimezoneError;
56use crate::storage::identity_profile::model::IdentityProfile;
6778pub(crate) fn supported_timezones(handle: Option<&IdentityProfile>) -> (&str, Vec<&str>) {
···79808081 // Parse the combined string into a NaiveDateTime
8182 let naive_dt = NaiveDateTime::parse_from_str(&datetime_str, "%Y-%m-%dT%H:%M")
8282- .map_err(|e| anyhow!("Failed to parse date and time: {}", e))?;
8383+ .map_err(|e| TimezoneError::DateTimeParsingFailed { error: e.to_string() })?;
83848485 // Convert to timezone-aware datetime in the specified timezone
8586 let local_dt = naive_dt
8687 .and_local_timezone(timezone)
8788 .single()
8888- .ok_or_else(|| anyhow!("Ambiguous or non-existent local time"))?;
8989+ .ok_or(TimezoneError::AmbiguousOrNonExistentLocalTime)?;
89909091 // Convert to UTC
9192 Ok(local_dt.with_timezone(&Utc))
···77pub mod i18n;
88pub mod key_provider;
99pub mod processor;
1010+pub mod processor_errors;
1011pub mod refresh_tokens_errors;
1112pub mod service;
1213pub mod storage;
···1415pub mod task_oauth_requests_cleanup;
1516pub mod task_refresh_tokens;
1617pub mod task_search_indexer;
1818+pub mod task_search_indexer_errors;
1719pub mod task_webhooks;
2020+pub mod task_webhooks_errors;
1821pub mod webhooks;
+3-2
src/processor.rs
···11-use anyhow::{Result, anyhow};
11+use anyhow::Result;
22use atproto_client::com::atproto::repo::get_blob;
33use atproto_identity::model::Document;
44use atproto_identity::storage::DidDocumentStorage;
···88use std::sync::Arc;
991010use crate::atproto::lexicon::community::lexicon::calendar::event::Event;
1111+use crate::processor_errors::ProcessorError;
1112use crate::atproto::lexicon::community::lexicon::calendar::event::Media;
1213use crate::atproto::lexicon::community::lexicon::calendar::event::NSID as LexiconCommunityEventNSID;
1314use crate::atproto::lexicon::community::lexicon::calendar::rsvp::NSID as LexiconCommunityRSVPNSID;
···144145 let pds_endpoints = document.pds_endpoints();
145146 let pds_endpoint = pds_endpoints
146147 .first()
147147- .ok_or_else(|| anyhow!("no PDS in DID"))?;
148148+ .ok_or(ProcessorError::NoPdsInDid)?;
148149149150 let name = match &event_record {
150151 Event::Current { name, .. } => name.clone(),
+15
src/processor_errors.rs
···11+use thiserror::Error;
22+33+/// Represents errors that can occur during event processing.
44+///
55+/// These errors typically occur when processing events from the Jetstream
66+/// consumer, including DID resolution and data validation issues.
77+#[derive(Debug, Error)]
88+pub(crate) enum ProcessorError {
99+ /// Error when a DID document does not contain any PDS endpoints.
1010+ ///
1111+ /// This error occurs when attempting to process an event from a DID
1212+ /// that has no Personal Data Server (PDS) endpoints configured.
1313+ #[error("error-smokesignal-processor-1 no PDS in DID")]
1414+ NoPdsInDid,
1515+}
+4-4
src/refresh_tokens_errors.rs
···1010 ///
1111 /// This error occurs when attempting to refresh a token but the necessary
1212 /// secret key for signing the request is not available in the configuration.
1313- #[error("error-refresh-1 Secret signing key not found")]
1313+ #[error("error-smokesignal-refresh-1 Secret signing key not found")]
1414 SecretSigningKeyNotFound,
15151616 /// Error when creating a DPoP proof for token refresh fails.
1717 ///
1818 /// This error occurs when there is an issue with the cryptographic operations
1919 /// required to generate a DPoP (Demonstrating Proof-of-Possession) proof.
2020- #[error("error-refresh-2 Failed to create DPoP proof: {0:?}")]
2020+ #[error("error-smokesignal-refresh-2 Failed to create DPoP proof: {0:?}")]
2121 DpopProofCreationFailed(elliptic_curve::Error),
22222323 /// Error when a session cannot be placed in the refresh queue.
2424 ///
2525 /// This error occurs when there is an issue with the Redis-backed queue
2626 /// used to manage session refresh operations.
2727- #[error("error-refresh-3 Failed to place session group into refresh queue: {0:?}")]
2727+ #[error("error-smokesignal-refresh-3 Failed to place session group into refresh queue: {0:?}")]
2828 PlaceInRefreshQueueFailed(deadpool_redis::redis::RedisError),
29293030 /// Error when the identity document cannot be found.
3131 ///
3232 /// This error occurs when attempting to refresh a token but the necessary
3333 /// identity document for the user is not available in storage.
3434- #[error("error-refresh-4 Identity document not found")]
3434+ #[error("error-smokesignal-refresh-4 Identity document not found")]
3535 IdentityDocumentNotFound,
3636}
+4-4
src/storage/atproto.rs
···4444impl DidDocumentStorage for PostgresDidDocumentStorage {
4545 async fn get_document_by_did(&self, did: &str) -> Result<Option<Document>, anyhow::Error> {
4646 if did.trim().is_empty() {
4747- return Err(anyhow::anyhow!("DID cannot be empty"));
4747+ return Err(StorageError::DidCannotBeEmpty.into());
4848 }
49495050 let mut tx = self.pool.begin().await?;
···97979898 async fn delete_document_by_did(&self, did: &str) -> Result<(), anyhow::Error> {
9999 if did.trim().is_empty() {
100100- return Err(anyhow::anyhow!("DID cannot be empty"));
100100+ return Err(StorageError::DidCannotBeEmpty.into());
101101 }
102102103103 let mut tx = self.pool.begin().await?;
···226226 state: &str,
227227 ) -> Result<Option<OAuthRequest>, anyhow::Error> {
228228 if state.trim().is_empty() {
229229- return Err(anyhow::anyhow!("OAuth state cannot be empty"));
229229+ return Err(StorageError::OAuthStateCannotBeEmpty.into());
230230 }
231231232232 let mut tx = self.pool.begin().await?;
···263263264264 async fn delete_oauth_request_by_state(&self, state: &str) -> Result<(), anyhow::Error> {
265265 if state.trim().is_empty() {
266266- return Err(anyhow::anyhow!("OAuth state cannot be empty"));
266266+ return Err(StorageError::OAuthStateCannotBeEmpty.into());
267267 }
268268269269 let mut tx = self.pool.begin().await?;
+1-1
src/storage/content.rs
···447447 Err(e) => {
448448 // If it's a not-found error, add to the not-found filter
449449 if e.to_string()
450450- .starts_with("error-content-2 File storage operation failed:")
450450+ .starts_with("error-smokesignal-content-2 File storage operation failed:")
451451 {
452452 self.add_to_not_found_filter(cid).await;
453453 }
+33-19
src/storage/errors.rs
···1111 /// This error occurs when attempting to convert a JSON Web Key (JWK)
1212 /// into a secret key for DPoP (Demonstrating Proof-of-Possession) operations,
1313 /// typically due to invalid key format or cryptographic errors.
1414- #[error("error-oauth-model-1 Failed to create DPoP secret from JWK: {0:?}")]
1414+ #[error("error-smokesignal-oauth-model-1 Failed to create DPoP secret from JWK: {0:?}")]
1515 DpopSecretFromJwkFailed(elliptic_curve::Error),
16161717 /// Error when the OAuth flow state is invalid.
···1919 /// This error occurs when the state parameter in an OAuth flow
2020 /// does not match the expected value or cannot be verified,
2121 /// which could indicate a potential CSRF attack or session mismatch.
2222- #[error("error-oauth-model-2 Invalid OAuth flow state")]
2222+ #[error("error-smokesignal-oauth-model-2 Invalid OAuth flow state")]
2323 InvalidOAuthFlowState(),
24242525 /// Error when deserializing DPoP JWK from string fails.
2626 ///
2727 /// This error occurs when attempting to deserialize a string-encoded
2828 /// JSON Web Key (JWK) for DPoP operations, typically due to invalid JSON format.
2929- #[error("error-oauth-model-5 Failed to deserialize DPoP JWK: {0:?}")]
2929+ #[error("error-smokesignal-oauth-model-3 Failed to deserialize DPoP JWK: {0:?}")]
3030 DpopJwkDeserializationFailed(serde_json::Error),
31313232 /// Error when required OAuth session data is missing.
···3434 /// This error occurs when attempting to use an OAuth session
3535 /// that is missing critical data needed for authentication or
3636 /// authorization operations, such as tokens or session identifiers.
3737- #[error("error-oauth-model-3 Missing required OAuth session data")]
3737+ #[error("error-smokesignal-oauth-model-4 Missing required OAuth session data")]
3838 MissingRequiredOAuthSessionData(),
39394040 /// Error when an OAuth session has expired.
···4242 /// This error occurs when attempting to use an OAuth session
4343 /// that has exceeded its validity period and is no longer usable
4444 /// for authentication or authorization purposes.
4545- #[error("error-oauth-model-4 OAuth session has expired")]
4545+ #[error("error-smokesignal-oauth-model-5 OAuth session has expired")]
4646 OAuthSessionExpired(),
4747}
4848···5353 ///
5454 /// This error occurs when attempting to retrieve a web session using an
5555 /// invalid or expired session ID.
5656- #[error("error-storage-1 Web session not found")]
5656+ #[error("error-smokesignal-storage-1 Web session not found")]
5757 WebSessionNotFound,
58585959 /// Error when a handle cannot be found in the database.
6060 ///
6161 /// This error occurs when attempting to retrieve a user handle that
6262 /// doesn't exist in the system.
6363- #[error("error-storage-2 Handle not found")]
6363+ #[error("error-smokesignal-storage-2 Handle not found")]
6464 HandleNotFound,
65656666 /// Error when a database record cannot be found.
6767 ///
6868 /// This error occurs when attempting to retrieve a specific record
6969 /// using an ID or other identifier that doesn't exist in the database.
7070- #[error("error-storage-3 Record not found: {0} {1:?}")]
7070+ #[error("error-smokesignal-storage-3 Record not found: {0} {1:?}")]
7171 RowNotFound(String, sqlx::Error),
72727373 /// Error when a database transaction cannot be committed.
7474 ///
7575 /// This error occurs when there is an issue finalizing a database
7676 /// transaction, potentially causing data inconsistency.
7777- #[error("error-storage-4 Cannot commit database transaction: {0:?}")]
7777+ #[error("error-smokesignal-storage-4 Cannot commit database transaction: {0:?}")]
7878 CannotCommitDatabaseTransaction(sqlx::Error),
79798080 /// Error when a database transaction cannot be started.
8181 ///
8282 /// This error occurs when there is an issue initiating a database
8383 /// transaction, typically due to connection issues or database constraints.
8484- #[error("error-storage-5 Cannot begin database transaction: {0:?}")]
8484+ #[error("error-smokesignal-storage-5 Cannot begin database transaction: {0:?}")]
8585 CannotBeginDatabaseTransaction(sqlx::Error),
86868787 /// Error when a database query cannot be executed.
8888 ///
8989 /// This error occurs when a SQL query fails to execute, typically due to
9090 /// syntax errors, constraint violations, or database connectivity issues.
9191- #[error("error-storage-6 Unable to execute query: {0:?}")]
9191+ #[error("error-smokesignal-storage-6 Unable to execute query: {0:?}")]
9292 UnableToExecuteQuery(sqlx::Error),
93939494 /// Error when an OAuth request cannot be found.
9595 ///
9696 /// This error occurs when attempting to retrieve an OAuth request
9797 /// that doesn't exist or has expired.
9898- #[error("error-storage-7 OAuth request not found")]
9898+ #[error("error-smokesignal-storage-7 OAuth request not found")]
9999 OAuthRequestNotFound,
100100101101 /// Error when an RSVP cannot be found.
102102 ///
103103 /// This error occurs when attempting to retrieve an RSVP record
104104 /// that doesn't exist in the database.
105105- #[error("error-storage-8 RSVP not found")]
105105+ #[error("error-smokesignal-storage-8 RSVP not found")]
106106 RSVPNotFound,
107107108108 /// Error when an OAuth model operation fails.
109109 ///
110110 /// This error occurs when there's an issue with OAuth model operations,
111111 /// such as token generation, validation, or storage.
112112- #[error("error-storage-9 OAuth model error: {0}")]
112112+ #[error("error-smokesignal-storage-9 OAuth model error: {0}")]
113113 OAuthModelError(#[from] OAuthModelError),
114114+115115+ /// Error when a DID parameter is empty or invalid.
116116+ ///
117117+ /// This error occurs when a DID string is empty, which is not valid
118118+ /// for DID-based operations.
119119+ #[error("error-smokesignal-storage-10 DID cannot be empty")]
120120+ DidCannotBeEmpty,
121121+122122+ /// Error when an OAuth state parameter is empty or invalid.
123123+ ///
124124+ /// This error occurs when an OAuth state string is empty, which is not valid
125125+ /// for OAuth operations.
126126+ #[error("error-smokesignal-storage-11 OAuth state cannot be empty")]
127127+ OAuthStateCannotBeEmpty,
114128}
115129116130/// Represents errors that can occur during cache operations.
···120134 ///
121135 /// This error occurs when the system fails to initialize the Redis
122136 /// connection pool, typically due to configuration or connectivity issues.
123123- #[error("error-cache-1 Failed to create cache pool: {0:?}")]
137137+ #[error("error-smokesignal-cache-1 Failed to create cache pool: {0:?}")]
124138 FailedToCreatePool(deadpool_redis::CreatePoolError),
125139126140 /// Error when a cache connection cannot be obtained.
127141 ///
128142 /// This error occurs when the system fails to get a connection from
129143 /// the Redis connection pool, typically due to pool exhaustion or connectivity issues.
130130- #[error("error-cache-2 Failed to get connection: {0:?}")]
144144+ #[error("error-smokesignal-cache-2 Failed to get connection: {0:?}")]
131145 FailedToGetConnection(deadpool_redis::PoolError),
132146133147 /// Error when a session cannot be placed in the refresh queue.
134148 ///
135149 /// This error occurs when the system fails to add a session to the
136150 /// Redis-backed refresh queue, typically due to Redis errors or connectivity issues.
137137- #[error("error-cache-3 Failed to place session group into refresh queue: {0:?}")]
151151+ #[error("error-smokesignal-cache-3 Failed to place session group into refresh queue: {0:?}")]
138152 FailedToPlaceInRefreshQueue(deadpool_redis::redis::RedisError),
139153}
140154141155#[derive(Debug, Error)]
142156pub enum ContentError {
143143- #[error("error-content-1 Invalid S3 URL format: {details}")]
157157+ #[error("error-smokesignal-content-1 Invalid S3 URL format: {details}")]
144158 /// Failed to parse S3 URL format from environment variable.
145159 ConfigS3UrlInvalid {
146160 /// Details about the S3 URL parsing error.
147161 details: String,
148162 },
149163150150- #[error("error-content-2 File storage operation failed: {operation}")]
164164+ #[error("error-smokesignal-content-2 File storage operation failed: {operation}")]
151165 /// File storage operation failed.
152166 StorageFileOperationFailed {
153167 /// Description of the failed file operation.
···11+use thiserror::Error;
22+33+/// Represents errors that can occur during search indexing operations.
44+///
55+/// These errors typically occur when interacting with OpenSearch
66+/// during the indexing process.
77+#[derive(Debug, Error)]
88+pub(crate) enum SearchIndexerError {
99+ /// Error when OpenSearch index creation fails.
1010+ ///
1111+ /// This error occurs when attempting to create an OpenSearch index
1212+ /// and the operation fails with a server error response.
1313+ #[error("error-smokesignal-search-indexer-1 Failed to create index: {error_body}")]
1414+ IndexCreationFailed { error_body: String },
1515+}
+17-10
src/task_webhooks.rs
···1414use crate::{
1515 service::{ServiceDID, ServiceKey},
1616 storage::webhook::webhook_failed,
1717+ task_webhooks_errors::WebhookError,
1718 webhooks::{
1819 EVENT_CREATED_EVENT, RSVP_CREATED_EVENT, SMOKE_SIGNAL_AUTOMATION_SERVICE, TEST_EVENT,
1920 },
···153154 ) -> Result<()> {
154155 // Remove the suffix from service
155156 if !service.ends_with(SMOKE_SIGNAL_AUTOMATION_SERVICE) {
156156- return Err(anyhow::anyhow!(
157157- "Service must end with {}",
158158- SMOKE_SIGNAL_AUTOMATION_SERVICE
159159- ));
157157+ return Err(WebhookError::InvalidServiceSuffix {
158158+ suffix: SMOKE_SIGNAL_AUTOMATION_SERVICE.to_string()
159159+ }.into());
160160 }
161161162162 let service_did = service
···168168 .document_storage
169169 .get_document_by_did(service_did)
170170 .await
171171- .map_err(|e| anyhow::anyhow!("Failed to get DID document for {}: {}", service_did, e))?
172172- .ok_or_else(|| anyhow::anyhow!("DID document not found for {}", service_did))?;
171171+ .map_err(|e| WebhookError::DidDocumentRetrievalFailed {
172172+ did: service_did.to_string(),
173173+ error: e.to_string()
174174+ })?
175175+ .ok_or_else(|| WebhookError::DidDocumentNotFound {
176176+ did: service_did.to_string()
177177+ })?;
173178174179 // Extract the service endpoint
175180 let automation_service = document
176181 .service
177182 .iter()
178183 .find(|service| service.id.ends_with(SMOKE_SIGNAL_AUTOMATION_SERVICE))
179179- .ok_or_else(|| anyhow::anyhow!("service not found in DID document"))?;
184184+ .ok_or(WebhookError::ServiceNotFoundInDidDocument)?;
180185181186 // Get the service endpoint - it should be a string URL
182187 let endpoint_url = &automation_service.service_endpoint;
···206211 );
207212208213 let token = mint(&self.service_key.0, &header, &claims)
209209- .map_err(|e| anyhow::anyhow!("Failed to create JWT: {}", e))?;
214214+ .map_err(|e| WebhookError::JwtCreationFailed {
215215+ error: e.to_string()
216216+ })?;
210217211218 // Prepare headers with JWT authorization
212219 let mut headers = reqwest::header::HeaderMap::new();
···241248 // Update database to mark webhook as failed
242249 webhook_failed(&self.pool, identity, service, &error_msg).await?;
243250244244- Err(anyhow::anyhow!("Webhook failed: {}", error_msg))
251251+ Err(WebhookError::WebhookRequestFailed { error: error_msg }.into())
245252 }
246253 Err(e) => {
247254 let error_msg = format!("Request failed: {}", e);
···250257 // Update database to mark webhook as failed
251258 webhook_failed(&self.pool, identity, service, &error_msg).await?;
252259253253- Err(anyhow::anyhow!("Webhook request failed: {}", e))
260260+ Err(WebhookError::WebhookTransportFailed { error: e.to_string() }.into())
254261 }
255262 }
256263 }
+57
src/task_webhooks_errors.rs
···11+use thiserror::Error;
22+33+/// Represents errors that can occur during webhook processing.
44+///
55+/// These errors typically occur when processing webhook notifications,
66+/// including service validation, DID resolution, and HTTP request failures.
77+#[derive(Debug, Error)]
88+pub(crate) enum WebhookError {
99+ /// Error when a service doesn't end with the required suffix.
1010+ ///
1111+ /// This error occurs when validating a webhook service URL that doesn't
1212+ /// end with the expected automation service suffix.
1313+ #[error("error-smokesignal-webhook-1 Service must end with {suffix}")]
1414+ InvalidServiceSuffix { suffix: String },
1515+1616+ /// Error when failing to get a DID document.
1717+ ///
1818+ /// This error occurs when attempting to retrieve a DID document
1919+ /// for service validation fails.
2020+ #[error("error-smokesignal-webhook-2 Failed to get DID document for {did}: {error}")]
2121+ DidDocumentRetrievalFailed { did: String, error: String },
2222+2323+ /// Error when a DID document is not found.
2424+ ///
2525+ /// This error occurs when a required DID document cannot be found
2626+ /// in the system.
2727+ #[error("error-smokesignal-webhook-3 DID document not found for {did}")]
2828+ DidDocumentNotFound { did: String },
2929+3030+ /// Error when a service is not found in a DID document.
3131+ ///
3232+ /// This error occurs when a DID document doesn't contain the expected
3333+ /// automation service entry.
3434+ #[error("error-smokesignal-webhook-4 service not found in DID document")]
3535+ ServiceNotFoundInDidDocument,
3636+3737+ /// Error when JWT creation fails.
3838+ ///
3939+ /// This error occurs when attempting to create a JWT token for
4040+ /// webhook authentication fails.
4141+ #[error("error-smokesignal-webhook-5 Failed to create JWT: {error}")]
4242+ JwtCreationFailed { error: String },
4343+4444+ /// Error when a webhook request fails with an error response.
4545+ ///
4646+ /// This error occurs when the webhook endpoint returns an error
4747+ /// status code or the request fails.
4848+ #[error("error-smokesignal-webhook-6 Webhook failed: {error}")]
4949+ WebhookRequestFailed { error: String },
5050+5151+ /// Error when a webhook request fails due to network or other issues.
5252+ ///
5353+ /// This error occurs when the HTTP request to the webhook endpoint
5454+ /// fails due to network issues or other transport problems.
5555+ #[error("error-smokesignal-webhook-7 Webhook request failed: {error}")]
5656+ WebhookTransportFailed { error: String },
5757+}