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
130
131
132
133
134
135
136
//! Shared HTTP server scaffolding for trusty-* daemons.
//!
//! Why: Every trusty-* daemon wants the same axum middleware stack (permissive
//! CORS for local browser UIs, a tracing layer, gzip compression) and the same
//! fast-fail reqwest client when one daemon calls another. Centralising removes
//! drift between trusty-search, future trusty-memory daemons, etc.
//!
//! What: pure helpers — no global state.
//! - [`with_standard_middleware`] layers CORS/Trace/Compression on a router.
//! - [`daemon_http_client`] builds a reqwest client with short timeouts so
//! CLI commands never hang on a missing daemon.
//!
//! Test: `cargo test -p trusty-common --features axum-server` covers router
//! composition (smoke) and client construction (timeouts surfaced through
//! the public `reqwest::Client` API — we just assert no error on build).
use ;
use ;
use json;
use Duration;
use ;
/// Apply the standard trusty-* middleware stack to an axum router.
///
/// Why: Local browser-based UIs (trusty-search SPA, future dashboards) need
/// permissive CORS to talk to `127.0.0.1:<port>`; every daemon benefits from
/// request tracing for debugging; gzip is a cheap wire-size win.
/// What: layers `CorsLayer` (any origin/methods/headers), `TraceLayer` (HTTP
/// span), and `CompressionLayer` (gzip) in that order. The order matters:
/// CORS must run on every response (including 404s from inner routes), and
/// compression should be outermost so the trace span captures the encoded
/// size if needed.
///
/// Compression skips `text/event-stream` (SSE) responses: gzip's trailer is
/// only flushed at stream close, so a fast-completing SSE response leaves the
/// client (reqwest) mid-decode and surfaces as
/// `Transport error: error decoding response body`. tower-http 0.5 ships
/// `NotForContentType::SSE` ("text/event-stream") for exactly this case; we
/// compose it with `DefaultPredicate` so all other heuristics (min size, no
/// already-compressed media) still apply.
/// Test: smoke-tested via the dependent crates' integration tests — any
/// regression breaks `cargo test -p trusty-search-service`.
/// Build a `reqwest::Client` configured for daemon-to-daemon calls.
///
/// Why: every CLI command that talks to the daemon must fail fast when the
/// daemon is not running. Without timeouts, reqwest waits for the OS TCP
/// stack (minutes on some platforms), freezing the terminal.
/// What: 2 s connect timeout, 5 s total request timeout. Returns
/// `anyhow::Result` so callers can `?`-propagate alongside other anyhow
/// errors without conversion boilerplate.
/// Test: `daemon_http_client_builds` — construction succeeds with the
/// configured timeouts; the timeout values themselves are exercised in the
/// dependent CLIs (manual: stop daemon, run `trusty-search status`).
/// Standard health-check handler returning `{"status":"ok","version":"<v>"}`.
///
/// Why: trusty-search and trusty-memory both expose `/health`, but their
/// payload shapes drifted (one returned plain `"ok"`, the other JSON with
/// version). Centralising gives every trusty-* daemon the same JSON contract
/// so monitoring tooling (curl probes, MCP supervisors) can rely on a single
/// shape.
/// What: returns a 200 OK with body `{"status":"ok","version":"<version>"}`.
/// The `version` argument is `&'static str` so callers can pass
/// `env!("CARGO_PKG_VERSION")` without allocation.
/// Usage: `.route("/health", get(|| health_handler(env!("CARGO_PKG_VERSION"))))`
/// Test: `health_handler_returns_expected_json` exercises the handler
/// directly and asserts the JSON body.
pub async