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}