Skip to main content

Problem

Struct Problem 

Source
pub struct Problem {
    pub type_uri: Option<String>,
    pub title: String,
    pub status: u16,
    pub detail: Option<String>,
    pub instance: Option<String>,
    pub extensions: Map<String, Value>,
    pub retry_after: Option<Duration>,
}
Expand description

An RFC 9457 problem details response body.

Serializes as:

{ "title": "Not Found", "status": 404 }
{ "type": "https://example.com/probs/out-of-credit", "title": "Insufficient credit",
  "status": 403, "detail": "Balance is 30, item costs 50",
  "instance": "/account/12345/msgs/abc", "balance": 30 }

Implements IntoResponse with Content-Type: application/problem+json and an optional delay-seconds Retry-After header.

§Example

use axum::{http::StatusCode, response::IntoResponse};
use axum_api_kit::Problem;

async fn handler() -> impl IntoResponse {
    Problem::new(StatusCode::FORBIDDEN, "Insufficient credit")
        .with_type("https://example.com/probs/out-of-credit")
        .with_detail("Balance is 30, item costs 50")
        .with_instance("/account/12345/msgs/abc")
        .with_extension("balance", 30)
}

Fields§

§type_uri: Option<String>

A URI reference identifying the problem type. Absent means “about:blank” per RFC 9457.

§title: String

A short, human-readable summary of the problem type. Stable per problem type.

§status: u16

The HTTP status code, duplicated in the body per RFC 9457.

§detail: Option<String>

A human-readable explanation specific to this occurrence.

§instance: Option<String>

A URI reference identifying this specific occurrence.

§extensions: Map<String, Value>

RFC 9457 extension members, flattened to top-level JSON keys.

§retry_after: Option<Duration>

Optional Retry-After header delay. Header-only; never serialized in the body.

Implementations§

Source§

impl Problem

Source

pub fn new(status: StatusCode, title: impl Into<String>) -> Self

Builds a minimal Problem with the given status and title.

type, detail, and instance start absent, extensions start empty, and no Retry-After header is emitted.

§Example
use axum::http::StatusCode;
use axum_api_kit::Problem;

let problem = Problem::new(StatusCode::NOT_FOUND, "Not Found");
assert_eq!(
    serde_json::to_value(&problem).unwrap(),
    serde_json::json!({ "title": "Not Found", "status": 404 })
);
Source

pub fn with_type(self, type_uri: impl Into<String>) -> Self

Sets the type member: a URI reference identifying the problem type.

§Example
use axum::http::StatusCode;
use axum_api_kit::Problem;

let problem = Problem::new(StatusCode::FORBIDDEN, "Insufficient credit")
    .with_type("https://example.com/probs/out-of-credit");
let v = serde_json::to_value(&problem).unwrap();
assert_eq!(v["type"], "https://example.com/probs/out-of-credit");
Source

pub fn with_detail(self, detail: impl Into<String>) -> Self

Sets the detail member: a human-readable explanation specific to this occurrence.

§Example
use axum::http::StatusCode;
use axum_api_kit::Problem;

let problem = Problem::new(StatusCode::FORBIDDEN, "Insufficient credit")
    .with_detail("Balance is 30, item costs 50");
let v = serde_json::to_value(&problem).unwrap();
assert_eq!(v["detail"], "Balance is 30, item costs 50");
Source

pub fn with_instance(self, instance: impl Into<String>) -> Self

Sets the instance member: a URI reference identifying this specific occurrence.

§Example
use axum::http::StatusCode;
use axum_api_kit::Problem;

let problem = Problem::new(StatusCode::FORBIDDEN, "Insufficient credit")
    .with_instance("/account/12345/msgs/abc");
let v = serde_json::to_value(&problem).unwrap();
assert_eq!(v["instance"], "/account/12345/msgs/abc");
Source

pub fn with_extension( self, key: impl Into<String>, value: impl Into<Value>, ) -> Self

Adds an RFC 9457 extension member, serialized as a top-level JSON key.

If key is one of the reserved members ("type", "title", "status", "detail", "instance"), the call is a silent no-op so the flattened extensions can never emit duplicate JSON keys. This mirrors the ApiError::with_source silent-no-op precedent.

§Example
use axum::http::StatusCode;
use axum_api_kit::Problem;

let problem = Problem::new(StatusCode::FORBIDDEN, "Insufficient credit")
    .with_extension("balance", 30)
    .with_extension("status", 999); // reserved key: ignored
let v = serde_json::to_value(&problem).unwrap();
assert_eq!(v["balance"], 30);
assert_eq!(v["status"], 403);
Source

pub fn with_retry_after(self, delay: Duration) -> Self

Emits a delay-seconds Retry-After header on the response, rounded up to whole seconds (1500ms becomes "2").

The delay never appears in the JSON body; add it via with_extension if body presence is wanted.

§Example
use axum::{http::StatusCode, response::IntoResponse};
use axum_api_kit::Problem;
use std::time::Duration;

let res = Problem::new(StatusCode::TOO_MANY_REQUESTS, "Too Many Requests")
    .with_retry_after(Duration::from_secs(30))
    .into_response();
