Skip to main content

Crate baukit_http

Crate baukit_http 

Source
Expand description

Shared Axum middleware, HTTP telemetry, and public errors for Baukit services.

finalize applies one consistent request lifecycle: standard extractor and routing errors, request identity, W3C trace extraction and response propagation, route-template spans, HTTP RED metrics, panic and timeout envelopes, body and concurrency limits, and explicit CORS.

§Example

use axum::{Router, routing::get};
use baukit_http::{HttpOptions, RequestId, finalize};

async fn hello(request_id: RequestId) -> String {
    format!("hello from request {}", request_id.as_str())
}

let options = HttpOptions::default()
    .with_allowed_origins(["https://app.example.com"])?
    .with_additional_allowed_headers(["accept", "x-webhook-secret"])?
    .with_json_rejection_code("invalid_json")?;
let app = finalize(Router::new().route("/hello", get(hello)), options);

classify_http_status turns an upstream response into a RetryClass so outbound clients share one retry policy.

§Paginated handler

Keyset pagination lives in baukit_core::pagination so domain crates can use it without Axum. PaginationError converts into ApiError.

use axum::extract::Query;
use baukit_core::pagination::{Page, PageKey, PageParams};
use baukit_http::{ApiError, ResponseEnvelope};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Deserialize)]
struct ListQuery {
    limit: Option<i64>,
    cursor: Option<String>,
    category: Option<String>,
}

#[derive(Serialize)]
struct Filters {
    category: Option<String>,
}

