Skip to main content

openapi_nexus_core/data/
status_code.rs

1use std::cmp::Ordering;
2use std::fmt;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
7pub enum StatusCodeKind {
8    Invalid,
9    Exact(u16),
10    Range { start: u16, end: u16 },
11    Default,
12}
13
14#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
15pub struct StatusCode {
16    raw: String,
17    kind: StatusCodeKind,
18}
19
20impl StatusCode {
21    pub fn new<S: AsRef<str>>(status: S) -> Self {
22        let raw = status.as_ref().trim().to_string();
23        let upper = raw.to_uppercase();
24        let kind = match upper.as_str() {
25            "DEFAULT" => StatusCodeKind::Default,
26            s if s.len() == 3 && s.ends_with("XX") => {
27                let prefix = s.chars().next().and_then(|c| c.to_digit(10));
28                if let Some(digit) = prefix {
29                    let start = (digit as u16) * 100;
30                    let end = start + 100;
31                    StatusCodeKind::Range { start, end }
32                } else {
33                    StatusCodeKind::Invalid
34                }
35            }
36            s => match s.parse::<u16>() {
37                Ok(code) => StatusCodeKind::Exact(code),
38                Err(_) => StatusCodeKind::Invalid,
39            },
40        };
41
42        Self { raw, kind }
43    }
44
45    pub fn raw(&self) -> &str {
46        &self.raw
47    }
48
49    pub fn literal(&self) -> Option<u16> {
50        match self.kind {
51            StatusCodeKind::Exact(code) => Some(code),
52            _ => None,
53        }
54    }
55
56    pub fn range_bounds(&self) -> Option<(u16, u16)> {
57        match self.kind {
58            StatusCodeKind::Range { start, end } => Some((start, end)),
59            _ => None,
60        }
61    }
62
63    pub fn is_default(&self) -> bool {
64        matches!(self.kind, StatusCodeKind::Default)
65    }
66
67    pub fn is_success(&self) -> bool {
68        match self.kind {
69            StatusCodeKind::Exact(code) => (200..300).contains(&code),
70            StatusCodeKind::Range { start, .. } => (200..300).contains(&start),
71            StatusCodeKind::Default | StatusCodeKind::Invalid => false,
72        }
73    }
74
75    pub fn condition_expression(&self) -> Option<String> {
76        match self.kind {
77            StatusCodeKind::Exact(code) => Some(format!("response.status === {}", code)),
78            StatusCodeKind::Range { start, end } => Some(format!(
79                "response.status >= {} && response.status < {}",
80                start, end
81            )),
82            StatusCodeKind::Default | StatusCodeKind::Invalid => None,
83        }
84    }
85
86    fn sort_key(&self) -> (u8, u16, &str) {
87        match self.kind {
88            StatusCodeKind::Exact(code) => (0, code, self.raw.as_str()),
89            StatusCodeKind::Range { start, .. } => (1, start, self.raw.as_str()),
90            StatusCodeKind::Default => (2, 0, self.raw.as_str()),
91            StatusCodeKind::Invalid => (3, 0, self.raw.as_str()),
92        }
93    }
94}
95
96impl fmt::Display for StatusCode {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        write!(f, "{}", self.raw)
99    }
100}
101
102impl From<&str> for StatusCode {
103    fn from(value: &str) -> Self {
104        StatusCode::new(value)
105    }
106}
107
108impl From<String> for StatusCode {
109    fn from(value: String) -> Self {
110        StatusCode::new(value)
111    }
112}
113
114impl Ord for StatusCode {
115    fn cmp(&self, other: &Self) -> Ordering {
116        self.sort_key().cmp(&other.sort_key())
117    }
118}
119
120impl PartialOrd for StatusCode {
121    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
122        Some(self.cmp(other))
123    }
124}