Skip to main content

google_cloud_spanner/
error.rs

1// Copyright 2026 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use google_cloud_gax::error::rpc::Status;
16use std::error::Error;
17
18pub use crate::from_value::ConvertError;
19pub use wkt::{DurationError, TimestampError};
20
21/// An unexpected error that occurs when the client receives data from Spanner
22/// that it cannot properly parse or handle. This typically indicates a bug in
23/// the client library or the Spanner service itself, though other causes are possible.
24///
25/// # Troubleshooting
26///
27/// This indicates a bug in the client, the service, or a message corrupted
28/// while in transit. Please [open an issue] with as much detail as possible.
29///
30/// [open an issue]: https://github.com/googleapis/google-cloud-rust/issues/new/choose
31#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
32#[non_exhaustive]
33pub enum SpannerInternalError {
34    /// Indicates that Spanner returned data in an unexpected or unparsable format.
35    ///
36    /// This represents an unexpected state and is an indication of an internal bug.
37    #[error("unexpected data received from Spanner: {0}")]
38    UnexpectedData(String),
39}
40
41impl SpannerInternalError {
42    pub(crate) fn new(message: impl Into<String>) -> Self {
43        Self::UnexpectedData(message.into())
44    }
45}
46
47pub(crate) fn internal_error(message: impl Into<String>) -> crate::Error {
48    crate::Error::deser(SpannerInternalError::new(message))
49}
50
51/// An error that occurs when an `execute_batch_update` partially succeeds.
52///
53/// It contains the update counts for each statement evaluated prior to the failure,
54/// as well as the underlying error that caused the batch to fail.
55///
56/// Statements are executed serially in the order provided in the batch.
57/// The `update_counts` correspond to the executed statements in the original
58/// request based on their relative order. The statement that failed is the
59/// one that follows directly after the last statement with an update count.
60/// Execution stops at the first failed statement, and the remaining statements
61/// are not executed.
62#[derive(thiserror::Error, Debug)]
63#[error("{status}")]
64#[non_exhaustive]
65pub struct BatchUpdateError {
66    /// The number of rows modified by each successful statement before the failure.
67    pub update_counts: Vec<i64>,
68    /// The error that caused the batch to fail.
69    #[source]
70    pub status: crate::Error,
71}
72
73impl BatchUpdateError {
74    /// Extracts a `BatchUpdateError` from a `google_cloud_spanner::Error`, if present.
75    pub fn extract(err: &crate::Error) -> Option<&Self> {
76        err.source()
77            .and_then(|source| source.downcast_ref::<BatchUpdateError>())
78    }
79
80    pub(crate) fn build_error(update_counts: Vec<i64>, grpc_status: Status) -> crate::Error {
81        let status = crate::Error::service(grpc_status.clone());
82        let err = Self {
83            update_counts,
84            status,
85        };
86        crate::Error::service_full(grpc_status, None, None, Some(Box::new(err)))
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use google_cloud_gax::error::rpc::Code;
94    use static_assertions::assert_impl_all;
95
96    #[test]
97    fn auto_traits() {
98        assert_impl_all!(BatchUpdateError: Send, Sync, std::fmt::Debug);
99    }
100
101    #[test]
102    fn extract_success() {
103        let update_counts = vec![1, 2, 3];
104        let grpc_status = Status::default()
105            .set_code(Code::Aborted)
106            .set_message("Batch failed");
107
108        let err = BatchUpdateError::build_error(update_counts.clone(), grpc_status);
109
110        let extracted = BatchUpdateError::extract(&err).expect("should extract BatchUpdateError");
111        assert_eq!(extracted.update_counts, update_counts);
112        assert_eq!(
113            extracted
114                .status
115                .status()
116                .expect("status should be populated")
117                .code,
118            Code::Aborted
119        );
120    }
121
122    #[test]
123    fn extract_failure() {
124        let grpc_status = Status::default()
125            .set_code(Code::Unknown)
126            .set_message("Regular error");
127        let err = crate::Error::service(grpc_status);
128
129        let extracted = BatchUpdateError::extract(&err);
130        assert!(
131            extracted.is_none(),
132            "should not extract BatchUpdateError from standard service error"
133        );
134    }
135}