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