1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! 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
//!
//! ```rust
//! 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);
//! # let _: Router = app;
//! # Ok::<(), baukit_http::HttpOptionsError>(())
//! ```
//!
//! [`pagination`] adds keyset pagination whose opaque cursors are bound to the
//! request filters, and [`classify_http_status`] turns an upstream response into
//! a [`RetryClass`] so outbound clients share one retry policy.
//!
//! # Paginated handler
//!
//! ```rust
//! use axum::extract::Query;
//! use baukit_http::{ApiError, Page, PageKey, PageParams, 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)]
//! 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, ¶ms, &filters, |item| {
//! PageKey::new(item.name.clone(), item.id)
//! })?;
//! Ok(ResponseEnvelope::new(
//! page.items,
//! PageMeta { next_cursor: page.next_cursor },
//! ))
//! }
//! # let _ = list;
//! ```
//!
//! 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`].
pub use ;
pub use ApiError;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;
pub use finalize;
/// The metrics facade used by the HTTP lifecycle middleware.
///
/// Applications normally initialize a recorder through `baukit-telemetry`.
pub use metrics;
// Compiles the README's examples so they cannot drift from the API.
;