json_crawler/
error.rs

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