1use std::{cmp::min, fmt};
2
3#[derive(Debug, thiserror::Error)]
4pub enum Error {
5 #[error("Http error: {0}")]
6 Http(#[from] http::Error),
7
8 #[error("Network error: {0}")]
9 Net(#[from] hyper::Error),
10
11 #[error("Invalid body: {0}")]
12 InvalidBody(String),
13
14 #[error("Error serializing request payload: {0}")]
15 Serialization(serde_json::Error),
16
17 #[error("Error deserializing response: {error}\n{extract}")]
18 Deserialization {
19 error: serde_json::Error,
20 extract: ErrorExtract,
21 },
22
23 #[error("Received a non 2xx status code: StatusCode: {received_status}, body: {body}")]
24 Non2xxResponse {
25 received_status: http::StatusCode,
26 body: String,
27 },
28}
29
30#[derive(Debug)]
31pub enum ErrorExtract {
32 Failed,
33 Extracted { extract: String, pointer_idx: usize },
34}
35
36impl fmt::Display for ErrorExtract {
37 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 if let Self::Extracted {
39 extract,
40 pointer_idx,
41 } = self
42 {
43 writeln!(f, "...{}...", extract)?;
44
45 for _ in 0..pointer_idx + 3 {
46 f.write_str(" ")?;
47 }
48 f.write_str("^")?;
49 }
50
51 Ok(())
52 }
53}
54
55impl Error {
56 pub fn invalid_body(s: impl Into<String>) -> Self {
57 Self::InvalidBody(s.into())
58 }
59
60 pub fn deserialization(error: serde_json::Error, body: &[u8]) -> Self {
61 let column = error.column();
63 let extract = body
64 .split(|b| b == &b'\n')
65 .nth(error.line() - 1) .map_or_else(
67 || ErrorExtract::Failed,
68 |line| {
69 const GRASP: usize = 800;
70
71 let pointer_idx = min(column, GRASP);
72 let start = if GRASP < column {
73 column - GRASP
74 } else {
75 column
76 };
77 let snippet_bs = &line[start..min(column + GRASP, line.len())];
78 ErrorExtract::Extracted {
79 extract: String::from_utf8_lossy(snippet_bs).into_owned(),
80 pointer_idx,
81 }
82 },
83 );
84
85 Self::Deserialization { error, extract }
86 }
87
88 pub fn non_2xx(received_status: http::StatusCode, body: &[u8]) -> Self {
89 Self::Non2xxResponse {
90 received_status,
91 body: Self::sanitize_body(body),
92 }
93 }
94
95 fn sanitize_body(body: &[u8]) -> String {
96 let mut res = body
97 .chunks(128)
98 .next()
99 .map(|bs| String::from_utf8_lossy(bs).to_string())
100 .unwrap_or_else(|| "".to_owned());
101
102 res.push_str("...");
103 res
104 }
105}