cognite/
error.rs

1use crate::{AuthenticatorError, FromErrorDetail};
2use reqwest::header::InvalidHeaderValue;
3use reqwest::StatusCode;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6use std::fmt;
7use thiserror::Error;
8
9#[derive(Serialize, Deserialize, Debug)]
10#[serde(rename_all = "camelCase")]
11/// Wrapper around an error from CDF.
12pub struct ApiErrorWrapper {
13    /// Error message from CDF API.
14    pub error: ApiErrorMessage,
15}
16
17#[derive(Serialize, Deserialize, Debug)]
18#[serde(untagged)]
19/// Value that is either an integer or a string.
20pub enum IntegerStringOrObject {
21    /// 64 bit integer.
22    Integer(i64),
23    /// String.
24    String(String),
25    /// Object.
26    Object(HashMap<String, Box<IntegerStringOrObject>>),
27}
28
29impl IntegerStringOrObject {
30    /// Return self as integer, or none.
31    pub fn integer(&self) -> Option<i64> {
32        match self {
33            Self::Integer(i) => Some(*i),
34            Self::String(s) => s.parse().ok(),
35            _ => None,
36        }
37    }
38    /// Return self as string, or none.
39    pub fn string(&self) -> Option<&String> {
40        match self {
41            Self::Integer(_) => None,
42            Self::String(s) => Some(s),
43            _ => None,
44        }
45    }
46    /// Return self as object, or none.
47    pub fn object(&self) -> Option<&HashMap<String, Box<Self>>> {
48        match self {
49            Self::Object(o) => Some(o),
50            _ => None,
51        }
52    }
53}
54
55#[derive(Serialize, Deserialize, Debug)]
56#[serde(rename_all = "camelCase")]
57/// Details about API errors.
58pub struct ApiErrorDetail(pub Vec<HashMap<String, Box<IntegerStringOrObject>>>);
59
60#[derive(Serialize, Deserialize, Debug)]
61#[serde(rename_all = "camelCase")]
62/// CDF error message.
63pub struct ApiErrorMessage {
64    /// HTTP error code.
65    pub code: u32,
66    /// Description of the error.
67    pub message: String,
68    /// List of missing items.
69    pub missing: Option<ApiErrorDetail>,
70    /// List of duplicated items.
71    pub duplicated: Option<ApiErrorDetail>,
72}
73
74impl ApiErrorDetail {
75    /// Get values of type `T` form self.
76    pub fn get_values<T: FromErrorDetail>(&self) -> impl Iterator<Item = T> + '_ {
77        self.iter().filter_map(|m| T::from_detail(m))
78    }
79
80    /// Get a value from `map` as integer.
81    pub fn get_integer(
82        map: &HashMap<String, Box<IntegerStringOrObject>>,
83        key: &str,
84    ) -> Option<i64> {
85        map.get(key).and_then(|f| f.integer())
86    }
87    /// Get a value from `map` as string.
88    pub fn get_string<'a>(
89        map: &'a HashMap<String, Box<IntegerStringOrObject>>,
90        key: &str,
91    ) -> Option<&'a String> {
92        map.get(key).and_then(|f| f.string())
93    }
94
95    /// Get an value from `map` as a nested object.
96    pub fn get_object<'a>(
97        map: &'a HashMap<String, Box<IntegerStringOrObject>>,
98        key: &str,
99    ) -> Option<&'a HashMap<String, Box<IntegerStringOrObject>>> {
100        map.get(key).and_then(|f| f.object())
101    }
102
103    /// Iterate over elements.
104    pub fn iter(&self) -> impl Iterator<Item = &HashMap<String, Box<IntegerStringOrObject>>> + '_ {
105        self.0.iter()
106    }
107}
108
109#[derive(Debug, Default)]
110/// An Error from the CDF API.
111pub struct CdfApiError {
112    /// HTTP status code.
113    pub code: u32,
114    /// Error description.
115    pub message: String,
116    /// List of missing items.
117    pub missing: Option<ApiErrorDetail>,
118    /// List of duplicated items.
119    pub duplicated: Option<ApiErrorDetail>,
120    /// Request ID, if available.
121    pub request_id: Option<String>,
122}
123
124impl fmt::Display for CdfApiError {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        write!(
127            f,
128            "{}: {}. RequestId: {}",
129            &self.code,
130            &self.message,
131            self.request_id.as_deref().unwrap_or("")
132        )
133    }
134}
135
136impl CdfApiError {
137    pub(crate) fn new(raw: ApiErrorMessage, request_id: Option<String>) -> Self {
138        CdfApiError {
139            code: raw.code,
140            message: raw.message,
141            missing: raw.missing,
142            duplicated: raw.duplicated,
143            request_id,
144        }
145    }
146}
147
148impl Error {
149    fn new_from_code(code: StatusCode, err: CdfApiError) -> Error {
150        match code {
151            StatusCode::BAD_REQUEST => Error::BadRequest(err),
152            StatusCode::UNAUTHORIZED => Error::Unauthorized(err),
153            StatusCode::FORBIDDEN => Error::Forbidden(err),
154            StatusCode::NOT_FOUND => Error::NotFound(err),
155            StatusCode::CONFLICT => Error::Conflict(err),
156            StatusCode::UNPROCESSABLE_ENTITY => Error::UnprocessableEntity(err),
157            _ => Error::OtherApiError(err),
158        }
159    }
160
161    pub(crate) fn new_from_cdf(
162        code: StatusCode,
163        err: ApiErrorWrapper,
164        request_id: Option<String>,
165    ) -> Error {
166        let cdf_err = CdfApiError::new(err.error, request_id);
167        Self::new_from_code(code, cdf_err)
168    }
169
170    pub(crate) fn new_without_json(
171        code: StatusCode,
172        err: String,
173        request_id: Option<String>,
174    ) -> Error {
175        let err = CdfApiError {
176            code: code.to_owned().as_u16().into(),
177            message: err,
178            request_id,
179            ..Default::default()
180        };
181        Self::new_from_code(code, err)
182    }
183}
184
185/// A `Result` alias where the `Err` case is `cognite::Error`.
186pub type Result<T> = ::std::result::Result<T, Error>;
187
188#[derive(Debug, Error)]
189/// Cognite SDK Error.
190pub enum Error {
191    #[error("Bad request (400): {0}")]
192    /// A bad request error (400)
193    BadRequest(CdfApiError),
194    #[error("Unauthorized (401): {0}")]
195    /// An unauthorized (401) error. When received from CDF this typically means
196    /// that the token is not valid for any project in the tenant.
197    Unauthorized(CdfApiError),
198    #[error("Forbidden (403): {0}")]
199    /// A forbidden (403) error. When received from CDF this typically means that
200    /// the user lacks access to the requested resource.
201    Forbidden(CdfApiError),
202    #[error("Not Found (404): {0}")]
203    /// A not found (404) error.
204    NotFound(CdfApiError),
205    #[error("Conflict (409): {0}")]
206    /// A conflict (409) error.
207    Conflict(CdfApiError),
208    #[error("Unprocessable Entity (422): {0}")]
209    /// An unprocessable entity (422) error.
210    UnprocessableEntity(CdfApiError),
211    #[error("Other Api Error: {0}")]
212    /// A different CDF error not covered by the common variants in this enum.
213    OtherApiError(CdfApiError),
214    #[error("Environment Variable Missing: {0}")]
215    /// Error returned due to a missing environment variable
216    EnvironmentVariableMissing(String),
217    #[error("Error from authenticator: {0}")]
218    /// Error from the authenticator.
219    Authenticator(AuthenticatorError),
220    #[error("Invalid header value: {0}")]
221    /// Error caused by an invalid header, for example one containing non-ascii symbols.
222    InvalidHeader(#[from] InvalidHeaderValue),
223    #[error("Error accessing file: {0}")]
224    /// Error reading from a file.
225    IOError(#[from] std::io::Error),
226    #[error("Error collecting stream: {0:#}")]
227    /// Error collecting a stream.
228    StreamError(anyhow::Error),
229    #[error("Error in middleware: {0:#}")]
230    /// Error in middleware.
231    Middleware(anyhow::Error),
232    #[error("Error in configuration: {0}")]
233    /// Error in configuration.
234    Config(String),
235    #[error("Unexpected request error: {0}")]
236    /// Reqwest error
237    Reqwest(#[from] reqwest::Error),
238    /// Serde JSON error.
239    #[error("Unexpected JSON error: {0}")]
240    /// Serde JSON error.
241    SerdeJson(#[from] ::serde_json::Error),
242    #[error("Unexpected protobuf error: {0}")]
243    /// Prost (protobuf deserializer) error
244    Prost(#[from] ::prost::DecodeError),
245    #[error("{0}")]
246    /// Something else went wrong.
247    Other(String),
248}
249
250impl From<reqwest_middleware::Error> for Error {
251    fn from(err: reqwest_middleware::Error) -> Self {
252        match err {
253            reqwest_middleware::Error::Middleware(x) => Error::Middleware(x),
254            reqwest_middleware::Error::Reqwest(x) => Self::from(x),
255        }
256    }
257}