use google_cloud_bigquery_v2::model::ErrorProto;
use google_cloud_gax::error::Error;
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum QueryError {
#[error("only query jobs are supported")]
UnsupportedJobType,
#[error("cannot convert dry run query to complete query")]
DryRun,
#[error("query job failed: {errors:?}")]
JobFailed {
errors: Vec<ErrorProto>,
},
#[non_exhaustive]
#[error("the operation failed. RPC error: {source}")]
Rpc {
#[from]
#[source]
source: Error,
},
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum RowError {
#[error("could not find column: {0}")]
ColumnNotFound(String),
#[error("column index out of range: {index} (expected < {len})")]
IndexOutOfRange {
index: usize,
len: usize,
},
#[error("type conversion error for column '{column}': {source}")]
TypeConversion {
column: String,
#[source]
source: ConvertError,
},
#[error("internal service JSON layout invalid: {0}")]
InvalidRowFormat(String),
#[non_exhaustive]
#[error("the operation failed. RPC error: {source}")]
Rpc {
#[from]
#[source]
source: Error,
},
}
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum ConvertError {
#[error("type mismatch, expected {expected}, got {got:?}")]
TypeMismatch {
expected: &'static str,
got: wkt::Value,
},
#[error("expected non-null value, got null")]
NotNull,
#[error("missing field: {0}")]
MissingField(String),
#[error("cannot convert value: {0}")]
Convert(
#[from]
#[source]
Box<dyn std::error::Error + Send + Sync + 'static>,
),
}
pub use crate::write::error::AppendError;
pub use crate::write::error::AttachError;
#[cfg(test)]
mod tests {
use super::*;
use google_cloud_gax::error::rpc::{Code, Status};
#[test]
fn test_dry_run_display() {
let err = QueryError::DryRun;
assert_eq!(
err.to_string(),
"cannot convert dry run query to complete query"
);
}
#[test]
fn test_job_failed_display() {
let err = QueryError::JobFailed {
errors: vec![
ErrorProto::new()
.set_reason("invalidQuery")
.set_message("Syntax error: Unexpected end of input"),
],
};
assert!(err.to_string().contains("query job failed:"));
assert!(err.to_string().contains("invalidQuery"));
assert!(
err.to_string()
.contains("Syntax error: Unexpected end of input")
);
}
#[test]
fn test_rpc_display() {
let status = Status::default()
.set_code(Code::InvalidArgument)
.set_message("simulated bad request");
let err = QueryError::Rpc {
source: Error::service(status),
};
assert_eq!(
err.to_string(),
"the operation failed. RPC error: the service reports an error with code INVALID_ARGUMENT described as: simulated bad request"
);
}
#[test]
fn test_row_error_display() {
let err = RowError::ColumnNotFound("name".to_string());
assert_eq!(err.to_string(), "could not find column: name");
let err = RowError::IndexOutOfRange { index: 5, len: 3 };
assert_eq!(
err.to_string(),
"column index out of range: 5 (expected < 3)"
);
let err = RowError::TypeConversion {
column: "age".to_string(),
source: ConvertError::NotNull,
};
assert_eq!(
err.to_string(),
"type conversion error for column 'age': expected non-null value, got null"
);
let err = RowError::InvalidRowFormat("missing f field".to_string());
assert_eq!(
err.to_string(),
"internal service JSON layout invalid: missing f field"
);
let status = Status::default()
.set_code(Code::Internal)
.set_message("internal error");
let err = RowError::Rpc {
source: Error::service(status),
};
assert!(err.to_string().contains("the operation failed. RPC error:"));
}
#[test]
fn test_convert_error_display() {
let err = ConvertError::TypeMismatch {
expected: "i64",
got: wkt::Value::String("hello".to_string()),
};
assert_eq!(
err.to_string(),
"type mismatch, expected i64, got String(\"hello\")"
);
let err = ConvertError::NotNull;
assert_eq!(err.to_string(), "expected non-null value, got null");
let err = ConvertError::MissingField("custom_col".to_string());
assert_eq!(err.to_string(), "missing field: custom_col");
let inner_err: Box<dyn std::error::Error + Send + Sync> = "invalid integer".into();
let err = ConvertError::Convert(inner_err);
assert_eq!(err.to_string(), "cannot convert value: invalid integer");
}
}