Skip to main content

defect_http/
user_agent.rs

1//! A layer that injects a `User-Agent` header.
2//!
3//! Before each `inner.call(req)`, it writes `User-Agent` into `req.headers_mut()`,
4//! skipping if the provider has already explicitly set it (i.e. `entry.or_insert(...)`
5//! semantics). The fixed value is computed at construction time; subsequent clones
6//! reuse the [`HeaderValue`]'s internal `Arc` sharing.
7//!
8//! The default value is given by [`default_user_agent`]:
9//! `defect-http/{version} ({git_sha})`.
10
11use std::task::{Context, Poll};
12
13use http::HeaderValue;
14use http::header::USER_AGENT;
15use tower::{Layer, Service};
16
17/// The default `User-Agent` value is `defect-http/{CARGO_PKG_VERSION}
18/// ({DEFECT_HTTP_GIT_SHA})`.
19///
20/// `DEFECT_HTTP_GIT_SHA` is injected by `build.rs`: it first reads the build-time
21/// environment variable `DEFECT_HTTP_BUILD_SHA` (for downstream packaging scenarios
22/// without a `.git` directory), then falls back to running `git rev-parse`, and finally
23/// degrades to `"unknown"` if neither is available.
24pub fn default_user_agent() -> HeaderValue {
25    let pkg = env!("CARGO_PKG_VERSION");
26    let sha = env!("DEFECT_HTTP_GIT_SHA");
27    let raw = format!("defect-http/{pkg} ({sha})");
28    HeaderValue::from_str(&raw).unwrap_or_else(|_| HeaderValue::from_static("defect-http"))
29}
30
31#[derive(Debug, Clone)]
32pub(crate) struct UserAgentLayer {
33    value: HeaderValue,
34}
35
36impl UserAgentLayer {
37    pub(crate) fn new(value: HeaderValue) -> Self {
38        Self { value }
39    }
40}
41
42impl<S> Layer<S> for UserAgentLayer {
43    type Service = UserAgent<S>;
44
45    fn layer(&self, inner: S) -> Self::Service {
46        UserAgent {
47            inner,
48            value: self.value.clone(),
49        }
50    }
51}
52
53#[derive(Debug, Clone)]
54pub(crate) struct UserAgent<S> {
55    inner: S,
56    value: HeaderValue,
57}
58
59impl<S, B> Service<http::Request<B>> for UserAgent<S>
60where
61    S: Service<http::Request<B>>,
62{
63    type Response = S::Response;
64    type Error = S::Error;
65    type Future = S::Future;
66
67    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
68        self.inner.poll_ready(cx)
69    }
70
71    fn call(&mut self, mut req: http::Request<B>) -> Self::Future {
72        req.headers_mut()
73            .entry(USER_AGENT)
74            .or_insert_with(|| self.value.clone());
75        self.inner.call(req)
76    }
77}
78
79#[cfg(test)]
80mod tests;