async_nats/
status.rs

1// Copyright 2020-2023 The NATS Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! NATS status codes.
15
16// Heavily borrowed from the http crate, would re-export since it is already a dependency
17// but we have our own range of constants.
18use std::convert::TryFrom;
19use std::error::Error;
20use std::fmt;
21use std::num::NonZeroU16;
22use std::str::FromStr;
23
24use serde::{Deserialize, Serialize};
25
26/// A possible error value when converting a `StatusCode` from a `u16` or `&str`
27///
28/// This error indicates that the supplied input was not a valid number, was less
29/// than 100, or was greater than 999.
30pub struct InvalidStatusCode {}
31
32impl fmt::Debug for InvalidStatusCode {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        f.debug_struct("InvalidStatusCode").finish()
35    }
36}
37
38impl fmt::Display for InvalidStatusCode {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str("invalid status code")
41    }
42}
43
44impl Error for InvalidStatusCode {}
45
46impl InvalidStatusCode {
47    fn new() -> InvalidStatusCode {
48        InvalidStatusCode {}
49    }
50}
51
52/// An NATS status code.
53///
54/// Constants are provided for known status codes.
55///
56/// Status code values in the range 100-999 (inclusive) are supported by this
57/// type. Values in the range 100-599 are semantically classified by the most
58/// significant digit. See [`StatusCode::is_success`], etc.
59///
60/// # Examples
61///
62/// ```
63/// use async_nats::StatusCode;
64///
65/// assert_eq!(StatusCode::OK.as_u16(), 200);
66/// assert!(StatusCode::OK.is_success());
67/// ```
68#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
69pub struct StatusCode(NonZeroU16);
70
71impl StatusCode {
72    /// Converts a u16 to a status code.
73    ///
74    /// The function validates the correctness of the supplied u16. It must be
75    /// greater or equal to 100 and less than 1000.
76    ///
77    /// # Example
78    ///
79    /// ```
80    /// use async_nats::status::StatusCode;
81    ///
82    /// let ok = StatusCode::from_u16(200).unwrap();
83    /// assert_eq!(ok, StatusCode::OK);
84    ///
85    /// let err = StatusCode::from_u16(99);
86    /// assert!(err.is_err());
87    ///
88    /// let err = StatusCode::from_u16(1000);
89    /// assert!(err.is_err());
90    /// ```
91    #[inline]
92    pub fn from_u16(src: u16) -> Result<StatusCode, InvalidStatusCode> {
93        if !(100..1000).contains(&src) {
94            return Err(InvalidStatusCode::new());
95        }
96
97        NonZeroU16::new(src)
98            .map(StatusCode)
99            .ok_or_else(InvalidStatusCode::new)
100    }
101
102    /// Converts a `&[u8]` to a status code
103    pub fn from_bytes(src: &[u8]) -> Result<StatusCode, InvalidStatusCode> {
104        if src.len() != 3 {
105            return Err(InvalidStatusCode::new());
106        }
107
108        let a = src[0].wrapping_sub(b'0') as u16;
109        let b = src[1].wrapping_sub(b'0') as u16;
110        let c = src[2].wrapping_sub(b'0') as u16;
111
112        if a == 0 || a > 9 || b > 9 || c > 9 {
113            return Err(InvalidStatusCode::new());
114        }
115
116        let status = (a * 100) + (b * 10) + c;
117        NonZeroU16::new(status)
118            .map(StatusCode)
119            .ok_or_else(InvalidStatusCode::new)
120    }
121
122    /// Returns the `u16` corresponding to this `StatusCode`.
123    ///
124    /// # Example
125    ///
126    /// ```
127    /// let status = async_nats::StatusCode::OK;
128    /// assert_eq!(status.as_u16(), 200);
129    /// ```
130    #[inline]
131    pub fn as_u16(&self) -> u16 {
132        (*self).into()
133    }
134
135    /// Check if status is within 100-199.
136    #[inline]
137    pub fn is_informational(&self) -> bool {
138        (100..200).contains(&self.0.get())
139    }
140
141    /// Check if status is within 200-299.
142    #[inline]
143    pub fn is_success(&self) -> bool {
144        (200..300).contains(&self.0.get())
145    }
146
147    /// Check if status is within 300-399.
148    #[inline]
149    pub fn is_redirection(&self) -> bool {
150        (300..400).contains(&self.0.get())
151    }
152
153    /// Check if status is within 400-499.
154    #[inline]
155    pub fn is_client_error(&self) -> bool {
156        (400..500).contains(&self.0.get())
157    }
158
159    /// Check if status is within 500-599.
160    #[inline]
161    pub fn is_server_error(&self) -> bool {
162        (500..600).contains(&self.0.get())
163    }
164}
165
166impl fmt::Debug for StatusCode {
167    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
168        fmt::Debug::fmt(&self.0, f)
169    }
170}
171
172/// Formats the status code.
173///
174/// # Example
175///
176/// ```
177/// # use async_nats::StatusCode;
178/// assert_eq!(format!("{}", StatusCode::OK), "200");
179/// ```
180impl fmt::Display for StatusCode {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        // TODO(caspervonb) display a canonical statically known reason / human readable description of the status
183        write!(f, "{}", u16::from(*self))
184    }
185}
186
187impl Default for StatusCode {
188    #[inline]
189    fn default() -> StatusCode {
190        StatusCode::OK
191    }
192}
193
194impl PartialEq<u16> for StatusCode {
195    #[inline]
196    fn eq(&self, other: &u16) -> bool {
197        self.as_u16() == *other
198    }
199}
200
201impl PartialEq<StatusCode> for u16 {
202    #[inline]
203    fn eq(&self, other: &StatusCode) -> bool {
204        *self == other.as_u16()
205    }
206}
207
208impl From<StatusCode> for u16 {
209    #[inline]
210    fn from(status: StatusCode) -> u16 {
211        status.0.get()
212    }
213}
214
215impl FromStr for StatusCode {
216    type Err = InvalidStatusCode;
217
218    fn from_str(s: &str) -> Result<StatusCode, InvalidStatusCode> {
219        StatusCode::from_bytes(s.as_ref())
220    }
221}
222
223impl<'a> From<&'a StatusCode> for StatusCode {
224    #[inline]
225    fn from(t: &'a StatusCode) -> Self {
226        *t
227    }
228}
229
230impl<'a> TryFrom<&'a [u8]> for StatusCode {
231    type Error = InvalidStatusCode;
232
233    #[inline]
234    fn try_from(t: &'a [u8]) -> Result<Self, Self::Error> {
235        StatusCode::from_bytes(t)
236    }
237}
238
239impl<'a> TryFrom<&'a str> for StatusCode {
240    type Error = InvalidStatusCode;
241
242    #[inline]
243    fn try_from(t: &'a str) -> Result<Self, Self::Error> {
244        t.parse()
245    }
246}
247
248impl TryFrom<u16> for StatusCode {
249    type Error = InvalidStatusCode;
250
251    #[inline]
252    fn try_from(t: u16) -> Result<Self, Self::Error> {
253        StatusCode::from_u16(t)
254    }
255}
256
257impl StatusCode {
258    pub const IDLE_HEARTBEAT: StatusCode = StatusCode(new_nonzero_u16(100));
259    pub const OK: StatusCode = StatusCode(new_nonzero_u16(200));
260    pub const NOT_FOUND: StatusCode = StatusCode(new_nonzero_u16(404));
261    pub const TIMEOUT: StatusCode = StatusCode(new_nonzero_u16(408));
262    pub const NO_RESPONDERS: StatusCode = StatusCode(new_nonzero_u16(503));
263    pub const REQUEST_TERMINATED: StatusCode = StatusCode(new_nonzero_u16(409));
264}
265
266/// This function is needed until const Option::unwrap becomes stable.
267/// Ref: https://github.com/nats-io/nats.rs/issues/804
268const fn new_nonzero_u16(n: u16) -> NonZeroU16 {
269    match NonZeroU16::new(n) {
270        Some(d) => d,
271        None => {
272            panic!("Invalid non-zero u16");
273        }
274    }
275}