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
90pub(crate) fn aborted_due_to_failed_initial_statement() -> crate::Error {
91    crate::Error::service(
92        google_cloud_gax::error::rpc::Status::default()
93            .set_code(google_cloud_gax::error::rpc::Code::Aborted)
94            .set_message("Aborted due to failed initial statement"),
95    )
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use google_cloud_gax::error::rpc::Code;
102    use static_assertions::assert_impl_all;
103
104    #[test]
105    fn auto_traits() {
106        assert_impl_all!(BatchUpdateError: Send, Sync, std::fmt::Debug);
107    }
108
109    #[test]
110    fn extract_success() {
111        let update_counts = vec![1, 2, 3];
112        let grpc_status = Status::default()
113            .set_code(Code::Aborted)
114            .set_message("Batch failed");
115
116        let err = BatchUpdateError::build_error(update_counts.clone(), grpc_status);
117
118        let extracted = BatchUpdateError::extract(&err).expect("should extract BatchUpdateError");
119        assert_eq!(extracted.update_counts, update_counts);
120        assert_eq!(
121            extracted
122                .status
123                .status()
124                .expect("status should be populated")
125                .code,
126            Code::Aborted
127        );
128    }
129
130    #[test]
131    fn extract_failure() {
132        let grpc_status = Status::default()
133            .set_code(Code::Unknown)
134            .set_message("Regular error");
135        let err = crate::Error::service(grpc_status);
136
137        let extracted = BatchUpdateError::extract(&err);
138        assert!(
139            extracted.is_none(),
140            "should not extract BatchUpdateError from standard service error"
141        );
142    }
143}