lychee_lib/types/
cache.rs1use std::fmt::Display;
2
3use http::StatusCode;
4use serde::{Deserialize, Deserializer, Serialize, Serializer, ser::SerializeStruct};
5
6use crate::{ErrorKind, Status, StatusCodeSelector};
7
8#[derive(Debug, Serialize, Hash, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
12pub enum CacheStatus {
13 #[serde(serialize_with = "serialize_status_code")]
15 Ok(StatusCode),
16 #[serde(serialize_with = "serialize_optional_status_code")]
18 Error(Option<StatusCode>),
19 Excluded,
24 Unsupported,
31}
32
33#[allow(clippy::trivially_copy_pass_by_ref)]
35pub(crate) fn serialize_status_code<S>(
36 status: &StatusCode,
37 serializer: S,
38) -> Result<S::Ok, S::Error>
39where
40 S: Serializer,
41{
42 let mut s = serializer.serialize_struct("StatusCode", 1)?;
43 s.serialize_field("code", &status.as_u16())?;
44 s.end()
45}
46
47#[allow(clippy::trivially_copy_pass_by_ref, clippy::ref_option)]
48fn serialize_optional_status_code<S>(
49 status: &Option<StatusCode>,
50 serializer: S,
51) -> Result<S::Ok, S::Error>
52where
53 S: Serializer,
54{
55 match status {
56 Some(code) => serialize_status_code(code, serializer),
57 None => serializer.serialize_none(),
58 }
59}
60
61impl<'de> Deserialize<'de> for CacheStatus {
62 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63 where
64 D: Deserializer<'de>,
65 {
66 let status = <&str as Deserialize<'de>>::deserialize(deserializer)?;
67 match status {
68 "Excluded" => Ok(CacheStatus::Excluded),
69 "Unsupported" => Ok(CacheStatus::Unsupported),
73 other => match other.parse::<u16>() {
74 Ok(code) => {
75 let code = StatusCode::from_u16(code).map_err(|_| {
76 use serde::de::Error;
77 D::Error::custom(
78 "invalid status code value, expected the value to be >= 100 and <= 999",
79 )
80 })?;
81 if code.is_success() {
82 Ok(CacheStatus::Ok(code))
87 } else {
88 Ok(CacheStatus::Error(Some(code)))
90 }
91 }
92 Err(_) => Ok(CacheStatus::Error(None)),
93 },
94 }
95 }
96}
97
98impl Display for CacheStatus {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 match self {
101 Self::Ok(_) => write!(f, "OK (cached)"),
102 Self::Error(_) => write!(f, "Error (cached)"),
103 Self::Excluded => write!(f, "Excluded (cached)"),
104 Self::Unsupported => write!(f, "Unsupported (cached)"),
105 }
106 }
107}
108
109impl From<&Status> for CacheStatus {
110 fn from(s: &Status) -> Self {
111 match s {
112 Status::Cached(s) => *s,
113 Status::Ok(code) | Status::UnknownStatusCode(code) => Self::Ok(*code),
117 Status::Excluded => Self::Excluded,
118 Status::Unsupported(_) => Self::Unsupported,
119 Status::Redirected(code, _) => Self::Error(Some(*code)),
120 Status::Timeout(code) => Self::Error(*code),
121 Status::Error(e) => match e {
122 ErrorKind::RejectedStatusCode(code) => Self::Error(Some(*code)),
123 ErrorKind::ReadResponseBody(e) | ErrorKind::BuildRequestClient(e) => {
124 match e.status() {
125 Some(code) => Self::Error(Some(code)),
126 None => Self::Error(None),
127 }
128 }
129 _ => Self::Error(None),
130 },
131 Status::RequestError(_) | Status::UnknownMailStatus(_) => Self::Error(None),
132 }
133 }
134}
135
136impl From<CacheStatus> for Option<StatusCode> {
137 fn from(val: CacheStatus) -> Self {
138 match val {
139 CacheStatus::Ok(status) => Some(status),
140 CacheStatus::Error(status) => status,
141 _ => None,
142 }
143 }
144}
145
146impl CacheStatus {
147 #[must_use]
149 pub fn is_excluded(&self, excluder: &StatusCodeSelector) -> bool {
150 match Option::<StatusCode>::from(*self) {
151 Some(status) => excluder.contains(status.as_u16()),
152 _ => false,
153 }
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use http::StatusCode;
160 use serde::Deserialize;
161 use serde::de::value::{BorrowedStrDeserializer, Error as DeserializerError};
162
163 use crate::CacheStatus;
164
165 fn deserialize_cache_status(s: &str) -> Result<CacheStatus, DeserializerError> {
166 let deserializer: BorrowedStrDeserializer<DeserializerError> =
167 BorrowedStrDeserializer::new(s);
168 CacheStatus::deserialize(deserializer)
169 }
170
171 #[test]
172 fn test_deserialize_cache_status_success_code() {
173 assert_eq!(
174 deserialize_cache_status("200"),
175 Ok(CacheStatus::Ok(StatusCode::OK))
176 );
177 }
178
179 #[test]
180 fn test_deserialize_cache_status_error_code() {
181 assert_eq!(
182 deserialize_cache_status("404"),
183 Ok(CacheStatus::Error(Some(StatusCode::NOT_FOUND)))
184 );
185 }
186
187 #[test]
188 fn test_deserialize_cache_status_excluded() {
189 assert_eq!(
190 deserialize_cache_status("Excluded"),
191 Ok(CacheStatus::Excluded)
192 );
193 }
194
195 #[test]
196 fn test_deserialize_cache_status_unsupported() {
197 assert_eq!(
198 deserialize_cache_status("Unsupported"),
199 Ok(CacheStatus::Unsupported)
200 );
201 }
202
203 #[test]
204 fn test_deserialize_cache_status_blank() {
205 assert_eq!(deserialize_cache_status(""), Ok(CacheStatus::Error(None)));
206 }
207}