Skip to main content

hook0_client/generated/
errors.rs

1// Generated by hook0-sdkgen from the OpenAPI snapshot the API crate commits.
2// Do not edit by hand: run `UPDATE_SDK=rust cargo test -p hook0-sdkgen sdk_targets` and commit the result.
3//! The failures the API reports: the document it describes one with, and the one error
4//! every generated method answers instead of what it was asked for.
5
6use super::models::Problem;
7use super::models::ProblemId;
8
9/// Lowest status the API answers a success under.
10const LOWEST_SUCCESS: u16 = 200;
11
12/// Lowest status that is no longer a success.
13const LOWEST_REDIRECTION: u16 = 300;
14
15/// Longest fragment of an answer a message carries.
16///
17/// Bodies are written by a server this crate does not control, so they are cut at a fixed
18/// budget rather than echoed whole into whatever the caller logs.
19const MAX_PREVIEW_BYTES: usize = 256;
20
21/// What the API answered when it did not answer a success.
22///
23/// Every problem the document names is one of these, told apart by the kind it carries;
24/// the document the API sent, when it sent one this crate can read, is beside it. A body
25/// naming no problem still reaches a caller as one of these, carrying no kind.
26#[derive(Debug, Clone)]
27pub struct ProblemError {
28    /// Status the API answered under.
29    pub status: u16,
30    /// Problem the API named, absent when the body named none.
31    pub kind: Option<ProblemId>,
32    /// Document the API answered, absent when it answered none this crate can read.
33    pub problem: Option<Problem>,
34    /// What to say about the failure, as much of the API's answer as fits included.
35    pub detail: String,
36}
37
38impl std::fmt::Display for ProblemError {
39    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        formatter.write_str(&self.detail)
41    }
42}
43
44impl std::error::Error for ProblemError {}
45
46/// Everything a generated method answers instead of what it was asked for.
47#[derive(Debug)]
48pub enum RequestError {
49    /// The request never reached the API, or its answer never came back.
50    Transport(Box<dyn std::error::Error + Send + Sync>),
51    /// The API answered a failure, and described it.
52    ///
53    /// Held behind a pointer, so that every method answers a small error whatever the
54    /// document declares a failure body to carry.
55    Api(Box<ProblemError>),
56    /// The API answered something this crate cannot read.
57    Unreadable {
58        /// Status the API answered under.
59        status: u16,
60        /// What was answered, as much of it as fits included.
61        detail: String,
62    },
63    /// The body of the request could not be written.
64    Unwritable {
65        /// Why it could not be written.
66        detail: String,
67    },
68}
69
70impl RequestError {
71    /// Reports that the request never reached the API.
72    pub fn transport<E>(cause: E) -> Self
73    where
74        E: std::error::Error + Send + Sync + 'static,
75    {
76        Self::Transport(Box::new(cause))
77    }
78
79    /// Reports a request body this crate could not write.
80    pub fn unwritable(cause: serde_json::Error) -> Self {
81        Self::Unwritable {
82            detail: cause.to_string(),
83        }
84    }
85
86    /// Reports an answer this crate could not read.
87    pub fn unreadable(status: u16, payload: &[u8], cause: &serde_json::Error) -> Self {
88        let seen = preview(payload);
89        let unread = format!("the API answered {status} with an unreadable body");
90        let detail = format!("{unread}: {cause} ({seen})");
91        Self::Unreadable { status, detail }
92    }
93}
94
95impl std::fmt::Display for RequestError {
96    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        match self {
98            Self::Transport(cause) => write!(formatter, "the API was not reached: {cause}"),
99            Self::Api(failure) => failure.fmt(formatter),
100            Self::Unreadable { detail, .. } => formatter.write_str(detail),
101            Self::Unwritable { detail } => formatter.write_str(detail),
102        }
103    }
104}
105
106impl std::error::Error for RequestError {}
107
108/// The failure the API reported, and nothing at all when what it answered was a success.
109pub fn problem_for(status: u16, payload: &[u8]) -> Option<ProblemError> {
110    if (LOWEST_SUCCESS..LOWEST_REDIRECTION).contains(&status) {
111        return None;
112    }
113
114    let problem: Option<Problem> = serde_json::from_slice(payload).ok();
115    let seen = preview(payload);
116    let kind = problem.as_ref().map(|problem| problem.id);
117
118    Some(ProblemError {
119        status,
120        kind,
121        problem,
122        detail: format!("the API answered {status}: {seen}"),
123    })
124}
125
126/// As much of an answer as a message may carry, with whatever was not text left out.
127fn preview(payload: &[u8]) -> String {
128    let text = String::from_utf8_lossy(payload);
129    let mut kept = String::with_capacity(MAX_PREVIEW_BYTES);
130
131    for character in text.chars() {
132        if kept.len() + character.len_utf8() > MAX_PREVIEW_BYTES {
133            kept.push('…');
134            break;
135        }
136        kept.push(character);
137    }
138
139    kept
140}