Skip to main content

lychee_lib/types/
request_error.rs

1use std::convert::TryFrom;
2use std::sync::LazyLock;
3use thiserror::Error;
4
5use crate::{ErrorKind, RawUri, Response, Status, Uri};
6use crate::{InputSource, ResolvedInputSource};
7
8static ERROR_URI: LazyLock<Uri> = LazyLock::new(|| Uri::try_from("error:").unwrap());
9
10/// An error which occurs while trying to construct a [`Request`] object.
11/// That is, an error which happens while trying to load links from an input
12/// source.
13#[derive(Error, Debug, PartialEq, Eq, Hash)]
14pub enum RequestError {
15    /// Unable to construct a URL for a link appearing within the given source.
16    #[error("Error building URL for {0}: {2}")]
17    CreateRequestItem(RawUri, ResolvedInputSource, #[source] ErrorKind),
18
19    /// Unable to load the content of an input source.
20    #[error("Error reading input '{0}': {1}")]
21    GetInputContent(InputSource, #[source] ErrorKind),
22
23    /// Unable to load an input source directly specified by the user.
24    #[error("Error reading user input '{0}': {1}")]
25    UserInputContent(InputSource, #[source] ErrorKind),
26
27    /// Unable to process URLs from an input source due to an error.
28    /// This is for errors that happen after the input content was loaded.
29    #[error("Error processing input '{0}': {1}")]
30    InputSourceError(InputSource, #[source] ErrorKind),
31}
32
33impl RequestError {
34    /// Get the underlying cause of this [`RequestError`].
35    #[must_use]
36    pub const fn error(&self) -> &ErrorKind {
37        match self {
38            Self::CreateRequestItem(_, _, e)
39            | Self::GetInputContent(_, e)
40            | Self::UserInputContent(_, e)
41            | Self::InputSourceError(_, e) => e,
42        }
43    }
44
45    /// Convert this [`RequestError`] into its source error.
46    #[must_use]
47    pub fn into_error(self) -> ErrorKind {
48        match self {
49            Self::CreateRequestItem(_, _, e)
50            | Self::GetInputContent(_, e)
51            | Self::UserInputContent(_, e)
52            | Self::InputSourceError(_, e) => e,
53        }
54    }
55
56    /// Get (a clone of) the input source within which the error happened.
57    #[must_use]
58    pub fn input_source(&self) -> InputSource {
59        match self {
60            Self::CreateRequestItem(_, src, _) => src.clone().into(),
61            Self::GetInputContent(src, _)
62            | Self::UserInputContent(src, _)
63            | Self::InputSourceError(src, _) => src.clone(),
64        }
65    }
66
67    /// Convert this request error into a (failed) [`Response`] for reporting
68    /// purposes.
69    ///
70    /// # Errors
71    ///
72    /// If this `RequestError` was caused by failing to load a user-specified
73    /// input, the underlying cause of the `RequestError` will be returned
74    /// as an Err. This allows the error to be propagated back to the user.
75    pub fn into_response(self) -> Result<Response, ErrorKind> {
76        match self {
77            RequestError::UserInputContent(_, e) => Err(e),
78            e => {
79                let src = e.input_source();
80                let span = match &e {
81                    RequestError::CreateRequestItem(raw, _, _) => Some(raw.span),
82                    _ => None,
83                };
84
85                Ok(Response::new(
86                    ERROR_URI.clone(),
87                    Status::RequestError(e),
88                    src,
89                    span,
90                    None,
91                ))
92            }
93        }
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::ERROR_URI;
100    use std::sync::LazyLock;
101
102    #[test]
103    fn test_error_url_parses() {
104        let _ = LazyLock::force(&ERROR_URI);
105    }
106}