kamu_snap_response/
response_code.rs1use crate::error::Error;
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
18#[serde(transparent)]
19pub struct ResponseCode(String);
20
21impl ResponseCode {
22 pub fn parse(s: impl Into<String>) -> Self {
24 Self(s.into())
25 }
26
27 pub fn from_parts(http: u16, service: ServiceCode, case: u8) -> Self {
33 assert!((100..=999).contains(&http), "http status out of range");
34 assert!(case <= 99, "case code out of range (must be 0..=99)");
35 Self(format!("{http:03}{:02}{case:02}", service.get()))
36 }
37
38 pub fn success(service: ServiceCode, sub: u8) -> Self {
40 Self::from_parts(200, service, sub)
41 }
42
43 pub fn raw(&self) -> &str {
45 &self.0
46 }
47
48 pub fn http(&self) -> Option<http::StatusCode> {
52 if self.0.len() != 7 {
53 return None;
54 }
55 let n = self.0.get(..3)?.parse::<u16>().ok()?;
56 http::StatusCode::from_u16(n).ok()
57 }
58
59 pub fn service(&self) -> Option<ServiceCode> {
61 if self.0.len() != 7 {
62 return None;
63 }
64 let n = self.0.get(3..5)?.parse::<u8>().ok()?;
65 ServiceCode::new(n)
66 }
67
68 pub fn case(&self) -> Option<u8> {
70 if self.0.len() != 7 {
71 return None;
72 }
73 self.0.get(5..7)?.parse::<u8>().ok()
74 }
75
76 pub fn classify(&self) -> Option<Error> {
83 let http = self.http()?.as_u16();
84 let case = self.case()?;
85 Error::from_http_and_case(http, case)
86 }
87}
88
89impl core::fmt::Display for ResponseCode {
90 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
91 f.write_str(&self.0)
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub struct ServiceCode(u8);
101
102impl ServiceCode {
103 pub const fn new(code: u8) -> Option<Self> {
105 if code > 99 { None } else { Some(Self(code)) }
106 }
107
108 pub const fn get(self) -> u8 {
110 self.0
111 }
112}
113
114impl core::fmt::Display for ServiceCode {
115 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116 write!(f, "{:02}", self.0)
117 }
118}
119
120impl serde::Serialize for ServiceCode {
121 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
122 self.0.serialize(serializer)
123 }
124}
125
126impl<'de> serde::Deserialize<'de> for ServiceCode {
127 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
128 let n = u8::deserialize(deserializer)?;
129 ServiceCode::new(n).ok_or_else(|| serde::de::Error::custom("service code must be 0..=99"))
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn from_parts_builds_canonical_seven_digit_code() {
139 let sc = ServiceCode::new(5).unwrap();
140 let rc = ResponseCode::from_parts(404, sc, 11);
141 assert_eq!(rc.raw(), "4040511");
142 assert_eq!(rc.raw().len(), 7);
143 assert_eq!(rc.http().unwrap().as_u16(), 404);
144 assert_eq!(rc.service().unwrap().get(), 5);
145 assert_eq!(rc.case(), Some(11));
146 }
147
148 #[test]
149 #[should_panic(expected = "case code out of range")]
150 fn from_parts_panics_on_case_over_99() {
151 let sc = ServiceCode::new(5).unwrap();
152 let _ = ResponseCode::from_parts(404, sc, 100);
154 }
155
156 #[test]
157 #[should_panic(expected = "case code out of range")]
158 fn success_panics_on_sub_over_99() {
159 let sc = ServiceCode::new(0).unwrap();
160 let _ = ResponseCode::success(sc, 200);
161 }
162}