#[derive(Clone, Serialize)]
struct Item {
    id: Uuid,
    name: String,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct PageMeta {
    next_cursor: Option<String>,
}

async fn list(
    Query(query): Query<ListQuery>,
) -> Result<ResponseEnvelope<Vec<Item>, PageMeta>, ApiError> {
    let params = PageParams::new(query.limit, query.cursor)?;
    let filters = Filters { category: query.category };
    let after = params.decode_cursor(&filters)?;

    // Fetch `params.fetch_limit()?` rows ordered by (name, id), starting
    // after `after.page_key::<String>()?` when it is present.
    let _ = after;
    let rows: Vec<Item> = Vec::new();

    let page = Page::from_rows(rows, &params, &filters, |item| {
        PageKey::new(item.name.clone(), item.id)
    })?;
    Ok(ResponseEnvelope::new(
        page.items,
        PageMeta { next_cursor: page.next_cursor },
    ))
}

This crate records HTTP metrics through the metrics facade and never installs a recorder. The recorder owner must configure http_request_duration_seconds with DURATION_BUCKETS.

Re-exports§

pub use retry::RetryClass;
pub use retry::RetryHeaderOptions;
pub use retry::classify_http_status;
pub use retry::classify_http_status_with_options;
pub use retry::classify_http_status_with_options_at;
pub use retry::classify_transport_error;
pub use retry::retry_after_from_headers;
pub use retry::retry_after_from_headers_at;
pub use retry::retry_after_from_headers_with_options;
pub use retry::retry_after_from_headers_with_options_at;
pub use metrics;

Modules§

retry
Classification of upstream HTTP responses and transport failures.

Structs§

ApiError
A safe public API error rendered in Baukit’s standard JSON envelope.
ApiJson
A JSON body extractor whose rejections use Baukit’s standard error envelope.
ApiPath
Backward-compatible name for Path. Backward-compatible name for Path. Backward-compatible name for Path. A route-path extractor whose rejections use Baukit’s standard error envelope.
ApiQuery
Backward-compatible name for Query. Backward-compatible name for Query. Backward-compatible name for Query. A query-string extractor whose rejections use Baukit’s standard error envelope.
ErrorBody
The error body nested inside ErrorEnvelope.
ErrorEnvelope
The shared public error response envelope.
HttpOptions
Configuration for the shared HTTP layer stack.
IdempotencyKey
A validated Idempotency-Key value.
IdempotencyKeyRule
Length bounds for Idempotency-Key values on one route.
InvalidEtagPrefix
An ETag prefix is longer than MAX_ETAG_PREFIX_BYTES or has a byte outside [A-Za-z0-9._:-].
InvalidIdempotencyKeyRule
Bounds outside 1 <= min_bytes <= max_bytes <= MAX_IDEMPOTENCY_KEY_BYTES.
JsonRejectionCodes
Stable error codes for each JSON extractor rejection class.
Path
A route-path extractor whose rejections use Baukit’s standard error envelope.
Query
A query-string extractor whose rejections use Baukit’s standard error envelope.
RequestId
A request identifier accepted from the client or generated as a UUID.
RequestLocale
A locale selected from the configured supported set for one request.
RequestLocaleConfig
Product-owned locale choices used by RequestLocale.
ResponseEnvelope
A fully typed success-response envelope.
Revision
A non-negative resource revision carried in a strong ETag.
RevisionEtag
Formats and parses strong revision ETags of the form "<prefix><revision>".
RevisionOutOfRange
A value outside 0..=i64::MAX cannot be a Revision.
Rfc3339DateTime
An API timestamp with one JSON and OpenAPI representation: an RFC 3339 string with OpenAPI date-time format.

Enums§

HttpOptionsError
An invalid shared HTTP layer option.
IdempotencyError
A keyed mutation that cannot run or replay.
InvalidIdempotencyKey
Why an Idempotency-Key value is outside the route’s grammar.
InvalidIfMatch
Why an If-Match value is not one strong ETag for the resource.
LocaleQueryOverride
Controls whether a decoded query parameter overrides Accept-Language.
PreconditionError
A failed revision precondition.
RequestLocaleConfigError
Invalid request-locale configuration.
RequestLocaleRejection
Predictable malformed-input failures from RequestLocale.
ResponseCachePolicy
Whether the HTTP layers add a default Cache-Control header to responses.

Constants§

DURATION_BUCKETS
Required Prometheus buckets for HTTP_REQUEST_DURATION_SECONDS, in seconds.
HTTP_REQUESTS_IN_FLIGHT
Gauge metric for active HTTP requests, labeled method and route.
HTTP_REQUESTS_TOTAL
Counter metric for completed HTTP requests, labeled method, route, and status.
HTTP_REQUEST_DURATION_SECONDS
Histogram metric for HTTP request seconds, labeled method, route, and status.
IDEMPOTENCY_KEY
The Idempotency-Key request header.
IDEMPOTENCY_KEY_IN_PROGRESS_CODE
Error code for a key whose first request has not finished.
IDEMPOTENCY_KEY_REQUIRED_CODE
Error code for a route that requires Idempotency-Key and received none.
IDEMPOTENCY_KEY_REUSED_CODE
Error code for a key already used in the same scope with a different request.
INVALID_IDEMPOTENCY_KEY_CODE
Error code for an Idempotency-Key value outside the route’s grammar.
MAX_ACCEPT_LANGUAGE_BYTES
Maximum combined Accept-Language header length inspected by RequestLocale.
MAX_ETAG_PREFIX_BYTES
The longest prefix a RevisionEtag accepts, in bytes.
MAX_IDEMPOTENCY_KEY_BYTES
The longest key any IdempotencyKeyRule may accept, in bytes.
MAX_LOCALE_QUERY_BYTES
Maximum raw query length inspected by RequestLocale.
MAX_SUPPORTED_LOCALES
Maximum number of locales in a RequestLocaleConfig.
X_REQUEST_ID
The standard request ID header.

Functions§

ensure_current_revision
Compares the revision a client sent with the stored one.
extract_trace_context
Extracts W3C trace context from request headers using the global propagator.
finalize
Finalizes a public router with standard 404/405 envelopes and HTTP layers.
inject_current_trace_context
Injects the current tracing span’s OpenTelemetry context into outbound headers.
inject_trace_context
Injects context into outbound headers using the global W3C propagator.
layers
Applies the complete Baukit HTTP defaults stack to an Axum router.