Skip to main content

watermelon_proto/
status_code.rs

1use core::{
2    fmt::{self, Display, Formatter},
3    num::NonZero,
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(NonZero<u16>);
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 pull consumer batch has been terminated by the server
34    ///
35    /// This status code is used by the NATS Server for multiple unrelated
36    /// conditions, which can only be told apart via
37    /// [`ServerMessage::status_description`]. Known descriptions include
38    /// `Batch Completed`, `Server Shutdown`, `Leadership Change`,
39    /// `Consumer Deleted`, `Consumer is push based`,
40    /// `Exceeded MaxRequestBatch of %d`, `Exceeded MaxRequestExpires of %v`,
41    /// `Exceeded MaxRequestMaxBytes of %v`, `Exceeded MaxWaiting` and
42    /// `Message Size Exceeds MaxBytes`.
43    ///
44    /// [`ServerMessage::status_description`]: crate::ServerMessage::status_description
45    pub const CONFLICT: StatusCode = Self::new_internal(409);
46    /// The request was sent to a subject that does not appear to have any subscribers listening
47    pub const NO_RESPONDERS: StatusCode = Self::new_internal(503);
48
49    /// Decodes a status code from a slice of ASCII characters.
50    ///
51    /// The ASCII representation is expected to be in the form of `"NNN"`, where `N` is a numeric
52    /// digit.
53    ///
54    /// # Errors
55    ///
56    /// It returns an error if the slice of bytes does not contain a valid status code.
57    pub fn from_ascii_bytes(buf: &[u8]) -> Result<Self, StatusCodeError> {
58        if buf.len() != 3 {
59            return Err(StatusCodeError);
60        }
61
62        util::parse_u16(buf)
63            .map_err(|_| StatusCodeError)?
64            .try_into()
65            .map(Self)
66            .map_err(|_| StatusCodeError)
67    }
68
69    const fn new_internal(val: u16) -> Self {
70        Self(NonZero::new(val).unwrap())
71    }
72}
73
74impl FromStr for StatusCode {
75    type Err = StatusCodeError;
76
77    fn from_str(s: &str) -> Result<Self, Self::Err> {
78        Self::from_ascii_bytes(s.as_bytes())
79    }
80}
81
82impl Display for StatusCode {
83    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
84        Display::fmt(&self.0, f)
85    }
86}
87
88impl TryFrom<u16> for StatusCode {
89    type Error = StatusCodeError;
90
91    fn try_from(value: u16) -> Result<Self, Self::Error> {
92        if (100..1000).contains(&value) {
93            Ok(Self(NonZero::new(value).unwrap()))
94        } else {
95            Err(StatusCodeError)
96        }
97    }
98}
99
100impl From<StatusCode> for u16 {
101    fn from(value: StatusCode) -> Self {
102        value.0.get()
103    }
104}
105
106impl Serialize for StatusCode {
107    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
108        u16::from(*self).serialize(serializer)
109    }
110}
111
112impl<'de> Deserialize<'de> for StatusCode {
113    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
114        let n = u16::deserialize(deserializer)?;
115        n.try_into().map_err(de::Error::custom)
116    }
117}
118
119/// An error encountered while parsing [`StatusCode`]
120#[derive(Debug, thiserror::Error)]
121#[non_exhaustive]
122#[error("invalid status code")]
123pub struct StatusCodeError;
124
125#[cfg(test)]
126mod tests {
127    use alloc::string::ToString;
128
129    use claims::assert_err;
130
131    use super::StatusCode;
132
133    #[test]
134    fn valid_status_codes() {
135        let status_codes = [100, 200, 404, 408, 409, 503];
136
137        for status_code in status_codes {
138            assert_eq!(
139                status_code,
140                u16::from(StatusCode::try_from(status_code).unwrap())
141            );
142
143            let s = status_code.to_string();
144            assert_eq!(
145                status_code,
146                u16::from(StatusCode::from_ascii_bytes(s.as_bytes()).unwrap())
147            );
148        }
149    }
150
151    #[test]
152    fn invalid_status_codes() {
153        let status_codes = [0, 5, 55, 9999];
154
155        for status_code in status_codes {
156            assert_err!(StatusCode::try_from(status_code));
157
158            let s = status_code.to_string();
159            assert_err!(StatusCode::from_ascii_bytes(s.as_bytes()));
160        }
161    }
162}