json_crawler/
error.rs

1use std::{
2    fmt::{Debug, Display},
3    sync::Arc,
4};
5
6pub struct CrawlerError {
7    inner: Box<ErrorKind>,
8}
9
10pub type CrawlerResult<T> = std::result::Result<T, CrawlerError>;
11
12/// The kind of the error.
13enum ErrorKind {
14    /// Expected array at `key` to contain a minimum number of elements.
15    ArraySize {
16        /// The target path (JSON pointer notation) that we tried to parse.
17        key: String,
18        /// The source json from Innertube that we were trying to parse.
19        // NOTE: API could theoretically produce multiple errors referring to the same source json.
20        // Hence reference counted, Arc particularly to ensure Error is thread safe.
21        json: Arc<String>,
22        /// The minimum number of expected elements.
23        min_elements: usize,
24    },
25    /// Expected the array at `key` to contain a `target_path`
26    PathNotFoundInArray {
27        /// The target path (JSON pointer notation) that we tried to parse.
28        key: String,
29        /// The source json from Innertube that we were trying to parse.
30        // NOTE: API could theoretically produce multiple errors referring to the same source json.
31        // Hence reference counted, Arc particularly to ensure Error is thread safe.
32        json: Arc<String>,
33        /// The path (JSON pointer notation) we tried to find in the elements of
34        /// the array.
35        target_path: String,
36    },
37    /// Expected `key` to contain at least one of `target_paths`
38    PathsNotFound {
39        /// The path (JSON pointer notation) that we tried to parse.
40        key: String,
41        /// The source json from Innertube that we were trying to parse.
42        // NOTE: API could theoretically produce multiple errors referring to the same source json.
43        // Hence reference counted, Arc particularly to ensure Error is thread safe.
44        json: Arc<String>,
45        /// The paths (JSON pointer notation) we tried to find.
46        target_paths: Vec<String>,
47    },
48    // TODO: Consider adding query type to error.
49    /// Field of the JSON file was not in the expected format (e.g expected an
50    /// array).
51    Parsing {
52        /// The target path (JSON pointer notation) that we tried to parse.
53        key: String,
54        /// The source json from Innertube that we were trying to parse.
55        // NOTE: API could theoretically produce multiple errors referring to the same source json.
56        // Hence reference counted, Arc particularly to ensure Error is thread safe.
57        json: Arc<String>,
58        /// The format we were trying to parse into.
59        target: ParseTarget,
60        /// The message we received from the parser, if any.
61        //TODO: Include in ParseTarget.
62        message: Option<String>,
63    },
64    /// Expected key did not occur in the JSON file.
65    Navigation {
66        /// The target path (JSON pointer notation) that we tried to parse.
67        key: String,
68        /// The source json from Innertube.
69        // NOTE: API could theoretically produce multiple errors referring to the same source json.
70        // Hence reference counted, Arc particularly to ensure Error is thread safe.
71        json: Arc<String>,
72    },
73    /// Tried multiple ways to pass, and each one returned an error.
74    MultipleParseError {
75        key: String,
76        json: Arc<String>,
77        messages: Vec<String>,
78    },
79}
80
81/// The type we were attempting to pass from the Json.
82#[derive(Debug, Clone)]
83pub(crate) enum ParseTarget {
84    Array,
85    Other(String),
86}
87
88impl std::fmt::Display for ParseTarget {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        match self {
91            ParseTarget::Array => write!(f, "Array"),
92            ParseTarget::Other(t) => write!(f, "{t}"),
93        }
94    }
95}
96
97impl std::error::Error for CrawlerError {}
98
99impl CrawlerError {
100    /// Return the source Json and key at the location of the error.
101    pub fn get_json_and_key(&self) -> (String, &String) {
102        match self.inner.as_ref() {
103            ErrorKind::Navigation { json, key } => (json.to_string(), key),
104            ErrorKind::Parsing { json, key, .. } => (json.to_string(), key),
105            ErrorKind::PathNotFoundInArray { key, json, .. } => (json.to_string(), key),
106            ErrorKind::PathsNotFound { key, json, .. } => (json.to_string(), key),
107            ErrorKind::ArraySize { key, json, .. } => (json.to_string(), key),
108            ErrorKind::MultipleParseError { key, json, .. } => (json.to_string(), key),
109        }
110    }
111    pub(crate) fn multiple_parse_error(
112        key: impl Into<String>,
113        json: Arc<String>,
114        errors: Vec<CrawlerError>,
115    ) -> Self {
116        let messages = errors.into_iter().map(|e| format!("{e}")).collect();
117        Self {
118            inner: Box::new(ErrorKind::MultipleParseError {
119                key: key.into(),
120                json,
121                messages,
122            }),
123        }
124    }
125    pub(crate) fn navigation(key: impl Into<String>, json: Arc<String>) -> Self {
126        Self {
127            inner: Box::new(ErrorKind::Navigation {
128                key: key.into(),
129                json,
130            }),
131        }
132    }
133    pub(crate) fn array_size(
134        key: impl Into<String>,
135        json: Arc<String>,
136        min_elements: usize,
137    ) -> Self {
138        let key = key.into();
139        Self {
140            inner: Box::new(ErrorKind::ArraySize {
141                key,
142                json,
143                min_elements,
144            }),
145        }
146    }
147    pub(crate) fn path_not_found_in_array(
148        key: impl Into<String>,
149        json: Arc<String>,
150        target_path: impl Into<String>,
151    ) -> Self {
152        let key = key.into();
153        let target_path = target_path.into();
154        Self {
155            inner: Box::new(ErrorKind::PathNotFoundInArray {
156                key,
157                json,
158                target_path,
159            }),
160        }
161    }
162    pub(crate) fn paths_not_found(
163        key: impl Into<String>,
164        json: Arc<String>,
165        target_paths: Vec<String>,
166    ) -> Self {
167        let key = key.into();
168        Self {
169            inner: Box::new(ErrorKind::PathsNotFound {
170                key,
171                json,
172                target_paths,
173            }),
174        }
175    }
176    pub(crate) fn parsing<S: Into<String>>(
177        key: S,
178        json: Arc<String>,
179        target: ParseTarget,
180        message: Option<String>,
181    ) -> Self {
182        Self {
183            inner: Box::new(ErrorKind::Parsing {
184                key: key.into(),
185                json,
186                target,
187                message,
188            }),
189        }
190    }
191}
192impl Display for ErrorKind {
193    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194        match self {
195            ErrorKind::PathsNotFound {
196                key, target_paths, ..
197            } => write!(
198                f,
199                "Expected {key} to contain one of the following paths: {:?}",
200                target_paths
201            ),
202            ErrorKind::PathNotFoundInArray {
203                key, target_path, ..
204            } => write!(f, "Expected {key} to contain a {target_path}"),
205            ErrorKind::Navigation { key, json: _ } => {
206                write!(f, "Key {key} not found in Api response.")
207            }
208            ErrorKind::ArraySize {
209                key,
210                json: _,
211                min_elements,
212            } => {
213                write!(
214                    f,
215                    "Expected {key} to contain at least {min_elements} elements."
216                )
217            }
218            ErrorKind::Parsing {
219                key,
220                json: _,
221                target,
222                message,
223            } => write!(
224                f,
225                "Error {}. Unable to parse into {target} at {key}",
226                message.as_deref().unwrap_or_default()
227            ),
228            ErrorKind::MultipleParseError { key, json: _, messages } => write!(f,"Expected one of the parsing functions at {key} to succeed, but all failed with the following errors: {:?}",messages),
229        }
230    }
231}
232
233// As this is displayed when unwrapping, we don't want to end up including the
234// entire format of this struct (potentially including entire source json file).
235impl Debug for CrawlerError {
236    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237        // TODO: Consider customising.
238        Display::fmt(&*self.inner, f)
239    }
240}
241impl Display for CrawlerError {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        Display::fmt(&*self.inner, f)
244    }
245}