tokn_headers/schema.rs
1//! The `HeaderSchema` trait + helper functions used by per-(persona, overlay)
2//! schema structs to round-trip between their typed Rust form and the
3//! generic [`HeaderMap`].
4//!
5//! Each schema struct hand-implements [`HeaderSchema`] in Phase 1; a
6//! proc-macro derive may follow in a later phase.
7
8use crate::error::Error;
9use crate::map::HeaderMap;
10use crate::name::HeaderName;
11use crate::value::HeaderValue;
12use smol_str::SmolStr;
13
14/// A typed view over a subset of HTTP headers belonging to a particular
15/// persona or provider overlay.
16pub trait HeaderSchema: Sized {
17 /// Build the typed struct from a [`HeaderMap`]. Returns [`Error::MissingHeader`]
18 /// when a required header is absent and [`Error::InvalidValue`] when a value
19 /// fails domain-specific validation.
20 fn parse(map: &HeaderMap) -> Result<Self, Error>;
21
22 /// Render the typed struct back into a [`HeaderMap`]. Inverse of [`parse`];
23 /// optional fields that are `None` are omitted from the output.
24 ///
25 /// The verb `dump` is deliberately distinct from `build`: a future `build`
26 /// constructor (or `build_from_vars`) will *construct* a populated schema
27 /// from a [`crate::TemplateVars`] instance. `dump` only round-trips an
28 /// already-populated schema.
29 fn dump(&self) -> HeaderMap;
30
31 /// All header names that this schema may emit. Useful for golden-test
32 /// allowlists and schema documentation.
33 fn known_names() -> &'static [&'static HeaderName];
34}
35
36/// Read a required header value as a [`SmolStr`].
37pub fn required(map: &HeaderMap, name: &HeaderName) -> Result<SmolStr, Error> {
38 map
39 .get(name)
40 .map(|v| SmolStr::new(v.as_str()))
41 .ok_or_else(|| Error::MissingHeader {
42 name: SmolStr::new(name.as_str()),
43 })
44}
45
46/// Read an optional header value as `Option<SmolStr>`.
47pub fn optional(map: &HeaderMap, name: &HeaderName) -> Option<SmolStr> {
48 map.get(name).map(|v| SmolStr::new(v.as_str()))
49}
50
51/// Insert a `SmolStr` value into the map under `name`.
52pub fn put(map: &mut HeaderMap, name: &HeaderName, value: &SmolStr) {
53 map.insert(name.clone(), HeaderValue::from_string(value.to_string()));
54}
55
56/// Insert a `SmolStr` value into the map under `name` only if `Some`.
57pub fn put_opt(map: &mut HeaderMap, name: &HeaderName, value: &Option<SmolStr>) {
58 if let Some(v) = value {
59 put(map, name, v);
60 }
61}
62
63/// Read a header value from `inbound` if present, otherwise compute the
64/// supplied default. Used by `build` constructors on persona/overlay structs
65/// to populate required fields with persona-specific fallbacks.
66pub fn from_inbound_or<F: FnOnce() -> SmolStr>(inbound: &HeaderMap, key: &HeaderName, default: F) -> SmolStr {
67 inbound
68 .get(key)
69 .map(|v| SmolStr::from(v.as_str()))
70 .unwrap_or_else(default)
71}
72
73/// Read an optional header value from `inbound`. Returns `None` if absent.
74/// Sister to [`from_inbound_or`] for non-required `build` fields.
75pub fn opt_from_inbound(inbound: &HeaderMap, key: &HeaderName) -> Option<SmolStr> {
76 inbound.get(key).map(|v| SmolStr::from(v.as_str()))
77}