assert_eq!(res.headers().get("retry-after").unwrap(), "30");
Source

pub fn status_code(&self) -> StatusCode

Returns the status field as a StatusCode, falling back to 500 Internal Server Error when it is not a valid status.

StatusCode::from_u16 accepts 100..=999, so the fallback only triggers for a hand-set pub status outside that range.

§Example
use axum::http::StatusCode;
use axum_api_kit::Problem;

let problem = Problem::new(StatusCode::NOT_FOUND, "Not Found");
assert_eq!(problem.status_code(), StatusCode::NOT_FOUND);
Source

pub fn into_response_for(self, headers: &HeaderMap) -> Response

Converts into a response, choosing the Content-Type by negotiating against the request’s Accept headers.

Shorthand for self.into_response_with(ProblemFormat::negotiate(headers)); see ProblemFormat::negotiate for the exact rules. Plain application/json is served only when the client strictly prefers it; every ambiguous case (no Accept header, */*, ties, unparseable values) serves application/problem+json. The body bytes and any Retry-After header are identical either way, and the plain IntoResponse behavior is untouched.

§Example
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
use axum_api_kit::Problem;

let mut headers = HeaderMap::new();
headers.insert(header::ACCEPT, HeaderValue::from_static("application/json"));

let res = Problem::new(StatusCode::NOT_FOUND, "Not Found").into_response_for(&headers);
assert_eq!(res.headers().get(header::CONTENT_TYPE).unwrap(), "application/json");

let res = Problem::new(StatusCode::NOT_FOUND, "Not Found").into_response_for(&HeaderMap::new());
assert_eq!(
    res.headers().get(header::CONTENT_TYPE).unwrap(),
    "application/problem+json"
);
Source

pub fn into_response_with(self, format: ProblemFormat) -> Response

Converts into a response served as the given ProblemFormat.

Pairs with the ProblemFormat extractor, which negotiates the format from the request’s Accept headers before the handler runs. The JSON body bytes and any Retry-After header are identical for both formats; only the Content-Type header differs.

§Example
use axum::http::{header, StatusCode};
use axum_api_kit::{Problem, ProblemFormat};

let res = Problem::new(StatusCode::NOT_FOUND, "Not Found")
    .into_response_with(ProblemFormat::Json);
assert_eq!(res.headers().get(header::CONTENT_TYPE).unwrap(), "application/json");

Trait Implementations§

Source§

impl Clone for Problem

Source§

fn clone(&self) -> Problem

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl ComposeSchema for Problem

Source§

impl Debug for Problem

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl From<(StatusCode, ApiError)> for Problem

Convert a (StatusCode, ApiError) pair into a Problem, losslessly.

sourceProblem member
statusstatus
status canonical reasontitle (falls back to code for nonstandard statuses; cosmetic)
messagedetail
code"code" extension member
details (when present)"details" extension member, verbatim

details is kept under the single "details" key, never flattened, so validation field maps cannot collide with reserved members. type and instance are left absent, and no Retry-After header is set.

Source§

fn from((status, err): (StatusCode, ApiError)) -> Self

Converts to this type from the input type.
Source§

impl From<(StatusCode, Json<ApiError>)> for Problem

Convert a (StatusCode, Json<ApiError>) factory tuple into a Problem.

Unwraps the Json and delegates to From<(StatusCode, ApiError)>, so existing factory results convert directly: Problem::from(ApiError::not_found("nope")).

Source§

fn from((status, Json): (StatusCode, Json<ApiError>)) -> Self

Converts to this type from the input type.
Source§

impl IntoResponse for Problem

Source§

fn into_response(self) -> Response

Create a response.
Source§

impl Serialize for Problem

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl ToSchema for Problem

Source§

fn name() -> Cow<'static, str>

Return name of the schema. Read more
Source§

fn schemas(schemas: &mut Vec<(String, RefOr<Schema>)>)

Implement reference utoipa::openapi::schema::Schemas for this type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T, S> Handler<IntoResponseHandler, S> for T
where T: IntoResponse + Clone + Send + Sync + 'static,

Source§

type Future = Ready<Response<Body>>

The type of future calling this handler returns.
Source§

fn call( self, _req: Request<Body>, _state: S, ) -> <T as Handler<IntoResponseHandler, S>>::Future

Call the handler with the given request.
Source§

fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>
where L: Layer<HandlerService<Self, T, S>> + Clone, <L as Layer<HandlerService<Self, T, S>>>::Service: Service<Request<Body>>,

Apply a tower::Layer to the handler. Read more
Source§

fn with_state(self, state: S) -> HandlerService<Self, T, S>

Convert the handler into a Service by providing the state
Source§

impl<H, T> HandlerWithoutStateExt<T> for H
where H: Handler<T, ()>,

Source§

fn into_service(self) -> HandlerService<H, T, ()>

Convert the handler into a Service and no state.
Source§

fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, ()>>

Convert the handler into a MakeService and no state. Read more
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> PartialSchema for T
where T: ComposeSchema + ?Sized,

Source§

fn schema() -> RefOr<Schema>

Return ref or schema of implementing type that can then be used to construct combined schemas.
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more