openapi_nexus_core/data/
status_code.rs1use 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}