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
//! Compile-time per-route database query budgets (#1667).
//!
//! The work of this feature happens in the [`query_budget`] attribute macro,
//! which walks a handler's AST at build time and fails the build when a
//! statically reachable path can exceed the declared query count. What lives
//! here is the small, zero-cost artefact that expansion leaves behind: a
//! [`StaticQueryBudget`] constant recording what the handler declared and what
//! the analysis proved.
//!
//! The constant carries no runtime behaviour — nothing checks it during a
//! request; that job belongs to timeouts, load-shedding, and the runtime
//! query-count assertions in [`crate::test`]. It exists so tooling and tests
//! can read the proof back:
//!
//! ```ignore
//! use autumn_web::{get, query_budget};
//!
//! #[get("/posts")]
//! #[query_budget(2)]
//! async fn index(repo: PgPostRepository) -> AutumnResult<Markup> { /* … */ }
//!
//! // The expansion emits `__AUTUMN_QUERY_BUDGET_index`:
//! assert_eq!(__AUTUMN_QUERY_BUDGET_index.declared, Some(2));
//! assert_eq!(__AUTUMN_QUERY_BUDGET_index.proven_max, Some(2));
//! ```
//!
//! See `docs/guide/query-budgets.md` for the user-facing guide.
//!
//! [`query_budget`]: macro@crate::query_budget
//! [`StaticQueryBudget`]: crate::query_budget::StaticQueryBudget
/// The compile-time query budget proved for one handler.
///
/// Emitted by [`query_budget`](macro@crate::query_budget) as a `const` named
/// `__AUTUMN_QUERY_BUDGET_{handler}`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StaticQueryBudget {
/// The handler function's name.
pub handler: &'static str,
/// The declared ceiling — `None` for `#[query_budget(unbounded, …)]`.
pub declared: Option<u32>,
/// The upper bound the analysis proved, or `None` when a reachable path
/// could not be bounded (only possible under an `unbounded` declaration,
/// since a bounded one fails the build instead).
pub proven_max: Option<u32>,
}
impl StaticQueryBudget {
/// Construct a budget record. Called by the macro expansion.
#[must_use]
pub const fn new(
handler: &'static str,
declared: Option<u32>,
proven_max: Option<u32>,
) -> Self {
Self {
handler,
declared,
proven_max,
}
}
/// Whether the handler opted out of a finite budget.
#[must_use]
pub const fn is_unbounded(&self) -> bool {
self.declared.is_none()
}
/// How much of the declared budget the handler leaves unused, when both a
/// ceiling and a proof exist.
#[must_use]
pub const fn headroom(&self) -> Option<u32> {
match (self.declared, self.proven_max) {
(Some(declared), Some(proven)) => Some(declared.saturating_sub(proven)),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::StaticQueryBudget;
#[test]
fn headroom_is_the_unused_slack() {
let budget = StaticQueryBudget::new("index", Some(5), Some(2));
assert_eq!(budget.headroom(), Some(3));
assert!(!budget.is_unbounded());
}
#[test]
fn a_handler_at_its_ceiling_has_no_headroom() {
let budget = StaticQueryBudget::new("index", Some(2), Some(2));
assert_eq!(budget.headroom(), Some(0));
}
#[test]
fn an_unbounded_handler_reports_no_ceiling_and_no_headroom() {
let budget = StaticQueryBudget::new("backfill", None, None);
assert!(budget.is_unbounded());
assert_eq!(budget.headroom(), None);
}
}