Skip to main content

mongodb/
error.rs

1//! Contains the `Error` and `Result` types that `mongodb` uses.
2
3pub(crate) mod bulk_write;
4
5use std::{
6    any::Any,
7    collections::{HashMap, HashSet},
8    fmt::{self, Debug},
9    sync::Arc,
10};
11
12use serde::{Deserialize, Serialize};
13use thiserror::Error;
14
15use crate::{
16    bson::{doc, rawdoc, Bson, Document, RawDocumentBuf},
17    cmap::RawCommandResponse,
18    options::ServerAddress,
19    sdam::{ServerType, TopologyVersion},
20};
21
22pub use bulk_write::{BulkWriteError, PartialBulkWriteResult};
23
24/// Codes indicating the node is in a recovering state (per the SDAM spec).
25/// SHUTTING_DOWN_CODES is a strict subset of these.
26pub(crate) const RECOVERING_CODES: [i32; 5] = [11600, 11602, 13436, 189, 91];
27/// Codes indicating the node is not a writable primary (per the SDAM spec).
28pub(crate) const NOTWRITABLEPRIMARY_CODES: [i32; 3] = [10107, 13435, 10058];
29/// Codes indicating the node is shutting down. Strict subset of RECOVERING_CODES;
30/// both sets must be updated together if new codes are added.
31pub(crate) const SHUTTING_DOWN_CODES: [i32; 2] = [11600, 91];
32/// Codes that make a read operation retryable (wire version <= 8).
33/// Superset of RETRYABLE_WRITE_CODES — includes code 134 (ReadConcernMajorityNotAvailableYet).
34pub(crate) const RETRYABLE_READ_CODES: [i32; 13] = [
35    11600, 11602, 10107, 13435, 13436, 189, 91, 7, 6, 89, 9001, 134, 262,
36];
37/// Codes that make a write operation retryable (wire version <= 8).
38/// Subset of RETRYABLE_READ_CODES.
39pub(crate) const RETRYABLE_WRITE_CODES: [i32; 12] = [
40    11600, 11602, 10107, 13435, 13436, 189, 91, 7, 6, 89, 9001, 262,
41];
42const UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL_CODES: [i32; 3] = [50, 64, 91];
43const REAUTHENTICATION_REQUIRED_CODE: i32 = 391;
44/// Code 43 is CursorNotFound, which is always resumable regardless of wire version.
45const CURSOR_NOT_FOUND_CODE: i32 = 43;
46/// Error codes indicating a change stream is resumable on servers with wire version < 9
47/// (MongoDB < 4.4). On wire version >= 9, the server instead sets the
48/// "ResumableChangeStreamError" label directly.
49const RESUMABLE_CHANGE_STREAM_CODES: [i32; 17] = [
50    6, 7, 89, 91, 189, 262, 9001, 10107, 11600, 11602, 13435, 13436, 63, 150, 13388, 234, 133,
51];
52
53/// Retryable write error label. This label will be added to an error when the error is
54/// write-retryable.
55pub const RETRYABLE_WRITE_ERROR: &str = "RetryableWriteError";
56/// Transient transaction error label. This label will be added to a network error or server
57/// selection error that occurs during a transaction.
58pub const TRANSIENT_TRANSACTION_ERROR: &str = "TransientTransactionError";
59/// Unknown transaction commit result error label. This label will be added to a server selection
60/// error, network error, write-retryable error, MaxTimeMSExpired error, or write concern
61/// failed/timeout during a commitTransaction.
62pub const UNKNOWN_TRANSACTION_COMMIT_RESULT: &str = "UnknownTransactionCommitResult";
63/// Indicates that an error occurred during connection establishment because the server was
64/// overloaded.
65pub const SYSTEM_OVERLOADED_ERROR: &str = "SystemOverloadedError";
66/// Indicates that an error is retryable.
67pub const RETRYABLE_ERROR: &str = "RetryableError";
68/// Indicates that no writes were performed before the error occurred.
69pub const NO_WRITES_PERFORMED: &str = "NoWritesPerformed";
70
71/// The result type for all methods that can return an error in the `mongodb` crate.
72pub type Result<T> = std::result::Result<T, Error>;
73
74/// An error that can occur in the `mongodb` crate. The inner
75/// [`ErrorKind`](enum.ErrorKind.html) is wrapped in an `Box` to allow the errors to be
76/// cloned.
77#[derive(Clone, Debug, Error)]
78#[cfg_attr(
79    feature = "error-backtrace",
80    error(
81        "Kind: {kind}, labels: {labels:?}, source: {source:?}, server response: \
82         {server_response:?}, backtrace: {backtrace}"
83    )
84)]
85#[cfg_attr(
86    not(feature = "error-backtrace"),
87    error(
88        "Kind: {kind}, labels: {labels:?}, source: {source:?}, server response: \
89         {server_response:?}"
90    )
91)]
92#[non_exhaustive]
93pub struct Error {
94    /// The type of error that occurred.
95    pub kind: Box<ErrorKind>,
96
97    labels: HashSet<String>,
98
99    pub(crate) wire_version: Option<i32>,
100
101    #[source]
102    pub(crate) source: Option<Box<Error>>,
103
104    pub(crate) server_response: Option<Box<RawDocumentBuf>>,
105
106    #[cfg(feature = "error-backtrace")]
107    pub(crate) backtrace: Arc<std::backtrace::Backtrace>,
108}
109
110impl Error {
111    /// Create a new `Error` wrapping an arbitrary value.  Can be used to abort transactions in
112    /// callbacks for [`StartTransaction::and_run`](crate::action::StartTransaction::and_run).
113    pub fn custom(e: impl Any + Send + Sync) -> Self {
114        Self::new(ErrorKind::Custom(Arc::new(e)), None::<Option<String>>)
115    }
116
117    /// Retrieve a reference to a value provided to `Error::custom`.  Returns `None` if this is not
118    /// a custom error or if the payload types mismatch.
119    pub fn get_custom<E: Any>(&self) -> Option<&E> {
120        if let ErrorKind::Custom(c) = &*self.kind {
121            c.downcast_ref()
122        } else {
123            None
124        }
125    }
126
127    pub(crate) fn new(kind: ErrorKind, labels: Option<impl IntoIterator<Item = String>>) -> Self {
128        let mut labels: HashSet<String> = labels
129            .map(|labels| labels.into_iter().collect())
130            .unwrap_or_default();
131        if let Some(wc) = kind.get_write_concern_error() {
132            labels.extend(wc.labels.clone());
133        }
134        Self {
135            kind: Box::new(kind),
136            labels,
137            wire_version: None,
138            source: None,
139            server_response: None,
140            #[cfg(feature = "error-backtrace")]
141            backtrace: Arc::new(std::backtrace::Backtrace::capture()),
142        }
143    }
144
145    pub(crate) fn pool_cleared_error(address: &ServerAddress, cause: &Error) -> Self {
146        ErrorKind::ConnectionPoolCleared {
147            message: format!(
148                "Connection pool for {} cleared because another operation failed with: {cause}",
149                Redact(address)
150            ),
151        }
152        .into()
153    }
154
155    /// Creates an `AuthenticationError` for the given mechanism with the provided reason.
156    pub(crate) fn authentication_error(mechanism_name: &str, reason: &str) -> Self {
157        ErrorKind::Authentication {
158            message: format!("{mechanism_name} failure: {reason}"),
159        }
160        .into()
161    }
162
163    /// Creates an `AuthenticationError` for the given mechanism with a generic "unknown" message.
164    pub(crate) fn unknown_authentication_error(mechanism_name: &str) -> Error {
165        Error::authentication_error(mechanism_name, "internal error")
166    }
167
168    /// Creates an `AuthenticationError` for the given mechanism when the server response is
169    /// invalid.
170    pub(crate) fn invalid_authentication_response(mechanism_name: &str) -> Error {
171        Error::authentication_error(mechanism_name, "invalid server response")
172    }
173
174    pub(crate) fn internal(message: impl Into<String>) -> Error {
175        ErrorKind::Internal {
176            message: message.into(),
177        }
178        .into()
179    }
180
181    pub(crate) fn invalid_response(message: impl Into<String>) -> Error {
182        ErrorKind::InvalidResponse {
183            message: message.into(),
184        }
185        .into()
186    }
187
188    /// Construct a generic network timeout error.
189    pub(crate) fn network_timeout() -> Error {
190        ErrorKind::Io(Arc::new(std::io::ErrorKind::TimedOut.into())).into()
191    }
192
193    pub(crate) fn invalid_argument(message: impl Into<String>) -> Error {
194        ErrorKind::InvalidArgument {
195            message: message.into(),
196        }
197        .into()
198    }
199
200    pub(crate) fn is_state_change_error(&self) -> bool {
201        self.is_recovering() || self.is_notwritableprimary()
202    }
203
204    pub(crate) fn is_auth_error(&self) -> bool {
205        matches!(self.kind.as_ref(), ErrorKind::Authentication { .. })
206    }
207
208    #[cfg(all(feature = "in-use-encryption", test))]
209    pub(crate) fn is_command_error(&self) -> bool {
210        matches!(self.kind.as_ref(), ErrorKind::Command(_))
211    }
212
213    pub(crate) fn is_network_timeout(&self) -> bool {
214        matches!(self.kind.as_ref(), ErrorKind::Io(ref io_err) if io_err.kind() == std::io::ErrorKind::TimedOut)
215    }
216
217    /// Whether this error is an "ns not found" error or not.
218    pub(crate) fn is_ns_not_found(&self) -> bool {
219        matches!(self.kind.as_ref(), ErrorKind::Command(ref err) if err.code == 26)
220    }
221
222    pub(crate) fn is_server_selection_error(&self) -> bool {
223        matches!(self.kind.as_ref(), ErrorKind::ServerSelection { .. })
224    }
225
226    pub(crate) fn is_max_time_ms_expired_error(&self) -> bool {
227        self.sdam_code() == Some(50)
228    }
229
230    /// Whether a read operation should be retried if this error occurs.
231    pub(crate) fn is_read_retryable(&self) -> bool {
232        if self.is_network_error() {
233            return true;
234        }
235        match self.sdam_code() {
236            Some(code) => RETRYABLE_READ_CODES.contains(&code),
237            None => false,
238        }
239    }
240
241    pub(crate) fn is_write_retryable(&self) -> bool {
242        self.contains_label(RETRYABLE_WRITE_ERROR)
243    }
244
245    fn is_write_concern_error(&self) -> bool {
246        match *self.kind {
247            ErrorKind::Write(WriteFailure::WriteConcernError(_)) => true,
248            ErrorKind::InsertMany(ref insert_many_error)
249                if insert_many_error.write_concern_error.is_some() =>
250            {
251                true
252            }
253            _ => false,
254        }
255    }
256
257    /// Whether a "RetryableWriteError" label should be added to this error. If max_wire_version
258    /// indicates a 4.4+ server, a label should only be added if the error is a network error.
259    /// Otherwise, a label should be added if the error is a network error or the error code
260    /// matches one of the retryable write codes.
261    pub(crate) fn should_add_retryable_write_label(
262        &self,
263        max_wire_version: Option<i32>,
264        server_type: Option<ServerType>,
265    ) -> bool {
266        const SERVER_4_2_0_WIRE_VERSION: i32 = 8;
267
268        if max_wire_version
269            .map(|mwv| mwv > SERVER_4_2_0_WIRE_VERSION)
270            .unwrap_or(true)
271        {
272            return self.is_network_error();
273        }
274        if self.is_network_error() {
275            return true;
276        }
277
278        if server_type == Some(ServerType::Mongos) && self.is_write_concern_error() {
279            return false;
280        }
281
282        match &self.sdam_code() {
283            Some(code) => RETRYABLE_WRITE_CODES.contains(code),
284            None => false,
285        }
286    }
287
288    pub(crate) fn should_add_unknown_transaction_commit_result_label(&self) -> bool {
289        if self.contains_label(TRANSIENT_TRANSACTION_ERROR) {
290            return false;
291        }
292        if self.is_network_error() || self.is_server_selection_error() || self.is_write_retryable()
293        {
294            return true;
295        }
296        match self.sdam_code() {
297            Some(code) => UNKNOWN_TRANSACTION_COMMIT_RESULT_LABEL_CODES.contains(&code),
298            None => false,
299        }
300    }
301
302    /// Whether an error originated from the server.
303    pub(crate) fn is_server_error(&self) -> bool {
304        matches!(
305            self.kind.as_ref(),
306            ErrorKind::Authentication { .. }
307                | ErrorKind::InsertMany(_)
308                | ErrorKind::Command(_)
309                | ErrorKind::Write(_)
310        )
311    }
312
313    /// Returns the labels for this error.
314    pub fn labels(&self) -> &HashSet<String> {
315        &self.labels
316    }
317
318    /// Whether this error contains the specified label.
319    pub fn contains_label<T: AsRef<str>>(&self, label: T) -> bool {
320        let label = label.as_ref();
321        self.labels().contains(label)
322            || self
323                .source
324                .as_ref()
325                .map(|source| source.contains_label(label))
326                .unwrap_or(false)
327    }
328
329    /// Adds the given label to this error.
330    pub(crate) fn add_label<T: AsRef<str>>(&mut self, label: T) {
331        let label = label.as_ref().to_string();
332        self.labels.insert(label);
333    }
334
335    pub(crate) fn with_backpressure_labels(mut self) -> Self {
336        if self.is_network_error() {
337            self.add_label(SYSTEM_OVERLOADED_ERROR);
338            self.add_label(RETRYABLE_ERROR);
339        }
340        self
341    }
342
343    /// The full response returned from the server. This can be used to inspect error fields that
344    /// are not represented in the `Error` type.
345    pub fn server_response(&self) -> Option<&RawDocumentBuf> {
346        self.server_response.as_deref()
347    }
348
349    /// Adds the server's response to this error if it is not already present.
350    pub(crate) fn with_server_response(mut self, response: &RawCommandResponse) -> Self {
351        if self.server_response.is_none() {
352            self.server_response = Some(Box::new(response.raw_body().to_owned()));
353        }
354        self
355    }
356
357    #[cfg(feature = "dns-resolver")]
358    pub(crate) fn from_resolve_error(error: hickory_net::NetError) -> Self {
359        ErrorKind::DnsResolve {
360            message: error.to_string(),
361        }
362        .into()
363    }
364
365    #[cfg(feature = "dns-resolver")]
366    pub(crate) fn from_resolve_proto_error(error: hickory_proto::ProtoError) -> Self {
367        ErrorKind::DnsResolve {
368            message: error.to_string(),
369        }
370        .into()
371    }
372
373    pub(crate) fn is_non_timeout_network_error(&self) -> bool {
374        matches!(self.kind.as_ref(), ErrorKind::Io(ref io_err) if io_err.kind() != std::io::ErrorKind::TimedOut)
375    }
376
377    pub(crate) fn is_network_error(&self) -> bool {
378        #[cfg(feature = "socks5-proxy")]
379        if matches!(self.kind.as_ref(), ErrorKind::ProxyConnect { .. }) {
380            return true;
381        }
382        matches!(
383            self.kind.as_ref(),
384            ErrorKind::Io(..) | ErrorKind::ConnectionPoolCleared { .. }
385        )
386    }
387
388    #[cfg(all(test, feature = "in-use-encryption"))]
389    pub(crate) fn is_csfle_error(&self) -> bool {
390        matches!(self.kind.as_ref(), ErrorKind::Encryption(..))
391    }
392
393    /// Gets the code from this error for performing SDAM updates, if applicable.
394    /// Any codes contained in WriteErrors are ignored.
395    pub(crate) fn sdam_code(&self) -> Option<i32> {
396        match self.kind.as_ref() {
397            ErrorKind::Command(command_error) => Some(command_error.code),
398            // According to SDAM spec, write concern error codes MUST also be checked, and
399            // writeError codes MUST NOT be checked.
400            ErrorKind::InsertMany(InsertManyError {
401                write_concern_error: Some(wc_error),
402                ..
403            }) => Some(wc_error.code),
404            ErrorKind::Write(WriteFailure::WriteConcernError(wc_error)) => Some(wc_error.code),
405            _ => None,
406        }
407        .or_else(|| self.source.as_ref().and_then(|s| s.sdam_code()))
408    }
409
410    /// Gets the code from this error.
411    #[cfg(test)]
412    pub(crate) fn code(&self) -> Option<i32> {
413        match self.kind.as_ref() {
414            ErrorKind::Command(command_error) => Some(command_error.code),
415            ErrorKind::InsertMany(InsertManyError {
416                write_concern_error: Some(wc_error),
417                ..
418            }) => Some(wc_error.code),
419            ErrorKind::Write(e) => Some(e.code()),
420            _ => None,
421        }
422        .or_else(|| self.source.as_ref().and_then(|s| s.sdam_code()))
423    }
424
425    /// Gets the message for this error, if applicable, for use in testing.
426    /// If this error is an InsertManyError, the messages are concatenated.
427    #[cfg(test)]
428    pub(crate) fn message(&self) -> Option<String> {
429        match self.kind.as_ref() {
430            ErrorKind::Command(command_error) => Some(command_error.message.clone()),
431            // since this is used primarily for errorMessageContains assertions in the unified
432            // runner, we just concatenate all the relevant server messages into one for
433            // insert many errors.
434            ErrorKind::InsertMany(InsertManyError {
435                write_concern_error,
436                write_errors,
437                inserted_ids: _,
438            }) => {
439                let mut msg = "".to_string();
440                if let Some(wc_error) = write_concern_error {
441                    msg.push_str(wc_error.message.as_str());
442                }
443                if let Some(write_errors) = write_errors {
444                    for we in write_errors {
445                        msg.push_str(we.message.as_str());
446                    }
447                }
448                Some(msg)
449            }
450            ErrorKind::Write(WriteFailure::WriteConcernError(wc_error)) => {
451                Some(wc_error.message.clone())
452            }
453            ErrorKind::Write(WriteFailure::WriteError(write_error)) => {
454                Some(write_error.message.clone())
455            }
456            ErrorKind::Transaction { message } => Some(message.clone()),
457            ErrorKind::IncompatibleServer { message } => Some(message.clone()),
458            ErrorKind::InvalidArgument { message } => Some(message.clone()),
459            #[cfg(feature = "in-use-encryption")]
460            ErrorKind::Encryption(err) => err.message.clone(),
461            _ => None,
462        }
463    }
464
465    /// Gets the code name from this error, if applicable.
466    #[cfg(test)]
467    pub(crate) fn code_name(&self) -> Option<&str> {
468        match self.kind.as_ref() {
469            ErrorKind::Command(ref cmd_err) => Some(cmd_err.code_name.as_str()),
470            ErrorKind::Write(ref failure) => match failure {
471                WriteFailure::WriteConcernError(ref wce) => Some(wce.code_name.as_str()),
472                WriteFailure::WriteError(ref we) => we.code_name.as_deref(),
473            },
474            ErrorKind::InsertMany(ref bwe) => bwe
475                .write_concern_error
476                .as_ref()
477                .map(|wce| wce.code_name.as_str()),
478            _ => None,
479        }
480    }
481
482    /// If this error corresponds to a "not writable primary" error as per the SDAM spec.
483    pub(crate) fn is_notwritableprimary(&self) -> bool {
484        self.sdam_code()
485            .map(|code| NOTWRITABLEPRIMARY_CODES.contains(&code))
486            .unwrap_or(false)
487    }
488
489    /// If this error corresponds to a "reauthentication required" error.
490    pub(crate) fn is_reauthentication_required(&self) -> bool {
491        self.sdam_code() == Some(REAUTHENTICATION_REQUIRED_CODE)
492    }
493
494    /// If this error corresponds to a "node is recovering" error as per the SDAM spec.
495    pub(crate) fn is_recovering(&self) -> bool {
496        self.sdam_code()
497            .map(|code| RECOVERING_CODES.contains(&code))
498            .unwrap_or(false)
499    }
500
501    /// If this error corresponds to a "node is shutting down" error as per the SDAM spec.
502    pub(crate) fn is_shutting_down(&self) -> bool {
503        self.sdam_code()
504            .map(|code| SHUTTING_DOWN_CODES.contains(&code))
505            .unwrap_or(false)
506    }
507
508    pub(crate) fn is_pool_cleared(&self) -> bool {
509        matches!(self.kind.as_ref(), ErrorKind::ConnectionPoolCleared { .. })
510    }
511
512    /// If this error is resumable as per the change streams spec.
513    pub(crate) fn is_resumable(&self) -> bool {
514        if !self.is_server_error() {
515            return true;
516        }
517        let code = self.sdam_code();
518        if code == Some(CURSOR_NOT_FOUND_CODE) {
519            return true;
520        }
521        if matches!(self.wire_version, Some(v) if v >= 9)
522            && self.contains_label("ResumableChangeStreamError")
523        {
524            return true;
525        }
526        if let (Some(code), true) = (code, matches!(self.wire_version, Some(v) if v < 9)) {
527            if RESUMABLE_CHANGE_STREAM_CODES.contains(&code) {
528                return true;
529            }
530        }
531        false
532    }
533
534    pub(crate) fn is_incompatible_server(&self) -> bool {
535        matches!(self.kind.as_ref(), ErrorKind::IncompatibleServer { .. })
536    }
537
538    #[allow(unused)]
539    pub(crate) fn is_invalid_argument(&self) -> bool {
540        matches!(self.kind.as_ref(), ErrorKind::InvalidArgument { .. })
541    }
542
543    pub(crate) fn with_source<E: Into<Option<Error>>>(mut self, source: E) -> Self {
544        self.source = source.into().map(Box::new);
545        self
546    }
547
548    pub(crate) fn topology_version(&self) -> Option<TopologyVersion> {
549        match self.kind.as_ref() {
550            ErrorKind::Command(c) => c.topology_version,
551            _ => None,
552        }
553    }
554
555    /// Per the CLAM spec, for sensitive commands we must redact everything besides the
556    /// error labels, error code, and error code name from errors received in response to
557    /// sensitive commands. Currently, the only field besides those that we expose is the
558    /// error message.
559    pub(crate) fn redact(&mut self) {
560        if let Some(source) = self.source.as_deref_mut() {
561            source.redact();
562        }
563
564        if self.server_response.is_some() {
565            self.server_response = Some(Box::new(rawdoc! { "redacted": true }));
566        }
567
568        // This is intentionally written without a catch-all branch so that if new error
569        // kinds are added we remember to reason about whether they need to be redacted.
570        match *self.kind {
571            ErrorKind::InsertMany(ref mut insert_many_error) => {
572                if let Some(ref mut wes) = insert_many_error.write_errors {
573                    for we in wes {
574                        we.redact();
575                    }
576                }
577                if let Some(ref mut wce) = insert_many_error.write_concern_error {
578                    wce.redact();
579                }
580            }
581            ErrorKind::BulkWrite(ref mut bulk_write_error) => {
582                for write_concern_error in bulk_write_error.write_concern_errors.iter_mut() {
583                    write_concern_error.redact();
584                }
585                for (_, write_error) in bulk_write_error.write_errors.iter_mut() {
586                    write_error.redact();
587                }
588            }
589            ErrorKind::Command(ref mut command_error) => {
590                command_error.redact();
591            }
592            ErrorKind::Write(ref mut write_error) => match write_error {
593                WriteFailure::WriteConcernError(wce) => {
594                    wce.redact();
595                }
596                WriteFailure::WriteError(we) => {
597                    we.redact();
598                }
599            },
600            ErrorKind::InvalidArgument { .. }
601            | ErrorKind::BsonDeserialization(_)
602            | ErrorKind::BsonSerialization(_)
603            | ErrorKind::DnsResolve { .. }
604            | ErrorKind::Io(_)
605            | ErrorKind::Internal { .. }
606            | ErrorKind::ConnectionPoolCleared { .. }
607            | ErrorKind::InvalidResponse { .. }
608            | ErrorKind::ServerSelection { .. }
609            | ErrorKind::SessionsNotSupported
610            | ErrorKind::InvalidTlsConfig { .. }
611            | ErrorKind::Transaction { .. }
612            | ErrorKind::IncompatibleServer { .. }
613            | ErrorKind::MissingResumeToken
614            | ErrorKind::Authentication { .. }
615            | ErrorKind::Custom(_)
616            | ErrorKind::Shutdown
617            | ErrorKind::GridFs(_) => {}
618            #[cfg(feature = "socks5-proxy")]
619            ErrorKind::ProxyConnect { .. } => {}
620            #[cfg(feature = "in-use-encryption")]
621            ErrorKind::Encryption(_) => {}
622            #[cfg(feature = "bson-3")]
623            ErrorKind::Bson(_) => {}
624        }
625    }
626}
627
628impl<E> From<E> for Error
629where
630    ErrorKind: From<E>,
631{
632    fn from(err: E) -> Self {
633        Error::new(err.into(), None::<Option<String>>)
634    }
635}
636
637#[cfg(not(feature = "bson-3"))]
638impl From<crate::bson::de::Error> for ErrorKind {
639    fn from(err: crate::bson::de::Error) -> Self {
640        Self::BsonDeserialization(err)
641    }
642}
643
644#[cfg(not(feature = "bson-3"))]
645impl From<crate::bson::ser::Error> for ErrorKind {
646    fn from(err: crate::bson::ser::Error) -> Self {
647        Self::BsonSerialization(err)
648    }
649}
650
651#[cfg(not(feature = "bson-3"))]
652impl From<crate::bson_compat::RawError> for ErrorKind {
653    fn from(err: crate::bson_compat::RawError) -> Self {
654        Self::InvalidResponse {
655            message: err.to_string(),
656        }
657    }
658}
659
660#[cfg(not(feature = "bson-3"))]
661impl From<crate::bson::raw::ValueAccessError> for ErrorKind {
662    fn from(err: crate::bson::raw::ValueAccessError) -> Self {
663        Self::InvalidResponse {
664            message: err.to_string(),
665        }
666    }
667}
668
669#[cfg(feature = "bson-3")]
670impl From<crate::bson::error::Error> for ErrorKind {
671    fn from(err: crate::bson::error::Error) -> Self {
672        Self::Bson(err)
673    }
674}
675
676impl From<std::io::Error> for ErrorKind {
677    fn from(err: std::io::Error) -> Self {
678        Self::Io(Arc::new(err))
679    }
680}
681
682impl From<std::io::ErrorKind> for ErrorKind {
683    fn from(err: std::io::ErrorKind) -> Self {
684        Self::Io(Arc::new(err.into()))
685    }
686}
687
688#[cfg(feature = "in-use-encryption")]
689impl From<mongocrypt::error::Error> for ErrorKind {
690    fn from(err: mongocrypt::error::Error) -> Self {
691        Self::Encryption(err)
692    }
693}
694
695impl From<std::convert::Infallible> for ErrorKind {
696    fn from(_err: std::convert::Infallible) -> Self {
697        unreachable!()
698    }
699}
700
701/// The types of errors that can occur.
702#[allow(missing_docs)]
703#[derive(Clone, Debug, Error)]
704#[non_exhaustive]
705pub enum ErrorKind {
706    /// An invalid argument was provided.
707    #[error("An invalid argument was provided: {message}")]
708    #[non_exhaustive]
709    InvalidArgument { message: String },
710
711    /// An error occurred while the [`Client`](../struct.Client.html) attempted to authenticate a
712    /// connection.
713    #[error("{message}")]
714    #[non_exhaustive]
715    Authentication { message: String },
716
717    /// Wrapper around `bson::de::Error`.  Unused if the `bson-3` feature is enabled.
718    #[error("{0}")]
719    BsonDeserialization(crate::bson_compat::DeError),
720
721    /// Wrapper around `bson::ser::Error`.  Unused if the `bson-3` feature is enabled.
722    #[error("{0}")]
723    BsonSerialization(crate::bson_compat::SerError),
724
725    /// Wrapper around `bson::error::Error`.
726    #[cfg(feature = "bson-3")]
727    #[error("{0}")]
728    Bson(crate::bson::error::Error),
729
730    /// An error occurred when trying to execute an [`insert_many`](crate::Collection::insert_many)
731    /// operation.
732    #[error("An error occurred when trying to execute an insert_many operation: {0:?}")]
733    InsertMany(InsertManyError),
734
735    #[error("An error occurred when executing Client::bulk_write: {0:?}")]
736    BulkWrite(BulkWriteError),
737
738    /// The server returned an error to an attempted operation.
739    #[error("Command failed: {0}")]
740    // note that if this Display impl changes, COMMAND_ERROR_REGEX in the unified runner matching
741    // logic will need to be updated.
742    Command(CommandError),
743
744    /// An error occurred during DNS resolution.
745    #[error("An error occurred during DNS resolution: {message}")]
746    #[non_exhaustive]
747    DnsResolve { message: String },
748
749    /// A GridFS error occurred.
750    #[error("{0:?}")]
751    GridFs(GridFsErrorKind),
752
753    #[error("Internal error: {message}")]
754    #[non_exhaustive]
755    Internal { message: String },
756
757    /// Wrapper around [`std::io::Error`](https://doc.rust-lang.org/std/io/struct.Error.html).
758    #[error("I/O error: {0}")]
759    // note that if this Display impl changes, IO_ERROR_REGEX in the unified runner matching logic
760    // will need to be updated.
761    Io(Arc<std::io::Error>),
762
763    /// The connection pool for a server was cleared during operation execution due to
764    /// a concurrent error, causing the operation to fail.
765    #[error("{message}")]
766    #[non_exhaustive]
767    ConnectionPoolCleared { message: String },
768
769    /// The server returned an invalid reply to a database operation.
770    #[error("The server returned an invalid reply to a database operation: {message}")]
771    #[non_exhaustive]
772    InvalidResponse { message: String },
773
774    /// The Client was not able to select a server for the operation.
775    #[error("{message}")]
776    #[non_exhaustive]
777    ServerSelection { message: String },
778
779    /// The Client does not support sessions.
780    #[error("Attempted to start a session on a deployment that does not support sessions")]
781    SessionsNotSupported,
782
783    #[error("{message}")]
784    #[non_exhaustive]
785    InvalidTlsConfig { message: String },
786
787    /// An error occurred when trying to execute a write operation.
788    #[error("An error occurred when trying to execute a write operation: {0:?}")]
789    Write(WriteFailure),
790
791    /// An error occurred during a transaction.
792    #[error("{message}")]
793    #[non_exhaustive]
794    Transaction { message: String },
795
796    /// The server does not support the operation.
797    #[error("The server does not support a database operation: {message}")]
798    #[non_exhaustive]
799    IncompatibleServer { message: String },
800
801    /// No resume token was present in a change stream document.
802    #[error("Cannot provide resume functionality when the resume token is missing")]
803    MissingResumeToken,
804
805    /// An error occurred during encryption or decryption.
806    #[cfg(feature = "in-use-encryption")]
807    #[error("An error occurred during client-side encryption: {0}")]
808    Encryption(mongocrypt::error::Error),
809
810    /// A custom value produced by user code.
811    #[error("Custom user error{string}", string = display_custom(.0))]
812    Custom(Arc<dyn Any + Send + Sync>),
813
814    /// A method was called on a client that was shut down.
815    #[error("Client has been shut down")]
816    Shutdown,
817
818    /// An error occurred when connecting to a proxy host.
819    #[error("An error occurred when connecting to a proxy host: {message}")]
820    #[non_exhaustive]
821    #[cfg(feature = "socks5-proxy")]
822    ProxyConnect { message: String },
823}
824
825fn display_custom(custom: &Arc<dyn Any + Send + Sync>) -> String {
826    if let Some(string) = custom.downcast_ref::<String>() {
827        format!(": {string}")
828    } else {
829        String::new()
830    }
831}
832
833impl ErrorKind {
834    // This is only used as part of a workaround to Atlas Proxy not returning
835    // toplevel error labels.
836    // TODO CLOUDP-105256 Remove this when Atlas Proxy error label behavior is fixed.
837    fn get_write_concern_error(&self) -> Option<&WriteConcernError> {
838        match self {
839            ErrorKind::InsertMany(InsertManyError {
840                write_concern_error,
841                ..
842            }) => write_concern_error.as_ref(),
843            ErrorKind::Write(WriteFailure::WriteConcernError(err)) => Some(err),
844            _ => None,
845        }
846    }
847
848    #[cfg(feature = "opentelemetry")]
849    pub(crate) fn name(&self) -> &'static str {
850        match self {
851            ErrorKind::InvalidArgument { .. } => "InvalidArgument",
852            ErrorKind::Authentication { .. } => "Authentication",
853            ErrorKind::BsonDeserialization(..) => "BsonDeserialization",
854            ErrorKind::BsonSerialization(..) => "BsonSerialization",
855            #[cfg(feature = "bson-3")]
856            ErrorKind::Bson(..) => "Bson",
857            ErrorKind::InsertMany(..) => "InsertMany",
858            ErrorKind::BulkWrite(..) => "BulkWrite",
859            ErrorKind::Command(..) => "Command",
860            ErrorKind::DnsResolve { .. } => "DnsResolve",
861            ErrorKind::GridFs(..) => "GridFs",
862            ErrorKind::Internal { .. } => "Internal",
863            ErrorKind::Io(..) => "Io",
864            ErrorKind::ConnectionPoolCleared { .. } => "ConnectionPoolCleared",
865            ErrorKind::InvalidResponse { .. } => "InvalidResponse",
866            ErrorKind::ServerSelection { .. } => "ServerSelection",
867            ErrorKind::SessionsNotSupported => "SessionsNotSupported",
868            ErrorKind::InvalidTlsConfig { .. } => "InvalidTlsConfig",
869            ErrorKind::Write(..) => "Write",
870            ErrorKind::Transaction { .. } => "Transaction",
871            ErrorKind::IncompatibleServer { .. } => "IncompatibleServer",
872            ErrorKind::MissingResumeToken => "MissingResumeToken",
873            #[cfg(feature = "in-use-encryption")]
874            ErrorKind::Encryption(..) => "Encryption",
875            ErrorKind::Custom(..) => "Custom",
876            ErrorKind::Shutdown => "Shutdown",
877            #[cfg(feature = "socks5-proxy")]
878            ErrorKind::ProxyConnect { .. } => "ProxyConnect",
879        }
880    }
881}
882
883/// An error that occurred due to a database command failing.
884#[derive(Clone, Debug, Serialize, Deserialize)]
885#[non_exhaustive]
886pub struct CommandError {
887    /// Identifies the type of error.
888    pub code: i32,
889
890    /// The name associated with the error code.
891    #[serde(rename = "codeName", default)]
892    pub code_name: String,
893
894    /// A description of the error that occurred.
895    #[serde(rename = "errmsg", default = "String::new")]
896    pub message: String,
897
898    /// The topology version reported by the server in the error response.
899    #[serde(rename = "topologyVersion")]
900    pub(crate) topology_version: Option<TopologyVersion>,
901}
902
903impl CommandError {
904    // If any new fields are added to CommandError, this implementation must be updated to
905    // redact them per the CLAM spec.
906    fn redact(&mut self) {
907        self.message = "REDACTED".to_string();
908    }
909}
910
911impl fmt::Display for CommandError {
912    // note that if this Display impl changes, COMMAND_ERROR_REGEX in the unified runner matching
913    // logic will need to be updated.
914    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
915        write!(
916            fmt,
917            "Error code {} ({}): {}",
918            self.code, self.code_name, self.message
919        )
920    }
921}
922
923/// An error that occurred due to not being able to satisfy a write concern.
924#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
925#[non_exhaustive]
926pub struct WriteConcernError {
927    /// Identifies the type of write concern error.
928    pub code: i32,
929
930    /// The name associated with the error code.
931    #[serde(rename = "codeName", default)]
932    pub code_name: String,
933
934    /// A description of the error that occurred.
935    #[serde(alias = "errmsg", default = "String::new")]
936    pub message: String,
937
938    /// A document identifying the write concern setting related to the error.
939    #[serde(rename = "errInfo")]
940    pub details: Option<Document>,
941
942    /// Labels categorizing the error.
943    // TODO CLOUDP-105256 Remove this when the Atlas Proxy properly returns
944    // error labels at the top level.
945    #[serde(rename = "errorLabels", default)]
946    pub(crate) labels: Vec<String>,
947}
948
949impl WriteConcernError {
950    // If any new fields are added to WriteConcernError, this implementation must be updated to
951    // redact them per the CLAM spec.
952    fn redact(&mut self) {
953        self.message = "REDACTED".to_string();
954        self.details = None;
955    }
956}
957
958/// An error that occurred during a write operation that wasn't due to being unable to satisfy a
959/// write concern.
960#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
961#[non_exhaustive]
962pub struct WriteError {
963    /// Identifies the type of write error.
964    pub code: i32,
965
966    /// The name associated with the error code.
967    ///
968    /// Note that the server will not return this in some cases, hence `code_name` being an
969    /// `Option`.
970    #[serde(rename = "codeName", default)]
971    pub code_name: Option<String>,
972
973    /// A description of the error that occurred.
974    #[serde(rename = "errmsg", default = "String::new")]
975    pub message: String,
976
977    /// A document providing more information about the write error (e.g. details
978    /// pertaining to document validation).
979    #[serde(rename = "errInfo")]
980    pub details: Option<Document>,
981}
982
983impl WriteError {
984    // If any new fields are added to WriteError, this implementation must be updated to redact them
985    // per the CLAM spec.
986    fn redact(&mut self) {
987        self.message = "REDACTED".to_string();
988        self.details = None;
989    }
990}
991
992/// An individual write error that occurred during an
993/// [`insert_many`](crate::Collection::insert_many) operation.
994#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
995#[non_exhaustive]
996pub struct IndexedWriteError {
997    /// Index into the list of operations that this error corresponds to.
998    #[serde(default)]
999    pub index: usize,
1000
1001    /// Identifies the type of write concern error.
1002    pub code: i32,
1003
1004    /// The name associated with the error code.
1005    ///
1006    /// Note that the server will not return this in some cases, hence `code_name` being an
1007    /// `Option`.
1008    #[serde(rename = "codeName", default)]
1009    pub code_name: Option<String>,
1010
1011    /// A description of the error that occurred.
1012    #[serde(rename = "errmsg", default = "String::new")]
1013    pub message: String,
1014
1015    /// A document providing more information about the write error (e.g. details
1016    /// pertaining to document validation).
1017    #[serde(rename = "errInfo")]
1018    pub details: Option<Document>,
1019}
1020
1021impl IndexedWriteError {
1022    // If any new fields are added to InsertError, this implementation must be updated to redact
1023    // them per the CLAM spec.
1024    fn redact(&mut self) {
1025        self.message = "REDACTED".to_string();
1026        self.details = None;
1027    }
1028}
1029
1030/// The set of errors that occurred during a call to
1031/// [`insert_many`](crate::Collection::insert_many).
1032#[derive(Clone, Debug, Serialize, Deserialize)]
1033#[serde(rename_all = "camelCase")]
1034#[non_exhaustive]
1035pub struct InsertManyError {
1036    /// The error(s) that occurred on account of a non write concern failure.
1037    pub write_errors: Option<Vec<IndexedWriteError>>,
1038
1039    /// The error that occurred on account of write concern failure.
1040    pub write_concern_error: Option<WriteConcernError>,
1041
1042    #[serde(skip)]
1043    pub(crate) inserted_ids: HashMap<usize, Bson>,
1044}
1045
1046impl InsertManyError {
1047    pub(crate) fn new() -> Self {
1048        InsertManyError {
1049            write_errors: None,
1050            write_concern_error: None,
1051            inserted_ids: Default::default(),
1052        }
1053    }
1054}
1055
1056/// An error that occurred when trying to execute a write operation.
1057#[derive(Clone, Debug, Serialize, Deserialize)]
1058#[non_exhaustive]
1059pub enum WriteFailure {
1060    /// An error that occurred due to not being able to satisfy a write concern.
1061    WriteConcernError(WriteConcernError),
1062
1063    /// An error that occurred during a write operation that wasn't due to being unable to satisfy
1064    /// a write concern.
1065    WriteError(WriteError),
1066}
1067
1068impl WriteFailure {
1069    fn from_insert_many_error(bulk: InsertManyError) -> Result<Self> {
1070        if let Some(insert_error) = bulk.write_errors.and_then(|es| es.into_iter().next()) {
1071            let write_error = WriteError {
1072                code: insert_error.code,
1073                code_name: insert_error.code_name,
1074                message: insert_error.message,
1075                details: insert_error.details,
1076            };
1077            Ok(WriteFailure::WriteError(write_error))
1078        } else if let Some(wc_error) = bulk.write_concern_error {
1079            Ok(WriteFailure::WriteConcernError(wc_error))
1080        } else {
1081            Err(ErrorKind::InvalidResponse {
1082                message: "error missing write errors and write concern errors".to_string(),
1083            }
1084            .into())
1085        }
1086    }
1087
1088    #[cfg(test)]
1089    pub(crate) fn code(&self) -> i32 {
1090        match self {
1091            Self::WriteConcernError(e) => e.code,
1092            Self::WriteError(e) => e.code,
1093        }
1094    }
1095}
1096
1097/// An error that occurred during a GridFS operation.
1098#[derive(Clone, Debug)]
1099#[allow(missing_docs)]
1100#[non_exhaustive]
1101pub enum GridFsErrorKind {
1102    /// The file with the given identifier was not found.
1103    #[non_exhaustive]
1104    FileNotFound { identifier: GridFsFileIdentifier },
1105
1106    /// The file with the given revision was not found.
1107    #[non_exhaustive]
1108    RevisionNotFound { revision: i32 },
1109
1110    /// The chunk at index `n` was missing.
1111    #[non_exhaustive]
1112    MissingChunk { n: u32 },
1113
1114    /// An operation was attempted on a [`GridFsUploadStream`](crate::gridfs::GridFsUploadStream)
1115    /// that has already been shut down.
1116    UploadStreamClosed,
1117
1118    /// The chunk at index `n` was the incorrect size.
1119    #[non_exhaustive]
1120    WrongSizeChunk {
1121        actual_size: usize,
1122        expected_size: u32,
1123        n: u32,
1124    },
1125
1126    /// An incorrect number of chunks was present for the file.
1127    #[non_exhaustive]
1128    WrongNumberOfChunks {
1129        actual_number: u32,
1130        expected_number: u32,
1131    },
1132
1133    /// An error occurred when aborting a file upload.
1134    #[non_exhaustive]
1135    AbortError {
1136        /// The original error. Only present if the abort occurred because of an error during a
1137        /// GridFS operation.
1138        original_error: Option<Error>,
1139
1140        /// The error that occurred when attempting to remove any orphaned chunks.
1141        delete_error: Error,
1142    },
1143
1144    /// A close operation was attempted on a
1145    /// [`GridFsUploadStream`](crate::gridfs::GridFsUploadStream) while a write was still in
1146    /// progress.
1147    WriteInProgress,
1148}
1149
1150/// An identifier for a file stored in a GridFS bucket.
1151#[derive(Clone, Debug)]
1152#[non_exhaustive]
1153pub enum GridFsFileIdentifier {
1154    /// The name of the file. Not guaranteed to be unique.
1155    Filename(String),
1156
1157    /// The file's unique [`Bson`] ID.
1158    Id(Bson),
1159}
1160
1161/// Translates ErrorKind::InsertMany to ErrorKind::Write, leaving all other errors untouched.
1162pub(crate) fn convert_insert_many_error(error: Error) -> Error {
1163    match *error.kind {
1164        ErrorKind::InsertMany(insert_many_error) => {
1165            match WriteFailure::from_insert_many_error(insert_many_error) {
1166                Ok(failure) => Error::new(ErrorKind::Write(failure), Some(error.labels)),
1167                Err(e) => e,
1168            }
1169        }
1170        _ => error,
1171    }
1172}
1173
1174/// Flag a load-balanced mode mismatch.  With debug assertions enabled, it will panic; otherwise,
1175/// it will return the argument, or `()` if none is given.
1176// TODO RUST-230 Log an error in the non-panic branch for mode mismatch.
1177macro_rules! load_balanced_mode_mismatch {
1178    ($e:expr) => {{
1179        if cfg!(debug_assertions) {
1180            panic!("load-balanced mode mismatch")
1181        }
1182        return $e;
1183    }};
1184    () => {
1185        load_balanced_mode_mismatch!(())
1186    };
1187}
1188
1189pub(crate) use load_balanced_mode_mismatch;
1190
1191pub(crate) struct Redact<T>(pub T);
1192
1193impl<T: std::fmt::Display> std::fmt::Display for Redact<T> {
1194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1195        if cfg!(feature = "redact-errors") {
1196            write!(f, "<redacted>")
1197        } else {
1198            write!(f, "{}", self.0)
1199        }
1200    }
1201}
1202
1203impl<T: std::fmt::Debug> std::fmt::Debug for Redact<T> {
1204    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1205        if cfg!(feature = "redact-errors") {
1206            write!(f, "<redacted>")
1207        } else {
1208            write!(f, "{:?}", self.0)
1209        }
1210    }
1211}