-
-
Notifications
You must be signed in to change notification settings - Fork 4
feat(server): Move to structured ApiError #263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: lcian/feat/thiserror
Are you sure you want to change the base?
Conversation
Replaces anyhow::Error with structured error types to enable proper HTTP status codes and clearer error boundaries between the service and API layers. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enhances ApiError to return structured JSON responses containing the error message and full cause chain, improving debuggability for API consumers. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Consolidates ApiError and ApiErrorResponse into endpoints/common.rs where they logically belong as endpoint utilities. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes the FromRequestParts implementation to return ApiError instead of bare StatusCode, ensuring auth failures are logged with full error details and cause chains, and return structured JSON responses. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes: - Rename BadRequest → Client for malformed request errors - Rename Service → Server for internal server errors - Change Server variant to contain message and boxed error source - Implement From<ServiceError> manually (no #[from]) to allow future flexibility - Add logging for Server errors in IntoResponse - Update all usage sites to use new variant names Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
| @@ -1,42 +1,90 @@ | |||
| //! | |||
| //! This is mostly adapted from <https://github.com/tokio-rs/axum/blob/main/examples/anyhow-error-response/src/main.rs> | |||
| //! Common types and utilities for API endpoints. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now mostly adapted from Relay's ApiErrorResponse: https://github.com/getsentry/relay/blob/88f4c53e98e407025168f01276e09b2681373c98/relay-server/src/utils/api.rs#L24
| ) -> ApiResult<Response> { | ||
| let mut metadata = | ||
| Metadata::from_headers(&headers, "").context("extracting metadata from headers")?; | ||
| let mut metadata = Metadata::from_headers(&headers, "").map_err(ServiceError::from)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here it's kinda weird that we're creating a ServiceError from the server.
However ServiceError already conveniently has a variant that can be obtained directly from a objectstore_types::Error as we need to deal with these metadata conversions there too, so I thought we might as well use that one, instead of creating a brand new ApiError.
Properly distinguishes between authentication and authorization failures: - 401 UNAUTHORIZED: Authentication failed (ValidationFailure, VerificationFailure) - 403 FORBIDDEN: Authorization failed (NotPermitted - valid token but insufficient permissions) - 400 BAD_REQUEST: Malformed auth request (BadRequest) - 500 INTERNAL_SERVER_ERROR: Auth system errors (InternalError) Fixes Python test that expects 403 for insufficient permissions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
| } | ||
|
|
||
| ApiError::Server(_) => { | ||
| tracing::error!(error = &self as &dyn Error, "error handling request"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This whole refactor will end up creating new issue groups in Sentry
| /// Server errors, indicating that something went wrong when receiving or executing a request. | ||
| #[error("server error: {0}")] | ||
| Server(#[source] Box<dyn Error + Send + Sync>), | ||
| } | ||
|
|
||
| impl From<ServiceError> for ApiError { | ||
| fn from(err: ServiceError) -> Self { | ||
| ApiError::Server(Box::new(err)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not #[from] ServiceError?
Builds on top of #262 to achieve structured errors all the way to the API layer.
matchit and return the correct status codesAuthError::InitFailurevariant, as this can only be raised when the service starts up. For these kind of errors that would prevent the service from initializing, and are not returned to end users, it's fine to use Anyhows IMO.Some parts of the API still return either a status code or a pair of status code and description, and then convert it with
into_response. I wonder if we should change all of those occurrences to always returnApiErroras well, to achieve consistency in our response bodies.