Skip to main content

trillium_http/
priority.rs

1//! HTTP priority signals, as defined by [RFC 9218][rfc] (Extensible Prioritization
2//! Scheme for HTTP).
3//!
4//! A client expresses how it would like a server to schedule a response relative to
5//! other concurrent responses on the same connection — both how urgent it is and
6//! whether it can be delivered incrementally. The signal is advisory: a server is free
7//! to use it, adapt it, or ignore it.
8//!
9//! [rfc]: https://www.rfc-editor.org/rfc/rfc9218
10
11mod parse;
12
13use std::{
14    fmt::{self, Display, Formatter},
15    str::FromStr,
16};
17
18const DEFAULT_URGENCY: u8 = 3;
19const MAX_URGENCY: u8 = 7;
20
21/// A response priority: an urgency level and an incremental flag.
22///
23/// Urgency runs from 0 (most urgent) to 7 (least urgent), defaulting to 3. The
24/// incremental flag indicates whether the response can be usefully processed as it
25/// arrives (e.g. a progressively-rendered image) rather than only once complete;
26/// servers may interleave incremental responses of equal urgency.
27///
28/// Parse one from a `priority` header value with [`Priority::parse`] (or [`FromStr`]), or start
29/// from [`Priority::default`]; render it back to header form with [`Display`].
30#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
31pub struct Priority {
32    urgency: u8,
33    incremental: bool,
34}
35
36impl Default for Priority {
37    fn default() -> Self {
38        Self {
39            urgency: DEFAULT_URGENCY,
40            incremental: false,
41        }
42    }
43}
44
45impl Priority {
46    /// Construct a non-incremental priority with the given urgency.
47    ///
48    /// Urgency is clamped to the valid range 0..=7.
49    #[must_use]
50    #[cfg(test)] // builder API deferred with the handler-facing priority surface
51    pub fn new(urgency: u8) -> Self {
52        Self {
53            urgency: urgency.min(MAX_URGENCY),
54            incremental: false,
55        }
56    }
57
58    /// The urgency level, 0 (most urgent) through 7 (least urgent).
59    #[must_use]
60    pub const fn urgency(self) -> u8 {
61        self.urgency
62    }
63
64    /// Whether the response may be delivered incrementally.
65    #[must_use]
66    pub const fn is_incremental(self) -> bool {
67        self.incremental
68    }
69
70    /// Set the urgency, clamped to the valid range 0..=7.
71    #[must_use]
72    #[cfg(test)] // builder API deferred with the handler-facing priority surface
73    pub fn with_urgency(mut self, urgency: u8) -> Self {
74        self.urgency = urgency.min(MAX_URGENCY);
75        self
76    }
77
78    /// Set the incremental flag.
79    #[must_use]
80    #[cfg(test)] // builder API deferred with the handler-facing priority surface
81    pub const fn with_incremental(mut self, incremental: bool) -> Self {
82        self.incremental = incremental;
83        self
84    }
85
86    /// Parse a `priority` header value. Unrecognized, missing, or malformed fields fall back to
87    /// their defaults rather than erroring, as the scheme mandates, so this is infallible.
88    #[must_use]
89    pub fn parse(s: &str) -> Self {
90        parse::parse(s)
91    }
92}
93
94impl FromStr for Priority {
95    type Err = std::convert::Infallible;
96
97    /// Infallible — see [`Priority::parse`].
98    fn from_str(s: &str) -> Result<Self, Self::Err> {
99        Ok(Self::parse(s))
100    }
101}
102
103impl Display for Priority {
104    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
105        write!(f, "u={}", self.urgency)?;
106        if self.incremental {
107            f.write_str(", i")?;
108        }
109        Ok(())
110    }
111}
112
113#[cfg(test)]
114mod test {
115    use super::Priority;
116
117    #[test]
118    fn defaults() {
119        let p = Priority::default();
120        assert_eq!(p.urgency(), 3);
121        assert!(!p.is_incremental());
122    }
123
124    #[test]
125    fn constructors_clamp() {
126        assert_eq!(Priority::new(5).urgency(), 5);
127        assert_eq!(Priority::new(200).urgency(), 7);
128        assert_eq!(Priority::default().with_urgency(99).urgency(), 7);
129        assert!(Priority::new(0).with_incremental(true).is_incremental());
130    }
131
132    #[test]
133    fn display_roundtrip() {
134        assert_eq!(Priority::new(3).to_string(), "u=3");
135        assert_eq!(
136            Priority::new(0).with_incremental(true).to_string(),
137            "u=0, i"
138        );
139        for urgency in 0..=7 {
140            for incremental in [false, true] {
141                let p = Priority::new(urgency).with_incremental(incremental);
142                assert_eq!(p.to_string().parse::<Priority>().unwrap(), p);
143            }
144        }
145    }
146}