Skip to main content

couchbase_core/analyticsx/
error.rs

1/*
2 *
3 *  * Copyright (c) 2025 Couchbase, Inc.
4 *  *
5 *  * Licensed under the Apache License, Version 2.0 (the "License");
6 *  * you may not use this file except in compliance with the License.
7 *  * You may obtain a copy of the License at
8 *  *
9 *  *    http://www.apache.org/licenses/LICENSE-2.0
10 *  *
11 *  * Unless required by applicable law or agreed to in writing, software
12 *  * distributed under the License is distributed on an "AS IS" BASIS,
13 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  * See the License for the specific language governing permissions and
15 *  * limitations under the License.
16 *
17 */
18
19use crate::httpx;
20use crate::tracingcomponent::MetricsName;
21use http::StatusCode;
22use std::error::Error as StdError;
23use std::fmt::{Display, Formatter};
24
25pub type Result<T> = std::result::Result<T, Error>;
26
27#[derive(Debug, Clone, PartialEq)]
28pub struct Error {
29    inner: ErrorImpl,
30}
31
32impl Display for Error {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        write!(f, "{}", self.inner.kind)
35    }
36}
37
38impl StdError for Error {}
39
40impl Error {
41    pub(crate) fn new_server_error(e: ServerError) -> Error {
42        Self {
43            inner: ErrorImpl {
44                kind: Box::new(ErrorKind::Server(e)),
45            },
46        }
47    }
48
49    pub(crate) fn new_message_error(
50        msg: impl Into<String>,
51        endpoint: impl Into<Option<String>>,
52        statement: impl Into<Option<String>>,
53        client_context_id: impl Into<Option<String>>,
54    ) -> Error {
55        Self {
56            inner: ErrorImpl {
57                kind: Box::new(ErrorKind::Message {
58                    msg: msg.into(),
59                    endpoint: endpoint.into(),
60                    statement: statement.into(),
61                    client_context_id: client_context_id.into(),
62                }),
63            },
64        }
65    }
66
67    pub(crate) fn new_encoding_error(msg: impl Into<String>) -> Error {
68        Self {
69            inner: ErrorImpl {
70                kind: Box::new(ErrorKind::Encoding { msg: msg.into() }),
71            },
72        }
73    }
74
75    pub(crate) fn new_invalid_argument_error(
76        msg: impl Into<String>,
77        arg: impl Into<Option<String>>,
78    ) -> Self {
79        Self {
80            inner: ErrorImpl {
81                kind: Box::new(ErrorKind::InvalidArgument {
82                    msg: msg.into(),
83                    arg: arg.into(),
84                }),
85            },
86        }
87    }
88
89    pub(crate) fn new_http_error(
90        error: httpx::error::Error,
91        endpoint: impl Into<String>,
92        statement: impl Into<Option<String>>,
93        client_context_id: impl Into<Option<String>>,
94    ) -> Self {
95        Self {
96            inner: ErrorImpl {
97                kind: Box::new(ErrorKind::Http {
98                    error,
99                    endpoint: endpoint.into(),
100                    statement: statement.into(),
101                    client_context_id: client_context_id.into(),
102                }),
103            },
104        }
105    }
106
107    pub fn kind(&self) -> &ErrorKind {
108        &self.inner.kind
109    }
110}
111
112#[derive(Debug, Clone)]
113struct ErrorImpl {
114    kind: Box<ErrorKind>,
115}
116
117impl PartialEq for ErrorImpl {
118    fn eq(&self, other: &Self) -> bool {
119        self.kind == other.kind
120    }
121}
122
123#[derive(Clone, Debug, PartialEq, Eq)]
124#[non_exhaustive]
125pub enum ErrorKind {
126    Server(ServerError),
127    #[non_exhaustive]
128    Http {
129        error: httpx::error::Error,
130        endpoint: String,
131        statement: Option<String>,
132        client_context_id: Option<String>,
133    },
134    #[non_exhaustive]
135    Message {
136        msg: String,
137        endpoint: Option<String>,
138        statement: Option<String>,
139        client_context_id: Option<String>,
140    },
141    #[non_exhaustive]
142    InvalidArgument {
143        msg: String,
144        arg: Option<String>,
145    },
146    #[non_exhaustive]
147    Encoding {
148        msg: String,
149    },
150}
151
152impl Display for ErrorKind {
153    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154        match self {
155            ErrorKind::Server(e) => write!(f, "{e}"),
156            ErrorKind::InvalidArgument { msg, arg } => {
157                let base_msg = format!("invalid argument: {msg}");
158                if let Some(arg) = arg {
159                    write!(f, "{base_msg}, arg: {arg}")
160                } else {
161                    write!(f, "{base_msg}")
162                }
163            }
164            ErrorKind::Encoding { msg } => write!(f, "encoding error: {msg}"),
165            ErrorKind::Http {
166                error,
167                endpoint,
168                statement,
169                client_context_id,
170            } => {
171                write!(f, "http error {error}: endpoint: {endpoint}")?;
172                if let Some(statement) = statement {
173                    write!(f, ", statement: {statement}")?;
174                }
175                if let Some(client_context_id) = client_context_id {
176                    write!(f, ", client context id: {client_context_id}")?;
177                }
178                Ok(())
179            }
180            ErrorKind::Message {
181                msg,
182                endpoint,
183                statement,
184                client_context_id,
185            } => {
186                write!(f, "{msg}")?;
187                if let Some(endpoint) = endpoint {
188                    write!(f, ", endpoint: {endpoint}")?;
189                }
190                if let Some(statement) = statement {
191                    write!(f, ", statement: {statement}")?;
192                }
193                if let Some(client_context_id) = client_context_id {
194                    write!(f, ", client context id: {client_context_id}")?;
195                }
196                Ok(())
197            }
198        }
199    }
200}
201
202#[derive(Clone, Debug, PartialEq, Eq)]
203pub struct ServerError {
204    kind: ServerErrorKind,
205
206    endpoint: String,
207    status_code: StatusCode,
208    code: u32,
209    msg: String,
210
211    statement: Option<String>,
212    client_context_id: Option<String>,
213
214    all_error_descs: Vec<ErrorDesc>,
215}
216
217impl ServerError {
218    pub(crate) fn new(
219        kind: ServerErrorKind,
220        endpoint: impl Into<String>,
221        status_code: StatusCode,
222        code: u32,
223        msg: impl Into<String>,
224    ) -> Self {
225        Self {
226            kind,
227            endpoint: endpoint.into(),
228            status_code,
229            code,
230            msg: msg.into(),
231            statement: None,
232            client_context_id: None,
233            all_error_descs: vec![],
234        }
235    }
236
237    pub fn kind(&self) -> &ServerErrorKind {
238        &self.kind
239    }
240
241    pub fn endpoint(&self) -> &str {
242        &self.endpoint
243    }
244
245    pub fn statement(&self) -> Option<&str> {
246        self.statement.as_deref()
247    }
248
249    pub fn status_code(&self) -> StatusCode {
250        self.status_code
251    }
252
253    pub fn client_context_id(&self) -> Option<&str> {
254        self.client_context_id.as_deref()
255    }
256
257    pub fn code(&self) -> u32 {
258        self.code
259    }
260
261    pub fn msg(&self) -> &str {
262        &self.msg
263    }
264
265    pub fn all_error_descs(&self) -> &[ErrorDesc] {
266        &self.all_error_descs
267    }
268
269    pub(crate) fn with_statement(mut self, statement: impl Into<String>) -> Self {
270        self.statement = Some(statement.into());
271        self
272    }
273
274    pub(crate) fn with_client_context_id(mut self, client_context_id: impl Into<String>) -> Self {
275        self.client_context_id = Some(client_context_id.into());
276        self
277    }
278
279    pub(crate) fn with_error_descs(mut self, error_descs: Vec<ErrorDesc>) -> Self {
280        self.all_error_descs = error_descs;
281        self
282    }
283}
284
285impl Display for ServerError {
286    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
287        write!(
288            f,
289            "server error of kind: {} code: {}, msg: {}",
290            self.kind, self.code, self.msg
291        )?;
292
293        if let Some(client_context_id) = &self.client_context_id {
294            write!(f, ", client context id: {client_context_id}")?;
295        }
296        if let Some(statement) = &self.statement {
297            write!(f, ", statement: {statement}")?;
298        }
299
300        write!(
301            f,
302            ", endpoint: {},  status code: {}",
303            self.endpoint, self.status_code
304        )?;
305
306        if !self.all_error_descs.is_empty() {
307            write!(f, ", all error descriptions: {:?}", self.all_error_descs)?;
308        }
309
310        Ok(())
311    }
312}
313
314#[derive(Clone, Debug, PartialEq, Eq)]
315#[non_exhaustive]
316pub struct ErrorDesc {
317    kind: ServerErrorKind,
318
319    code: u32,
320    message: String,
321}
322
323impl ErrorDesc {
324    pub fn new(kind: ServerErrorKind, code: u32, message: String) -> Self {
325        Self {
326            kind,
327            code,
328            message,
329        }
330    }
331
332    pub fn kind(&self) -> &ServerErrorKind {
333        &self.kind
334    }
335
336    pub fn code(&self) -> u32 {
337        self.code
338    }
339
340    pub fn message(&self) -> &str {
341        &self.message
342    }
343}
344
345impl Display for ErrorDesc {
346    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
347        write!(
348            f,
349            "error description of kind: {}, code: {}, message: {}",
350            self.kind, self.code, self.message
351        )
352    }
353}
354
355#[derive(Clone, Debug, PartialEq, Eq)]
356#[non_exhaustive]
357pub enum ServerErrorKind {
358    CompilationFailure,
359    Internal,
360    AuthenticationFailure,
361    ParsingFailure,
362    ScanWaitTimeout,
363    InvalidArgument { argument: String, reason: String },
364    TemporaryFailure,
365    JobQueueFull,
366    IndexNotFound,
367    IndexExists,
368    DatasetNotFound,
369    DatasetExists,
370    DataverseNotFound,
371    DataverseExists,
372    LinkNotFound,
373    LinkExists,
374    Unknown,
375}
376
377impl Display for ServerErrorKind {
378    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
379        match self {
380            ServerErrorKind::Internal => write!(f, "internal server error"),
381            ServerErrorKind::AuthenticationFailure => write!(f, "authentication failure"),
382            ServerErrorKind::InvalidArgument { argument, reason } => write!(
383                f,
384                "server invalid argument: (argument: {argument}, reason: {reason})"
385            ),
386            ServerErrorKind::Unknown => write!(f, "unknown query error"),
387            ServerErrorKind::CompilationFailure => write!(f, "compilation failure"),
388            ServerErrorKind::ParsingFailure => write!(f, "parsing failure"),
389            ServerErrorKind::ScanWaitTimeout => write!(f, "scan wait timeout"),
390            ServerErrorKind::TemporaryFailure => write!(f, "temporary failure"),
391            ServerErrorKind::DatasetExists => write!(f, "dataset exists"),
392            ServerErrorKind::DatasetNotFound => write!(f, "dataset not found"),
393            ServerErrorKind::DataverseExists => write!(f, "dataverse exists"),
394            ServerErrorKind::DataverseNotFound => write!(f, "dataverse not found"),
395            ServerErrorKind::IndexExists => write!(f, "index exists"),
396            ServerErrorKind::IndexNotFound => write!(f, "index not found"),
397            ServerErrorKind::LinkExists => write!(f, "link exists"),
398            ServerErrorKind::LinkNotFound => write!(f, "link not found"),
399            ServerErrorKind::JobQueueFull => write!(f, "job queue full"),
400        }
401    }
402}
403
404impl MetricsName for Error {
405    fn metrics_name(&self) -> &'static str {
406        match self.kind() {
407            ErrorKind::Server(e) => e.kind().metrics_name(),
408            ErrorKind::Http { error, .. } => error.metrics_name(),
409            ErrorKind::Message { .. } => "analyticsx._OTHER",
410            ErrorKind::InvalidArgument { .. } => "analyticsx.InvalidArgument",
411            ErrorKind::Encoding { .. } => "analyticsx.Encoding",
412        }
413    }
414}
415
416impl MetricsName for ServerErrorKind {
417    fn metrics_name(&self) -> &'static str {
418        match self {
419            ServerErrorKind::CompilationFailure => "analyticsx.CompilationFailure",
420            ServerErrorKind::Internal => "analyticsx.Internal",
421            ServerErrorKind::AuthenticationFailure => "analyticsx.AuthenticationFailure",
422            ServerErrorKind::ParsingFailure => "analyticsx.ParsingFailure",
423            ServerErrorKind::ScanWaitTimeout => "analyticsx.ScanWaitTimeout",
424            ServerErrorKind::InvalidArgument { .. } => "analyticsx.InvalidArgument",
425            ServerErrorKind::TemporaryFailure => "analyticsx.TemporaryFailure",
426            ServerErrorKind::JobQueueFull => "analyticsx.JobQueueFull",
427            ServerErrorKind::IndexNotFound => "analyticsx.IndexNotFound",
428            ServerErrorKind::IndexExists => "analyticsx.IndexExists",
429            ServerErrorKind::DatasetNotFound => "analyticsx.DatasetNotFound",
430            ServerErrorKind::DatasetExists => "analyticsx.DatasetExists",
431            ServerErrorKind::DataverseNotFound => "analyticsx.DataverseNotFound",
432            ServerErrorKind::DataverseExists => "analyticsx.DataverseExists",
433            ServerErrorKind::LinkNotFound => "analyticsx.LinkNotFound",
434            ServerErrorKind::LinkExists => "analyticsx.LinkExists",
435            ServerErrorKind::Unknown => "analyticsx._OTHER",
436        }
437    }
438}