Skip to content

Conversation

@lcian
Copy link
Member

@lcian lcian commented Jan 12, 2026

Builds on top of #262 to achieve structured errors all the way to the API layer.

  • Changes ApiResult to also be based on thiserror, so that we can match it and return the correct status codes
  • Also removes the AuthError::InitFailure variant, 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 return ApiError as well, to achieve consistency in our response bodies.

lcian and others added 14 commits January 12, 2026 15:54
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.
Copy link
Member Author

@lcian lcian Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

) -> 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)?;
Copy link
Member Author

@lcian lcian Jan 12, 2026

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.

@lcian lcian changed the title wip feat(server): Move to structured ApiError Jan 12, 2026
lcian and others added 3 commits January 12, 2026 18:01
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");
Copy link
Member Author

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

@lcian lcian marked this pull request as ready for review January 12, 2026 17:12
@lcian lcian requested a review from a team as a code owner January 12, 2026 17:12
Comment on lines +25 to +32
/// 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not #[from] ServiceError?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants