Skip to content

Response & Error Handling

Response types

Handlers can return various types, the framework automatically converts them to HTTP responses.

Direct data return

rust
#[get("/")]
async fn index(&self) -> String {
    "Hello".to_string()
}

#[get("/health")]
async fn health(&self) -> StatusCode {
    StatusCode::OK
}

Json

Return JSON responses:

rust
#[get("/users")]
async fn list(&self) -> Json<Vec<User>> {
    Json(vec![/* ... */])
}

Result / WebrResult

Supports ? error propagation:

rust
use webr::WebrResult;

#[get("/users/{id}")]
async fn get_user(&self, Path(id): Path<i64>) -> WebrResult<Json<User>> {
    let user = self.service.find(id).await
        .ok_or_else(|| Error::Http {
            status: StatusCode::NOT_FOUND,
            message: format!("User {id} not found"),
        })?;
    Ok(Json(user))
}

Use WebrResult<T> as an alias for Result<T, Error>.

Error handling

Returning Error directly

Use Error::Http to construct errors with status code and message:

rust
use webr::Error;

#[get("/items/{id}")]
async fn get_item(&self, Path(id): Path<i64>) -> Result<Json<Item>, Error> {
    if id > 0 {
        Ok(Json(Item { id, name: "Item".into() }))
    } else {
        Err(Error::Http {
            status: StatusCode::NOT_FOUND,
            message: format!("Item {id} not found"),
        })
    }
}

Error types

rust
pub enum Error {
    Http { status: StatusCode, message: String },  // HTTP business error
    Database(Box<dyn Error + Send + Sync>),          // Database error
    Cache(Box<dyn Error + Send + Sync>),             // Cache error
    Internal(String),                                // Internal error
}

Errors automatically convert to JSON responses: {"code": 404, "message": "Item 42 not found"}.

#[derive(HttpError)]

Declarative error definition, automatically maps to HTTP responses:

rust
#[derive(Debug, webr::HttpError)]
pub enum UserError {
    #[error(status = 404, message = "User not found")]
    NotFound(i64),

    #[error(status = 409, message = "Email already exists")]
    DuplicateEmail(String),
}

// Usage in handler
async fn get_user(&self, Path(id): Path<i64>) -> Result<Json<User>, UserError> {
    let user = self.service.find(id).await
        .ok_or(UserError::NotFound(id))?;
    Ok(Json(user))
}

Features generated by HttpError derive:

  • Automatically implements IntoResponse, can be used directly as return value
  • Automatically implements From<Self> for Error, supports ? upward conversion to Error

Unified response wrapping

Enable unified_response to automatically wrap 2xx JSON responses into a standard format:

rust
app.unified_response();

Original response:

json
{"id": 1, "name": "Alice"}

Wrapped response:

json
{"code": 200, "message": "success", "data": {"id": 1, "name": "Alice"}}

Rules:

  • 2xx + JSON response → wrapped
  • Non-2xx responses → passed through as-is
  • Non-JSON responses (String, StatusCode, etc.) → passed through as-is