1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Error type definitions.

use serde::Deserialize;
use std::fmt;

use crate::limits::Limits;

/// A `Result` alias where the `Err` case is `axiom::Error`.
pub type Result<T> = std::result::Result<T, Error>;

/// The error type for the Axiom client.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
    #[error("Invalid time order")]
    /// Invalid time
    InvalidTimeOrder,
    #[error("Empty update")]
    /// Empty update.
    EmptyUpdate,
    #[error("Empty datasets")]
    /// Empty datasets.
    EmptyDatasets,
    #[error("Empty type")]
    /// Empty type.
    EmptyType,
    #[error("Missing token")]
    /// Missing token.
    MissingToken,
    #[error("Missing Org ID for Personal Access Token")]
    /// Missing Org ID for Personal Access Token.
    MissingOrgId,
    #[error("Invalid token (make sure there are no invalid characters)")]
    /// Invalid token.
    InvalidToken,
    #[error("Invalid Org ID (make sure there are no invalid characters)")]
    /// Invalid Org ID.
    InvalidOrgId,
    #[error("Failed to setup HTTP client: {0}")]
    /// Failed to setup HTTP client.
    HttpClientSetup(reqwest::Error),
    #[error("Failed to deserialize response: {0}")]
    /// Failed to deserialize response.
    Deserialize(reqwest::Error),
    #[error("Http error: {0}")]
    /// HTTP error.
    Http(reqwest::Error),
    #[error(transparent)]
    /// Axion API error.
    Axiom(Axiom),
    #[error("Query ID contains invisible characters (this is a server error)")]
    /// Query ID contains invisible characters (this is a server error).
    InvalidQueryId,
    #[error(transparent)]
    /// Invalid Query Parameters.
    InvalidParams(#[from] serde_qs::Error),
    #[error(transparent)]
    /// Invalid JSON.
    Serialize(#[from] serde_json::Error),
    #[error("Failed to encode payload: {0}")]
    /// Failed to encode payload.
    Encoding(std::io::Error),
    #[error("Duration is out of range (can't be larger than i64::MAX milliseconds)")]
    /// Duration is out of range (can't be larger than `i64::MAX` milliseconds).
    DurationOutOfRange,
    #[cfg(feature = "tokio")]
    #[error("Failed to join thread: {0}")]
    /// Failed to join thread.
    JoinError(tokio::task::JoinError),
    #[error("Rate limit exceeded for the {scope} scope: {limits}")]
    /// Rate limit exceeded.
    RateLimitExceeded {
        /// The scope of the rate limit.
        scope: String,
        /// The rate limit.
        limits: Limits,
    },
    #[error("Query limit exceeded: {0}")]
    /// Query limit exceeded.
    QueryLimitExceeded(Limits),
    #[error("Ingest limit exceeded: {0}")]
    /// Ingest limit exceeded.
    IngestLimitExceeded(Limits),
    #[error("Invalid URL: {0}")]
    /// Invalid URL.
    InvalidUrl(url::ParseError),
    #[error("Error in ingest stream: {0}")]
    /// Error in ingest stream.
    IngestStreamError(Box<dyn std::error::Error + Send + Sync>),
    #[error("Invalid content type: {0}")]
    /// Invalid content type.
    InvalidContentType(String),
    #[error("Invalid content encoding: {0}")]
    /// Invalid content encoding.
    InvalidContentEncoding(String),
}

/// This is the manual implementation. We don't really care if the error is
/// permanent or transient at this stage so we just return `Error::Http`.
impl From<backoff::Error<reqwest::Error>> for Error {
    fn from(err: backoff::Error<reqwest::Error>) -> Self {
        match err {
            backoff::Error::Permanent(err)
            | backoff::Error::Transient {
                err,
                retry_after: _,
            } => Error::Http(err),
        }
    }
}

/// An error returned by the Axiom API.
#[derive(Deserialize, Debug)]
pub struct Axiom {
    #[serde(skip)]
    /// The HTTP status code.
    pub status: u16,
    #[serde(skip)]
    /// The HTTP method.
    pub method: http::Method,
    #[serde(skip)]
    /// The path that was requested.
    pub path: String,
    /// The error message.
    pub message: Option<String>,
}

impl Axiom {
    pub(crate) fn new(
        status: u16,
        method: http::Method,
        path: String,
        message: Option<String>,
    ) -> Self {
        Self {
            status,
            method,
            path,
            message,
        }
    }
}

impl std::error::Error for Axiom {}

impl fmt::Display for Axiom {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if let Some(msg) = self.message.as_ref() {
            write!(
                f,
                "Received {} on {} {}: {}",
                self.status, self.method, self.path, msg
            )
        } else {
            write!(
                f,
                "Received {} on {} {})",
                self.method, self.path, self.status
            )
        }
    }
}