pub struct ApiError {
pub code: ErrorCode,
pub title: String,
pub status: u16,
pub detail: String,
pub request_id: Option<Uuid>,
pub errors: Vec<ValidationError>,
pub rate_limit: Option<RateLimitInfo>,
pub source: Option<Arc<dyn Error + Send + Sync + 'static>>,
pub causes: Vec<Self>,
pub extensions: BTreeMap<String, Value>,
}Expand description
RFC 9457 Problem Details error response.
Wire format field mapping:
code→"type"— URN per RFC 9457 §3.1.1 (e.g.urn:api-bones:error:resource-not-found)title→"title"— RFC 9457 §3.1.2status→"status"— HTTP status code, RFC 9457 §3.1.3 (valid range: 100–599)detail→"detail"— RFC 9457 §3.1.4request_id→"instance"— URI per RFC 9457 §3.1.5, asurn:uuid:<uuid>per RFC 4122 §3errors→"errors"— documented extension for field-level validation errors
Content-Type must be set to application/problem+json by the HTTP layer.
Requires std or alloc (fields contain String/Vec).
§Examples
use api_bones::error::{ApiError, ErrorCode};
let err = ApiError::new(ErrorCode::ResourceNotFound, "User 42 not found");
assert_eq!(err.status, 404);
assert_eq!(err.detail, "User 42 not found");Fields§
§code: ErrorCodeMachine-readable error URN (RFC 9457 §3.1.1 type).
title: StringHuman-friendly error label (RFC 9457 §3.1.2 title).
status: u16HTTP status code (RFC 9457 §3.1.3 status). Valid range: 100–599.
detail: StringHuman-readable error specifics (RFC 9457 §3.1.4 detail).
request_id: Option<Uuid>URI identifying this specific occurrence (RFC 9457 §3.1.5 instance).
Serialized as urn:uuid:<uuid> per RFC 4122 §3.
errors: Vec<ValidationError>Structured field-level validation errors (extension). Omitted when empty.
rate_limit: Option<RateLimitInfo>Structured rate-limit metadata (extension). Present on 429 responses
when built via ApiError::rate_limited_with or
ApiError::with_rate_limit. Serialized as the top-level
rate_limit member.
source: Option<Arc<dyn Error + Send + Sync + 'static>>Upstream error that caused this ApiError, if any.
Not serialized — for in-process error chaining only. Exposed via
core::error::Error::source so that anyhow, eyre, and tracing
can walk the full error chain.
Requires std or alloc (uses Arc).
causes: Vec<Self>Nested cause chain serialized as RFC 9457 extension member "causes".
Each entry is a nested Problem Details object. Omitted when empty.
Preserved through From conversions.
extensions: BTreeMap<String, Value>Arbitrary RFC 9457 extension members attached by the caller.
Serialized inline at the top level of the JSON object (flattened). Keys must not collide with the standard Problem Details fields.
Use ApiError::with_extension to attach values.
Implementations§
Source§impl ApiError
impl ApiError
Sourcepub fn new(code: ErrorCode, detail: impl Into<String>) -> Self
pub fn new(code: ErrorCode, detail: impl Into<String>) -> Self
Create a new ApiError. title and status are derived from code.
§Examples
use api_bones::error::{ApiError, ErrorCode};
let err = ApiError::new(ErrorCode::BadRequest, "missing field");
assert_eq!(err.status, 400);
assert_eq!(err.title, "Bad Request");
assert_eq!(err.detail, "missing field");Sourcepub fn with_request_id(self, id: Uuid) -> Self
pub fn with_request_id(self, id: Uuid) -> Self
Attach a request ID (typically set by tracing middleware).
Serializes as "instance": "urn:uuid:<id>" per RFC 9457 §3.1.5 + RFC 4122 §3.
§Examples
use api_bones::error::{ApiError, ErrorCode};
use uuid::Uuid;
let err = ApiError::new(ErrorCode::BadRequest, "oops")
.with_request_id(Uuid::nil());
assert_eq!(err.request_id, Some(Uuid::nil()));Sourcepub fn with_errors(self, errors: Vec<ValidationError>) -> Self
pub fn with_errors(self, errors: Vec<ValidationError>) -> Self
Attach structured field-level validation errors.
§Examples
use api_bones::error::{ApiError, ErrorCode, ValidationError};
let err = ApiError::new(ErrorCode::ValidationFailed, "invalid input")
.with_errors(vec![
ValidationError { field: "/email".into(), message: "invalid".into(), rule: None },
]);
assert_eq!(err.errors.len(), 1);Sourcepub fn with_source(self, source: impl Error + Send + Sync + 'static) -> Self
pub fn with_source(self, source: impl Error + Send + Sync + 'static) -> Self
Attach an upstream error as the source() for this ApiError.
The source is exposed via core::error::Error::source for error-chain
tools (anyhow, eyre, tracing) but is not serialized to the wire.
Requires std or alloc (uses Arc).
Sourcepub fn with_causes(self, causes: Vec<Self>) -> Self
pub fn with_causes(self, causes: Vec<Self>) -> Self
Attach a chain of nested cause errors, serialized as "causes" in
Problem Details output.
§Examples
use api_bones::error::{ApiError, ErrorCode};
let cause = ApiError::not_found("upstream resource missing");
let err = ApiError::internal("pipeline failed")
.with_causes(vec![cause]);
assert_eq!(err.causes.len(), 1);Sourcepub fn with_extension(
self,
key: impl Into<String>,
value: impl Into<Value>,
) -> Self
pub fn with_extension( self, key: impl Into<String>, value: impl Into<Value>, ) -> Self
Attach a single arbitrary RFC 9457 extension member.
The value is serialized inline (flattened) in the Problem Details
object. Requires the serde feature.
§Examples
use api_bones::error::ApiError;
let err = ApiError::internal("boom")
.with_extension("trace_id", "abc-123");
assert_eq!(err.extensions["trace_id"], "abc-123");Sourcepub fn status_code(&self) -> u16
pub fn status_code(&self) -> u16
HTTP status code.
Sourcepub fn is_client_error(&self) -> bool
pub fn is_client_error(&self) -> bool
Whether this is a client error (4xx).
§Examples
use api_bones::error::{ApiError, ErrorCode};
assert!(ApiError::not_found("gone").is_client_error());
assert!(!ApiError::internal("boom").is_client_error());Sourcepub fn is_server_error(&self) -> bool
pub fn is_server_error(&self) -> bool
Whether this is a server error (5xx).
§Examples
use api_bones::error::{ApiError, ErrorCode};
assert!(ApiError::internal("boom").is_server_error());
assert!(!ApiError::not_found("gone").is_server_error());Sourcepub fn bad_request(msg: impl Into<String>) -> Self
pub fn bad_request(msg: impl Into<String>) -> Self
400 Bad Request.
§Examples
use api_bones::error::ApiError;
let err = ApiError::bad_request("missing param");
assert_eq!(err.status, 400);
assert_eq!(err.title, "Bad Request");Sourcepub fn validation_failed(msg: impl Into<String>) -> Self
pub fn validation_failed(msg: impl Into<String>) -> Self
400 Validation Failed.
401 Unauthorized.
Sourcepub fn invalid_credentials() -> Self
pub fn invalid_credentials() -> Self
401 Invalid Credentials.
Sourcepub fn token_expired() -> Self
pub fn token_expired() -> Self
401 Token Expired.
Sourcepub fn insufficient_permissions(msg: impl Into<String>) -> Self
pub fn insufficient_permissions(msg: impl Into<String>) -> Self
403 Insufficient Permissions.
Sourcepub fn not_found(msg: impl Into<String>) -> Self
pub fn not_found(msg: impl Into<String>) -> Self
404 Resource Not Found.
§Examples
use api_bones::error::ApiError;
let err = ApiError::not_found("user 42 not found");
assert_eq!(err.status, 404);
assert_eq!(err.title, "Resource Not Found");Sourcepub fn already_exists(msg: impl Into<String>) -> Self
pub fn already_exists(msg: impl Into<String>) -> Self
409 Resource Already Exists.
Sourcepub fn unprocessable(msg: impl Into<String>) -> Self
pub fn unprocessable(msg: impl Into<String>) -> Self
422 Unprocessable Entity.
Sourcepub fn rate_limited(retry_after_seconds: u64) -> Self
pub fn rate_limited(retry_after_seconds: u64) -> Self
429 Rate Limited.
Sourcepub fn with_rate_limit(self, info: RateLimitInfo) -> Self
pub fn with_rate_limit(self, info: RateLimitInfo) -> Self
Attach structured RateLimitInfo
metadata. Serialized as the top-level rate_limit member and
propagated to ProblemJson as an extension of the same name.
§Examples
use api_bones::error::ApiError;
use api_bones::ratelimit::RateLimitInfo;
let info = RateLimitInfo::new(100, 0, 1_700_000_000).retry_after(60);
let err = ApiError::rate_limited(60).with_rate_limit(info.clone());
assert_eq!(err.rate_limit.as_ref(), Some(&info));Sourcepub fn rate_limited_with(info: RateLimitInfo) -> Self
pub fn rate_limited_with(info: RateLimitInfo) -> Self
429 Rate Limited with structured quota metadata.
Convenience over ApiError::rate_limited +
ApiError::with_rate_limit. The detail string is derived from
info.retry_after when set.
§Examples
use api_bones::error::ApiError;
use api_bones::ratelimit::RateLimitInfo;
let info = RateLimitInfo::new(100, 0, 1_700_000_000).retry_after(30);
let err = ApiError::rate_limited_with(info);
assert_eq!(err.status, 429);
assert!(err.rate_limit.is_some());Sourcepub fn internal(msg: impl Into<String>) -> Self
pub fn internal(msg: impl Into<String>) -> Self
500 Internal Server Error. Never expose internal details in msg.
503 Service Unavailable.
Sourcepub fn builder() -> ApiErrorBuilder<(), ()>
pub fn builder() -> ApiErrorBuilder<(), ()>
Return a typed builder for constructing an ApiError.
Required fields (code and detail) must be set before calling
ApiErrorBuilder::build; the compiler enforces this via typestate.
§Example
use api_bones::error::{ApiError, ErrorCode};
let err = ApiError::builder()
.code(ErrorCode::ResourceNotFound)
.detail("Booking 123 not found")
.build();
assert_eq!(err.status, 404);Trait Implementations§
Source§impl<'de> Deserialize<'de> for ApiError
impl<'de> Deserialize<'de> for ApiError
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Error for ApiError
Available on crate features std or alloc only.
impl Error for ApiError
std or alloc only.Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()
Source§impl From<ApiError> for ProblemJson
Available on crate features std and serde only.
impl From<ApiError> for ProblemJson
std and serde only.Source§fn from(err: ApiError) -> Self
fn from(err: ApiError) -> Self
Convert an ApiError into a ProblemJson.
code→typeviaErrorCode::urnrequest_id(UUID) →instanceas"urn:uuid:<id>"errors(validation) →"errors"extension membersourceis dropped (not part of the wire format)
§Examples
use api_bones::error::{ApiError, ErrorCode, ProblemJson};
let err = ApiError::new(ErrorCode::Forbidden, "not allowed");
let p = ProblemJson::from(err);
assert_eq!(p.status, 403);
assert_eq!(p.title, "Forbidden");Source§impl<E: HttpError> From<E> for ApiError
Available on crate features std or alloc only.Blanket conversion: any HttpError implementor becomes an ApiError.
impl<E: HttpError> From<E> for ApiError
std or alloc only.Blanket conversion: any HttpError implementor becomes an ApiError.
This is a blanket impl over a sealed trait parameter so it does not
conflict with other From impls on ApiError.