watermelon_proto/
status_code.rs

1use core::{
2    fmt::{self, Display, Formatter},
3    num::NonZeroU16,
4    str::FromStr,
5};
6
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
8
9use crate::util;
10
11/// A NATS status code
12///
13/// Constants are provided for known and accurately status codes
14/// within the NATS Server.
15///
16/// Values are guaranteed to be in range `100..1000`.
17#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
18pub struct StatusCode(NonZeroU16);
19
20impl StatusCode {
21    /// The Jetstream consumer hearthbeat timeout has been reached with no new messages to deliver
22    ///
23    /// See [ADR-9].
24    ///
25    /// [ADR-9]: https://github.com/nats-io/nats-architecture-and-design/blob/main/adr/ADR-9.md
26    pub const IDLE_HEARTBEAT: StatusCode = Self::new_internal(100);
27    /// The request has successfully been sent
28    pub const OK: StatusCode = Self::new_internal(200);
29    /// The requested Jetstream resource doesn't exist
30    pub const NOT_FOUND: StatusCode = Self::new_internal(404);
31    /// The pull consumer batch reached the timeout
32    pub const TIMEOUT: StatusCode = Self::new_internal(408);
33    /// The request was sent to a subject that does not appear to have any subscribers listening
34    pub const NO_RESPONDERS: StatusCode = Self::new_internal(503);
35
36    /// Decodes a status code from a slice of ASCII characters.
37    ///
38    /// The ASCII representation is expected to be in the form of `"NNN"`, where `N` is a numeric
39    /// digit.
40    ///
41    /// # Errors
42    ///
43    /// It returns an error if the slice of bytes does not contain a valid status code.
44    pub fn from_ascii_bytes(buf: &[u8]) -> Result<Self, StatusCodeError> {
45        if buf.len() != 3 {
46            return Err(StatusCodeError);
47        }
48
49        util::parse_u16(buf)
50            .map_err(|_| StatusCodeError)?
51            .try_into()
52            .map(Self)
53            .map_err(|_| StatusCodeError)
54    }
55
56    const fn new_internal(val: u16) -> Self {
57        match NonZeroU16::new(val) {
58            Some(val) => Self(val),
59            None => unreachable!(),
60        }
61    }
62}
63
64impl FromStr for StatusCode {
65    type Err = StatusCodeError;
66
67    fn from_str(s: &str) -> Result<Self, Self::Err> {
68        Self::from_ascii_bytes(s.as_bytes())
69    }
70}
71
72impl Display for StatusCode {
73    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
74        Display::fmt(&self.0, f)
75    }
76}
77
78impl TryFrom<u16> for StatusCode {
79    type Error = StatusCodeError;
80
81    fn try_from(value: u16) -> Result<Self, Self::Error> {
82        if (100..1000).contains(&value) {
83            Ok(Self(NonZeroU16::new(value).unwrap()))
84        } else {
85            Err(StatusCodeError)
86        }
87    }
88}
89
90impl From<StatusCode> for u16 {
91    fn from(value: StatusCode) -> Self {
92        value.0.get()
93    }
94}
95
96impl Serialize for StatusCode {
97    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
98        u16::from(*self).serialize(serializer)
99    }
100}
101
102impl<'de> Deserialize<'de> for StatusCode {
103    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
104        let n = u16::deserialize(deserializer)?;
105        n.try_into().map_err(de::Error::custom)
106    }
107}
108
109/// An error encountered while parsing [`StatusCode`]
110#[derive(Debug, thiserror::Error)]
111#[non_exhaustive]
112#[error("invalid status code")]
113pub struct StatusCodeError;
114
115#[cfg(test)]
116mod tests {
117    use alloc::string::ToString;
118
119    use claims::assert_err;
120
121    use super::StatusCode;
122
123    #[test]
124    fn valid_status_codes() {
125        let status_codes = [100, 200, 404, 408, 409, 503];
126
127        for status_code in status_codes {
128            assert_eq!(
129                status_code,
130                u16::from(StatusCode::try_from(status_code).unwrap())
131            );
132
133            let s = status_code.to_string();
134            assert_eq!(
135                status_code,
136                u16::from(StatusCode::from_ascii_bytes(s.as_bytes()).unwrap())
137            );
138        }
139    }
140
141    #[test]
142    fn invalid_status_codes() {
143        let status_codes = [0, 5, 55, 9999];
144
145        for status_code in status_codes {
146            assert_err!(StatusCode::try_from(status_code));
147
148            let s = status_code.to_string();
149            assert_err!(StatusCode::from_ascii_bytes(s.as_bytes()));
150        }
151    }
152}