Skip to main content

cratestack_axum/headers/
etag.rs

1use axum::http::{HeaderMap, HeaderValue, header};
2use axum::response::Response;
3use cratestack_core::CoolError;
4
5/// Parse an `If-Match` header carrying a strong ETag of the form `"<int>"`.
6/// Returns `None` if the header is absent. Returns an error if the header
7/// is present but malformed (weak validators, non-integer payloads, etc.).
8pub fn parse_if_match_version(headers: &HeaderMap) -> Result<Option<i64>, CoolError> {
9    let Some(value) = headers.get(header::IF_MATCH) else {
10        return Ok(None);
11    };
12    let raw = value
13        .to_str()
14        .map_err(|_| CoolError::BadRequest("If-Match header must be ASCII".to_owned()))?
15        .trim();
16    if raw == "*" {
17        return Err(CoolError::BadRequest(
18            "If-Match: * is not supported on versioned models".to_owned(),
19        ));
20    }
21    let stripped = raw
22        .strip_prefix('"')
23        .and_then(|s| s.strip_suffix('"'))
24        .ok_or_else(|| {
25            CoolError::BadRequest(
26                "If-Match must be a strong ETag of the form \"<integer>\"".to_owned(),
27            )
28        })?;
29    stripped
30        .parse::<i64>()
31        .map(Some)
32        .map_err(|_| CoolError::BadRequest("If-Match ETag must be an integer".to_owned()))
33}
34
35/// Insert an `ETag` header onto a response, formatted as a strong validator
36/// over the integer optimistic-locking version.
37pub fn set_version_etag(response: &mut Response, version: i64) {
38    if let Ok(value) = HeaderValue::from_str(&format!("\"{version}\"")) {
39        response.headers_mut().insert(header::ETAG, value);
40    }
41}