lychee_lib/types/
response.rs

1use std::fmt::Display;
2
3use http::StatusCode;
4use serde::Serialize;
5
6use crate::{InputSource, Status, Uri};
7
8/// Response type returned by lychee after checking a URI
9//
10// Body is public to allow inserting into stats maps (error_map, success_map,
11// etc.) without `Clone`, because the inner `ErrorKind` in `response.status` is
12// not `Clone`. Use `body()` to access the body in the rest of the code.
13//
14// `pub(crate)` is insufficient, because the `stats` module is in the `bin`
15// crate crate.
16#[derive(Debug)]
17pub struct Response(InputSource, pub ResponseBody);
18
19impl Response {
20    #[inline]
21    #[must_use]
22    /// Create new response
23    pub const fn new(uri: Uri, status: Status, source: InputSource) -> Self {
24        Response(source, ResponseBody { uri, status })
25    }
26
27    #[inline]
28    #[must_use]
29    /// Retrieve the underlying status of the response
30    pub const fn status(&self) -> &Status {
31        &self.1.status
32    }
33
34    #[inline]
35    #[must_use]
36    /// Retrieve the underlying source of the response
37    /// (e.g. the input file or the URL)
38    pub const fn source(&self) -> &InputSource {
39        &self.0
40    }
41
42    #[inline]
43    #[must_use]
44    /// Retrieve the underlying body of the response
45    pub const fn body(&self) -> &ResponseBody {
46        &self.1
47    }
48}
49
50impl Display for Response {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        <ResponseBody as Display>::fmt(&self.1, f)
53    }
54}
55
56impl Serialize for Response {
57    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
58    where
59        S: serde::Serializer,
60    {
61        <ResponseBody as Serialize>::serialize(&self.1, s)
62    }
63}
64
65#[allow(clippy::module_name_repetitions)]
66#[derive(Debug, Serialize, Hash, PartialEq, Eq)]
67/// Encapsulates the state of a URI check
68pub struct ResponseBody {
69    #[serde(flatten)]
70    /// The URI which was checked
71    pub uri: Uri,
72    /// The status of the check
73    pub status: Status,
74}
75
76// Extract as much information from the underlying error conditions as possible
77// without being too verbose. Some dependencies (rightfully) don't expose all
78// error fields to downstream crates, which is why we have to defer to pattern
79// matching in these cases.
80impl Display for ResponseBody {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        // Always write the URI
83        write!(f, "{}", self.uri)?;
84
85        // Early return for OK status to avoid verbose output
86        if matches!(self.status, Status::Ok(StatusCode::OK)) {
87            return Ok(());
88        }
89
90        // Format status and return early if empty
91        let status_output = self.status.to_string();
92        if status_output.is_empty() {
93            return Ok(());
94        }
95
96        // Write status with separator
97        write!(f, " | {status_output}")?;
98
99        // Add details if available
100        if let Some(details) = self.status.details() {
101            write!(f, ": {details}")
102        } else {
103            Ok(())
104        }
105    }
106}