1use thiserror::Error;
2
3pub type Result<T> = std::result::Result<T, Error>;
4
5#[non_exhaustive]
6#[derive(Debug, Error)]
7pub enum Error {
8 #[cfg(feature = "reqwest")]
11 #[error("http transport error: {0}")]
12 Http(#[from] reqwest::Error),
13
14 #[error("matomo api error in {method}: {message}")]
16 Api {
17 message: String,
18 method: &'static str,
19 kind: ApiErrorKind,
20 },
21
22 #[error("failed to decode {method} response: {source}")]
24 Decode {
25 #[source]
26 source: serde_json::Error,
27 method: &'static str,
28 },
29
30 #[error("non-json body from {method}: {body}")]
32 NonJsonBody { method: &'static str, body: String },
33
34 #[error("configuration error: {0}")]
36 Config(String),
37
38 #[error("preflight failed: {0}")]
40 Preflight(String),
41}
42
43#[non_exhaustive]
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum ApiErrorKind {
48 Auth,
49 UnknownMethod,
50 InvalidParam,
51 RateLimited,
52 NoData,
53 Other,
54}
55
56impl ApiErrorKind {
57 pub(crate) fn classify(message: &str) -> Self {
58 let m = message.to_ascii_lowercase();
59 if m.contains("token_auth")
60 || m.contains("authentication")
61 || m.contains("not allowed")
62 || m.contains("permission")
63 {
64 ApiErrorKind::Auth
65 } else if m.contains("method") && (m.contains("not exist") || m.contains("not found")) {
66 ApiErrorKind::UnknownMethod
67 } else if m.contains("requires") || m.contains("parameter") || m.contains("invalid") {
68 ApiErrorKind::InvalidParam
69 } else if m.contains("rate") && m.contains("limit") {
70 ApiErrorKind::RateLimited
71 } else if m.contains("no data") {
72 ApiErrorKind::NoData
73 } else {
74 ApiErrorKind::Other
75 }
76 }
77}