Skip to main content

google_cloud_bigquery/
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_bigquery_v2::model::ErrorProto;
16use google_cloud_gax::error::Error;
17
18/// Errors that can occur during query configuration, execution, or polling.
19#[derive(thiserror::Error, Debug)]
20#[non_exhaustive]
21pub enum QueryError {
22    /// Only query jobs are supported by this client.
23    #[error("only query jobs are supported")]
24    UnsupportedJobType,
25
26    /// Dry run queries cannot be converted to a complete query.
27    #[error("cannot convert dry run query to complete query")]
28    DryRun,
29
30    /// The query job failed on the BigQuery service side.
31    /// Includes the list of error protocols returned by the service.
32    #[error("query job failed: {errors:?}")]
33    JobFailed {
34        /// The list of all errors associated with the job.
35        errors: Vec<ErrorProto>,
36    },
37
38    /// The underlying RPC failed.
39    #[non_exhaustive]
40    #[error("the operation failed. RPC error: {source}")]
41    Rpc {
42        /// The error returned by the service for the request.
43        #[from]
44        #[source]
45        source: Error,
46    },
47}
48
49/// Errors that can occur when retrieving value cells from a Row or iterating over query results.
50#[derive(thiserror::Error, Debug)]
51#[non_exhaustive]
52pub enum RowError {
53    /// The requested column name or index was not found in the row.
54    #[error("could not find column: {0}")]
55    ColumnNotFound(String),
56
57    /// The requested column index was out of range.
58    #[error("column index out of range: {index} (expected < {len})")]
59    IndexOutOfRange {
60        /// The index that was requested.
61        index: usize,
62        /// The total number of columns in the row.
63        len: usize,
64    },
65
66    /// Failed to convert/parse the cell value to the target type.
67    #[error("type conversion error for column '{column}': {source}")]
68    TypeConversion {
69        /// The column identifier (name or index).
70        column: String,
71        /// The underlying parsing error.
72        #[source]
73        source: ConvertError,
74    },
75
76    /// The JSON format returned by the service did not match expectations.
77    #[error("internal service JSON layout invalid: {0}")]
78    InvalidRowFormat(String),
79
80    /// The underlying RPC failed.
81    #[non_exhaustive]
82    #[error("the operation failed. RPC error: {source}")]
83    Rpc {
84        /// The error returned by the service for the request.
85        #[from]
86        #[source]
87        source: Error,
88    },
89}
90
91/// Represents failures when converting a raw BigQuery cell value (`wkt::Value`) to a Rust type.
92#[derive(thiserror::Error, Debug)]
93#[non_exhaustive]
94pub enum ConvertError {
95    /// The value type did not match the expected type.
96    #[error("type mismatch, expected {expected}, got {got:?}")]
97    TypeMismatch {
98        /// The expected type name.
99        expected: &'static str,
100        /// The actual value received.
101        got: wkt::Value,
102    },
103
104    /// The value was null, but the target type does not support nulls (non-Option).
105    #[error("expected non-null value, got null")]
106    NotNull,
107
108    /// A required field or element was missing during SQL type conversion.
109    #[error("missing field: {0}")]
110    MissingField(String),
111
112    /// An error occurred during custom conversion (e.g. parsing date/time strings).
113    #[error("cannot convert value: {0}")]
114    Convert(
115        #[from]
116        #[source]
117        Box<dyn std::error::Error + Send + Sync + 'static>,
118    ),
119}
120
121// TODO(#6443) - consolidate crates
122pub use crate::write::error::AppendError;
123pub use crate::write::error::AttachError;
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use google_cloud_gax::error::rpc::{Code, Status};
129
130    #[test]
131    fn test_dry_run_display() {
132        let err = QueryError::DryRun;
133        assert_eq!(
134            err.to_string(),
135            "cannot convert dry run query to complete query"
136        );
137    }
138
139    #[test]
140    fn test_job_failed_display() {
141        let err = QueryError::JobFailed {
142            errors: vec![
143                ErrorProto::new()
144                    .set_reason("invalidQuery")
145                    .set_message("Syntax error: Unexpected end of input"),
146            ],
147        };
148        assert!(err.to_string().contains("query job failed:"));
149        assert!(err.to_string().contains("invalidQuery"));
150        assert!(
151            err.to_string()
152                .contains("Syntax error: Unexpected end of input")
153        );
154    }
155
156    #[test]
157    fn test_rpc_display() {
158        let status = Status::default()
159            .set_code(Code::InvalidArgument)
160            .set_message("simulated bad request");
161        let err = QueryError::Rpc {
162            source: Error::service(status),
163        };
164        assert_eq!(
165            err.to_string(),
166            "the operation failed. RPC error: the service reports an error with code INVALID_ARGUMENT described as: simulated bad request"
167        );
168    }
169
170    #[test]
171    fn test_row_error_display() {
172        let err = RowError::ColumnNotFound("name".to_string());
173        assert_eq!(err.to_string(), "could not find column: name");
174
175        let err = RowError::IndexOutOfRange { index: 5, len: 3 };
176        assert_eq!(
177            err.to_string(),
178            "column index out of range: 5 (expected < 3)"
179        );
180
181        let err = RowError::TypeConversion {
182            column: "age".to_string(),
183            source: ConvertError::NotNull,
184        };
185        assert_eq!(
186            err.to_string(),
187            "type conversion error for column 'age': expected non-null value, got null"
188        );
189
190        let err = RowError::InvalidRowFormat("missing f field".to_string());
191        assert_eq!(
192            err.to_string(),
193            "internal service JSON layout invalid: missing f field"
194        );
195
196        let status = Status::default()
197            .set_code(Code::Internal)
198            .set_message("internal error");
199        let err = RowError::Rpc {
200            source: Error::service(status),
201        };
202        assert!(err.to_string().contains("the operation failed. RPC error:"));
203    }
204
205    #[test]
206    fn test_convert_error_display() {
207        let err = ConvertError::TypeMismatch {
208            expected: "i64",
209            got: wkt::Value::String("hello".to_string()),
210        };
211        assert_eq!(
212            err.to_string(),
213            "type mismatch, expected i64, got String(\"hello\")"
214        );
215
216        let err = ConvertError::NotNull;
217        assert_eq!(err.to_string(), "expected non-null value, got null");
218
219        let err = ConvertError::MissingField("custom_col".to_string());
220        assert_eq!(err.to_string(), "missing field: custom_col");
221
222        let inner_err: Box<dyn std::error::Error + Send + Sync> = "invalid integer".into();
223        let err = ConvertError::Convert(inner_err);
224        assert_eq!(err.to_string(), "cannot convert value: invalid integer");
225    }
226}