brokkr-broker::api::v1::error
Rust
Canonical error model for the v1 API.
Every fallible v1 handler returns Result<T, ApiError>. ApiError carries
an HTTP status, a stable machine-readable code, a human-readable message,
and an optional structured details map. It serializes to the
ErrorResponse wire format, which is documented in the OpenAPI spec and
consumed by generated SDK clients.
The code strings are part of the SDK contract — callers pattern-match on
them — so they should be chosen conservatively and treated as breaking when
renamed.
Structs
Section titled “Structs”struct ErrorResponse
Section titled “struct ErrorResponse”pub
Derives: Debug, Clone, Serialize, ToSchema
Wire format for every 4xx/5xx response body in the v1 API.
Fields
Section titled “Fields”| Name | Type | Description |
|---|---|---|
code |
String |
Stable, machine-readable error code (e.g. agent_not_found). |
message |
String |
Human-readable message; not stable and may change between versions. |
details |
Option<BTreeMap<String, Value>> |
Optional structured context. Keys and value shapes are documented per error code when present. |
struct ApiError
Section titled “struct ApiError”pub
Derives: Debug, Clone
Errors returned by v1 handlers. Maps 1:1 to an HTTP status and an ErrorResponse body via IntoResponse.
Fields
Section titled “Fields”| Name | Type | Description |
|---|---|---|
status |
StatusCode |
|
code |
String |
|
message |
String |
|
details |
Option<BTreeMap<String, Value>> |
Methods
Section titled “Methods”with_details
Section titled “with_details”pub
fn with_details(mut self, details: BTreeMap<String, Value>) -> SelfAttach structured context to an error. Useful for surfacing the ID that wasn’t found, the field that failed validation, etc.
Source
pub fn with_details(mut self, details: BTreeMap<String, Value>) -> Self { self.details = Some(details); self }bad_request
Section titled “bad_request”pub
fn bad_request( code: impl Into<String>, message: impl Into<String>,) -> SelfSource
pub fn bad_request(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::BAD_REQUEST, code, message) }unauthorized
Section titled “unauthorized”pub
fn unauthorized( code: impl Into<String>, message: impl Into<String>,) -> SelfSource
pub fn unauthorized(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::UNAUTHORIZED, code, message) }forbidden
Section titled “forbidden”pub
fn forbidden( code: impl Into<String>, message: impl Into<String>,) -> SelfSource
pub fn forbidden(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::FORBIDDEN, code, message) }not_found
Section titled “not_found”pub
fn not_found( code: impl Into<String>, message: impl Into<String>,) -> SelfSource
pub fn not_found(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::NOT_FOUND, code, message) }conflict
Section titled “conflict”pub
fn conflict(code: impl Into<String>, message: impl Into<String>) -> SelfSource
pub fn conflict(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::CONFLICT, code, message) }unprocessable
Section titled “unprocessable”pub
fn unprocessable( code: impl Into<String>, message: impl Into<String>,) -> SelfSource
pub fn unprocessable(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::UNPROCESSABLE_ENTITY, code, message) }internal
Section titled “internal”pub
fn internal(message: impl Into<String>) -> SelfSource
pub fn internal(message: impl Into<String>) -> Self { Self::new(StatusCode::INTERNAL_SERVER_ERROR, "internal_error", message) }service_unavailable
Section titled “service_unavailable”pub
fn service_unavailable( code: impl Into<String>, message: impl Into<String>,) -> SelfSource
pub fn service_unavailable(code: impl Into<String>, message: impl Into<String>) -> Self { Self::new(StatusCode::SERVICE_UNAVAILABLE, code, message) }from_diesel
Section titled “from_diesel”pub
fn from_diesel( err: diesel::result::Error, internal_message: impl Into<String>,) -> SelfSource
pub fn from_diesel(err: diesel::result::Error, internal_message: impl Into<String>) -> Self { use diesel::result::{DatabaseErrorKind, Error as DieselErr}; let internal_message = internal_message.into(); match err { DieselErr::DatabaseError(DatabaseErrorKind::UniqueViolation, info) => { let constraint = info.constraint_name().unwrap_or("unique constraint"); let mut details = std::collections::BTreeMap::new(); details.insert( "constraint".into(), serde_json::Value::String(constraint.to_string()), ); ApiError::conflict( "unique_violation", format!("violates unique constraint `{constraint}`"), ) .with_details(details) } DieselErr::DatabaseError(DatabaseErrorKind::ForeignKeyViolation, info) => { let constraint = info.constraint_name().unwrap_or("foreign-key constraint"); let mut details = std::collections::BTreeMap::new(); details.insert( "constraint".into(), serde_json::Value::String(constraint.to_string()), ); ApiError::unprocessable( "foreign_key_violation", format!("violates foreign-key constraint `{constraint}`"), ) .with_details(details) } DieselErr::DatabaseError(DatabaseErrorKind::CheckViolation, info) => { let constraint = info.constraint_name().unwrap_or("check constraint"); ApiError::unprocessable( "check_violation", format!("violates check constraint `{constraint}`"), ) } DieselErr::DatabaseError(DatabaseErrorKind::NotNullViolation, info) => { let column = info.column_name().unwrap_or("required column"); ApiError::unprocessable( "not_null_violation", format!("required column `{column}` is null"), ) } DieselErr::NotFound => ApiError::not_found("not_found", internal_message), other => { tracing::error!("diesel error: {:?}", other); ApiError::internal(internal_message) } } }