use crate::error::Error;
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[serde(transparent)]
pub struct ResponseCode(String);
impl ResponseCode {
pub fn parse(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn from_parts(http: u16, service: ServiceCode, case: u8) -> Self {
assert!((100..=999).contains(&http), "http status out of range");
assert!(case <= 99, "case code out of range (must be 0..=99)");
Self(format!("{http:03}{:02}{case:02}", service.get()))
}
pub fn success(service: ServiceCode, sub: u8) -> Self {
Self::from_parts(200, service, sub)
}
pub fn raw(&self) -> &str {
&self.0
}
pub fn http(&self) -> Option<http::StatusCode> {
if self.0.len() != 7 {
return None;
}
let n = self.0.get(..3)?.parse::<u16>().ok()?;
http::StatusCode::from_u16(n).ok()
}
pub fn service(&self) -> Option<ServiceCode> {
if self.0.len() != 7 {
return None;
}
let n = self.0.get(3..5)?.parse::<u8>().ok()?;
ServiceCode::new(n)
}
pub fn case(&self) -> Option<u8> {
if self.0.len() != 7 {
return None;
}
self.0.get(5..7)?.parse::<u8>().ok()
}
pub fn classify(&self) -> Option<Error> {
let http = self.http()?.as_u16();
let case = self.case()?;
Error::from_http_and_case(http, case)
}
}
impl core::fmt::Display for ResponseCode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ServiceCode(u8);
impl ServiceCode {
pub const fn new(code: u8) -> Option<Self> {
if code > 99 { None } else { Some(Self(code)) }
}
pub const fn get(self) -> u8 {
self.0
}
}
impl core::fmt::Display for ServiceCode {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{:02}", self.0)
}
}
impl serde::Serialize for ServiceCode {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for ServiceCode {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let n = u8::deserialize(deserializer)?;
ServiceCode::new(n).ok_or_else(|| serde::de::Error::custom("service code must be 0..=99"))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_parts_builds_canonical_seven_digit_code() {
let sc = ServiceCode::new(5).unwrap();
let rc = ResponseCode::from_parts(404, sc, 11);
assert_eq!(rc.raw(), "4040511");
assert_eq!(rc.raw().len(), 7);
assert_eq!(rc.http().unwrap().as_u16(), 404);
assert_eq!(rc.service().unwrap().get(), 5);
assert_eq!(rc.case(), Some(11));
}
#[test]
#[should_panic(expected = "case code out of range")]
fn from_parts_panics_on_case_over_99() {
let sc = ServiceCode::new(5).unwrap();
let _ = ResponseCode::from_parts(404, sc, 100);
}
#[test]
#[should_panic(expected = "case code out of range")]
fn success_panics_on_sub_over_99() {
let sc = ServiceCode::new(0).unwrap();
let _ = ResponseCode::success(sc, 200);
}
}