use surrealdb_types::{
AlreadyExistsError, AuthError, Chain, ConfigurationError, ConnectionError, ConversionError,
Error, ErrorDetails, Kind, LengthMismatchError, NotAllowedError, NotFoundError, Number, Object,
OutOfRangeError, QueryError, SerializationError, SurrealValue, TypeError, ValidationError,
Value,
};
#[test]
fn test_conversion_error_basic() {
let err = ConversionError::new(Kind::String, Kind::Int);
assert_eq!(err.expected, Kind::String);
assert_eq!(err.actual, Kind::Int);
assert!(err.context.is_none());
let msg = err.to_string();
assert!(msg.contains("string"));
assert!(msg.contains("int"));
}
#[test]
fn test_conversion_error_from_value() {
let value = Value::Number(42.into());
let err = ConversionError::from_value(Kind::String, &value);
assert_eq!(err.expected, Kind::String);
assert_eq!(err.actual, Kind::Number);
}
#[test]
fn test_conversion_error_with_context() {
let err =
ConversionError::new(Kind::String, Kind::Int).with_context("field 'name' on struct User");
assert_eq!(err.context, Some("field 'name' on struct User".to_string()));
let msg = err.to_string();
assert!(msg.contains("field 'name' on struct User"));
}
#[test]
fn test_out_of_range_error() {
let err = OutOfRangeError::new(300, "i8");
assert_eq!(err.value, "300");
assert_eq!(err.target_type, "i8");
assert!(err.context.is_none());
let msg = err.to_string();
assert!(msg.contains("300"));
assert!(msg.contains("i8"));
assert!(msg.contains("out of range"));
}
#[test]
fn test_out_of_range_error_with_context() {
let err = OutOfRangeError::new(-1, "u8").with_context("array index");
assert_eq!(err.context, Some("array index".to_string()));
let msg = err.to_string();
assert!(msg.contains("array index"));
}
#[test]
fn test_length_mismatch_error() {
let err = LengthMismatchError::new(5, 3, "Vec<String>");
assert_eq!(err.expected, 5);
assert_eq!(err.actual, 3);
assert_eq!(err.target_type, "Vec<String>");
let msg = err.to_string();
assert!(msg.contains("5"));
assert!(msg.contains("3"));
assert!(msg.contains("Vec<String>"));
assert!(msg.contains("Length mismatch"));
}
#[test]
fn test_type_error_variants() {
let conv_err = TypeError::Conversion(ConversionError::new(Kind::String, Kind::Int));
let range_err = TypeError::OutOfRange(OutOfRangeError::new(256, "u8"));
let len_err = TypeError::LengthMismatch(LengthMismatchError::new(3, 2, "tuple"));
let invalid_err = TypeError::Invalid("custom error".to_owned());
assert!(!conv_err.to_string().is_empty());
assert!(!range_err.to_string().is_empty());
assert!(!len_err.to_string().is_empty());
assert!(!invalid_err.to_string().is_empty());
}
#[test]
fn test_conversion_error_in_practice() {
let value = Value::Number(42.into());
let result: Result<String, _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("string"));
assert!(err_msg.contains("number"));
}
#[test]
fn test_out_of_range_error_in_practice() {
let value = Value::Number(300.into());
let result: Result<i8, _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("300"));
assert!(err_msg.contains("out of range") || err_msg.contains("i8"));
}
#[test]
fn test_length_mismatch_in_practice() {
let value = Value::Array(vec![Value::Number(1.into()), Value::Number(2.into())].into());
let result: Result<(i64, i64, i64), _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("3") && err_msg.contains("2") || err_msg.contains("Length"));
}
#[test]
fn test_u8_overflow() {
let value = Value::Number(256.into());
let result: Result<u8, _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("256"));
assert!(err_msg.contains("u8") || err_msg.contains("out of range"));
}
#[test]
fn test_u8_underflow() {
let value = Value::Number((-1).into());
let result: Result<u8, _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("-1") || err_msg.contains("out of range"));
}
#[test]
fn test_i8_overflow() {
let value = Value::Number(128.into());
let result: Result<i8, _> = value.into_t();
assert!(result.is_err());
}
#[test]
fn test_i8_underflow() {
let value = Value::Number((-129).into());
let result: Result<i8, _> = value.into_t();
assert!(result.is_err());
}
#[test]
fn test_u64_success() {
let value = Value::Number(Number::Int(i64::MAX));
let result: Result<u64, _> = value.into_t();
assert!(result.is_ok());
}
#[test]
fn test_array_length_mismatch_tuple() {
let value = Value::Array(vec![Value::Number(1.into()), Value::Number(2.into())].into());
let result: Result<(i64, i64, i64), _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("Length") || (err_msg.contains("3") && err_msg.contains("2")));
}
#[test]
fn test_fixed_array_length_mismatch() {
let value = Value::Array(vec![Value::Number(1.into()), Value::Number(2.into())].into());
let result: Result<[i64; 5], _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("5") && err_msg.contains("2"));
}
#[test]
fn test_nested_conversion_error() {
let value = Value::Array(
vec![
Value::String("hello".to_string()),
Value::Number(42.into()), ]
.into(),
);
let result: Result<Vec<String>, _> = value.into_t();
assert!(result.is_err());
let err = result.unwrap_err();
let err_msg = err.to_string();
assert!(err_msg.contains("string") || err_msg.contains("number"));
}
#[test]
fn test_conversion_errors_implement_std_error() {
let conv_err = ConversionError::new(Kind::String, Kind::Int);
assert!(!conv_err.to_string().is_empty());
let range_err = OutOfRangeError::new(300, "i8");
assert!(!range_err.to_string().is_empty());
let len_err = LengthMismatchError::new(3, 2, "tuple");
assert!(!len_err.to_string().is_empty());
}
#[test]
fn test_multiple_numeric_types() {
let values = vec![
(Value::Number(0.into()), true), (Value::Number(255.into()), true), (Value::Number(256.into()), false), (Value::Number((-1).into()), false), ];
for (value, should_succeed) in values {
let result: Result<u8, _> = value.into_t();
assert_eq!(result.is_ok(), should_succeed, "Failed for u8 with {:?}", result);
}
}
#[test]
fn test_error_message_quality() {
let value = Value::String("hello".to_string());
let result: Result<i64, _> = value.into_t();
let err_msg = result.unwrap_err().to_string();
assert!(err_msg.to_lowercase().contains("int") || err_msg.to_lowercase().contains("number"));
assert!(err_msg.contains("String") || err_msg.contains("string"));
println!("Error message: {}", err_msg);
}
#[test]
fn test_public_error_new() {
let err = Error::not_found("The table 'users' does not exist".to_string(), None);
assert!(err.is_not_found());
assert_eq!(err.message(), "The table 'users' does not exist");
assert!(err.not_found_details().is_none());
}
#[test]
fn test_public_error_with_details() {
let err = Error::not_allowed("Token expired".to_string(), AuthError::TokenExpired);
assert!(err.is_not_allowed());
let d = err.not_allowed_details().unwrap();
assert!(matches!(d, NotAllowedError::Auth(AuthError::TokenExpired)));
}
#[test]
fn test_public_error_validation_details() {
let err =
Error::validation("Invalid request".to_string(), Some(ValidationError::InvalidRequest));
assert!(err.is_validation());
assert_eq!(err.validation_details(), Some(&ValidationError::InvalidRequest));
let err_no_details = Error::validation("Parse error".to_string(), None);
assert_eq!(err_no_details.validation_details(), None);
assert!(err_no_details.is_validation());
let err_wrong_kind = Error::not_allowed("Auth failed".to_string(), None);
assert_eq!(err_wrong_kind.validation_details(), None);
}
#[test]
fn test_public_error_display() {
let err = Error::thrown("Something went wrong".to_string());
assert!(err.to_string().contains("Something went wrong"));
}
#[test]
fn test_public_error_details_pattern_matching() {
let err = Error::not_found(
"Table not found".to_string(),
NotFoundError::Table {
name: "users".into(),
},
);
match err.details() {
ErrorDetails::NotFound(Some(NotFoundError::Table {
name,
})) => assert_eq!(name, "users"),
_ => panic!("Expected NotFound Table details"),
}
}
#[test]
fn test_error_kind_unknown_falls_back_to_internal() {
let mut obj = Object::new();
obj.insert("kind", "future_kind");
obj.insert("message", "Message");
let value = Value::Object(obj);
let err = Error::from_value(value).unwrap();
assert!(err.is_internal());
assert_eq!(err.message(), "Message");
}
#[test]
fn test_error_deserialize_without_kind_defaults_to_internal() {
let mut obj = Object::new();
obj.insert("message", "Something went wrong");
let value = Value::Object(obj);
let err = Error::from_value(value).unwrap();
assert!(err.is_internal());
assert_eq!(err.message(), "Something went wrong");
}
#[test]
fn test_detail_wire_format_unit_variant() {
let val = AuthError::TokenExpired.into_value();
let Value::Object(obj) = &val else {
panic!("Expected object, got {val:?}");
};
assert_eq!(obj.get("kind"), Some(&Value::String("TokenExpired".into())));
assert!(!obj.contains_key("details"), "Unit variant should not have details field");
let parsed = AuthError::from_value(val).unwrap();
assert_eq!(parsed, AuthError::TokenExpired);
}
#[test]
fn test_detail_wire_format_struct_variant() {
let val = AuthError::InvalidRole {
name: "admin".into(),
}
.into_value();
let Value::Object(obj) = &val else {
panic!("Expected object, got {val:?}");
};
assert_eq!(obj.get("kind"), Some(&Value::String("InvalidRole".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details object");
};
assert_eq!(details.get("name"), Some(&Value::String("admin".into())));
let parsed = AuthError::from_value(val).unwrap();
assert_eq!(
parsed,
AuthError::InvalidRole {
name: "admin".into()
}
);
}
#[test]
fn test_detail_wire_format_newtype_variant() {
let val = NotAllowedError::Auth(AuthError::TokenExpired).into_value();
let Value::Object(obj) = &val else {
panic!("Expected object, got {val:?}");
};
assert_eq!(obj.get("kind"), Some(&Value::String("Auth".into())));
let Some(Value::Object(inner)) = obj.get("details") else {
panic!("Expected details object with inner auth error");
};
assert_eq!(inner.get("kind"), Some(&Value::String("TokenExpired".into())));
assert!(!inner.contains_key("details"), "Inner unit variant should not have details");
let parsed = NotAllowedError::from_value(val).unwrap();
assert_eq!(parsed, NotAllowedError::Auth(AuthError::TokenExpired));
}
#[test]
fn test_detail_wire_format_newtype_with_struct_inner() {
let val = NotAllowedError::Auth(AuthError::NotAllowed {
actor: "user:john".into(),
action: "edit".into(),
resource: "table:secrets".into(),
})
.into_value();
let Value::Object(obj) = &val else {
panic!("Expected object");
};
assert_eq!(obj.get("kind"), Some(&Value::String("Auth".into())));
let Some(Value::Object(inner)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(inner.get("kind"), Some(&Value::String("NotAllowed".into())));
let Some(Value::Object(inner_details)) = inner.get("details") else {
panic!("Expected inner details");
};
assert_eq!(inner_details.get("actor"), Some(&Value::String("user:john".into())));
let parsed = NotAllowedError::from_value(val).unwrap();
assert!(matches!(parsed, NotAllowedError::Auth(AuthError::NotAllowed { .. })));
}
#[test]
fn test_detail_wire_format_all_flat_enums() {
let val = ValidationError::Parse.into_value();
let parsed = ValidationError::from_value(val).unwrap();
assert_eq!(parsed, ValidationError::Parse);
let val = ValidationError::InvalidParameter {
name: "x".into(),
}
.into_value();
let parsed = ValidationError::from_value(val).unwrap();
assert_eq!(
parsed,
ValidationError::InvalidParameter {
name: "x".into()
}
);
let val = ConfigurationError::LiveQueryNotSupported.into_value();
let parsed = ConfigurationError::from_value(val).unwrap();
assert_eq!(parsed, ConfigurationError::LiveQueryNotSupported);
let val = SerializationError::Deserialization.into_value();
let parsed = SerializationError::from_value(val).unwrap();
assert_eq!(parsed, SerializationError::Deserialization);
let val = NotFoundError::Table {
name: "users".into(),
}
.into_value();
let Value::Object(obj) = &val else {
panic!("Expected object");
};
assert_eq!(obj.get("kind"), Some(&Value::String("Table".into())));
let parsed = NotFoundError::from_value(val).unwrap();
assert_eq!(
parsed,
NotFoundError::Table {
name: "users".into()
}
);
let val = NotFoundError::Transaction.into_value();
let parsed = NotFoundError::from_value(val).unwrap();
assert_eq!(parsed, NotFoundError::Transaction);
let val = AlreadyExistsError::Record {
id: "users:123".into(),
}
.into_value();
let parsed = AlreadyExistsError::from_value(val).unwrap();
assert_eq!(
parsed,
AlreadyExistsError::Record {
id: "users:123".into()
}
);
let val = ConnectionError::Uninitialised.into_value();
let parsed = ConnectionError::from_value(val).unwrap();
assert_eq!(parsed, ConnectionError::Uninitialised);
}
#[test]
fn test_detail_wire_format_full_error_round_trip() {
let err = Error::not_allowed("Token expired".to_string(), AuthError::TokenExpired);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
assert_eq!(parsed.message(), "Token expired");
let details = parsed.not_allowed_details().unwrap();
assert!(matches!(details, NotAllowedError::Auth(AuthError::TokenExpired)));
}
#[test]
fn test_detail_wire_format_query_timeout() {
use std::time::Duration;
let val = QueryError::TimedOut {
duration: Duration::from_secs(5),
}
.into_value();
let Value::Object(obj) = &val else {
panic!("Expected object");
};
assert_eq!(obj.get("kind"), Some(&Value::String("TimedOut".into())));
assert!(obj.contains_key("details"), "Struct variant should have details field");
let parsed = QueryError::from_value(val).unwrap();
assert!(
matches!(parsed, QueryError::TimedOut { duration } if duration == Duration::from_secs(5))
);
}
#[test]
fn test_error_snapshot_not_allowed_auth_token_expired() {
let err = Error::not_allowed("Token expired".to_string(), AuthError::TokenExpired);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
assert_eq!(obj.get("kind"), Some(&Value::String("NotAllowed".into())));
assert_eq!(obj.get("message"), Some(&Value::String("Token expired".into())));
assert!(obj.get("cause").is_none_or(|v| *v == Value::None));
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details object");
};
assert_eq!(details.get("kind"), Some(&Value::String("Auth".into())));
let Some(Value::Object(inner)) = details.get("details") else {
panic!("Expected inner details");
};
assert_eq!(inner.get("kind"), Some(&Value::String("TokenExpired".into())));
assert!(!inner.contains_key("details"), "Unit variant should not have inner details key");
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
assert_eq!(parsed.message(), "Token expired");
let details = parsed.not_allowed_details().unwrap();
assert!(matches!(details, NotAllowedError::Auth(AuthError::TokenExpired)));
}
#[test]
fn test_error_snapshot_not_allowed_auth_invalid_role() {
let err = Error::not_allowed(
"Bad role".to_string(),
AuthError::InvalidRole {
name: "admin".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Auth".into())));
let Some(Value::Object(auth_details)) = details.get("details") else {
panic!("Expected auth details");
};
assert_eq!(auth_details.get("kind"), Some(&Value::String("InvalidRole".into())));
let Some(Value::Object(role_details)) = auth_details.get("details") else {
panic!("Expected role details");
};
assert_eq!(role_details.get("name"), Some(&Value::String("admin".into())));
let parsed = Error::from_value(val).unwrap();
let details = parsed.not_allowed_details().unwrap();
assert!(matches!(
details,
NotAllowedError::Auth(AuthError::InvalidRole { name }) if name == "admin"
));
}
#[test]
fn test_error_snapshot_not_found_table() {
let err = Error::not_found(
"Table not found".to_string(),
NotFoundError::Table {
name: "users".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Table".into())));
let Some(Value::Object(table_details)) = details.get("details") else {
panic!("Expected table details");
};
assert_eq!(table_details.get("name"), Some(&Value::String("users".into())));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
let details = parsed.not_found_details().unwrap();
assert!(matches!(
details,
NotFoundError::Table { name } if name == "users"
));
}
#[test]
fn test_error_snapshot_validation_parse() {
let err = Error::validation("Parse error".to_string(), ValidationError::Parse);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("Validation".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Parse".into())));
assert!(!details.contains_key("details"), "Unit variant should not have inner details key");
let parsed = Error::from_value(val).unwrap();
assert_eq!(parsed.validation_details(), Some(&ValidationError::Parse));
}
#[test]
fn test_error_snapshot_query_timed_out() {
use std::time::Duration;
let err = Error::query(
"Timed out".to_string(),
QueryError::TimedOut {
duration: Duration::from_secs(5),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
assert_eq!(obj.get("kind"), Some(&Value::String("Query".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("TimedOut".into())));
assert!(details.contains_key("details"), "Struct variant should have details");
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_query());
let details = parsed.query_details().unwrap();
assert!(matches!(
details,
QueryError::TimedOut { duration } if *duration == Duration::from_secs(5)
));
}
#[test]
fn test_error_snapshot_already_exists_record() {
let err = Error::already_exists(
"Record exists".to_string(),
AlreadyExistsError::Record {
id: "users:123".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Record".into())));
let Some(Value::Object(record_details)) = details.get("details") else {
panic!("Expected record details");
};
assert_eq!(record_details.get("id"), Some(&Value::String("users:123".into())));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_already_exists());
let details = parsed.already_exists_details().unwrap();
assert!(matches!(
details,
AlreadyExistsError::Record { id } if id == "users:123"
));
}
#[test]
fn test_error_snapshot_not_allowed_method() {
let err = Error::not_allowed(
"Method blocked".to_string(),
NotAllowedError::Method {
name: "begin".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Method".into())));
let Some(Value::Object(method_details)) = details.get("details") else {
panic!("Expected method details");
};
assert_eq!(method_details.get("name"), Some(&Value::String("begin".into())));
let parsed = Error::from_value(val).unwrap();
let details = parsed.not_allowed_details().unwrap();
assert!(matches!(
details,
NotAllowedError::Method { name } if name == "begin"
));
}
#[test]
fn test_error_snapshot_internal_no_details() {
let err = Error::internal("Unexpected".to_string());
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
assert_eq!(obj.get("kind"), Some(&Value::String("Internal".into())));
assert_eq!(obj.get("message"), Some(&Value::String("Unexpected".into())));
assert!(!obj.contains_key("details"), "No details should be present");
assert!(obj.get("cause").is_none_or(|v| *v == Value::None));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_internal());
assert_eq!(parsed.message(), "Unexpected");
assert!(matches!(parsed.details(), ErrorDetails::Internal));
}
#[test]
fn test_error_snapshot_thrown_no_details() {
let err = Error::thrown("custom error".to_string());
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("Thrown".into())));
assert_eq!(obj.get("message"), Some(&Value::String("custom error".into())));
assert!(!obj.contains_key("details"));
assert!(obj.get("cause").is_none_or(|v| *v == Value::None));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_thrown());
assert_eq!(parsed.message(), "custom error");
assert!(matches!(parsed.details(), ErrorDetails::Thrown));
}
#[test]
fn test_error_snapshot_not_allowed_scripting_unit() {
let err = Error::not_allowed("Scripting not allowed".to_string(), NotAllowedError::Scripting);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("NotAllowed".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Scripting".into())));
assert!(!details.contains_key("details"), "Unit variant should not have inner details");
let parsed = Error::from_value(val).unwrap();
let details = parsed.not_allowed_details().unwrap();
assert_eq!(details, &NotAllowedError::Scripting);
}
#[test]
fn test_error_wire_validation_parse() {
let err = Error::validation("Failed to parse query".into(), ValidationError::Parse);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!("Expected object");
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32700))));
assert_eq!(obj.get("message"), Some(&Value::String("Failed to parse query".into())));
assert_eq!(obj.get("kind"), Some(&Value::String("Validation".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!("Expected details");
};
assert_eq!(details.get("kind"), Some(&Value::String("Parse".into())));
assert!(!details.contains_key("details"), "Unit variant has no inner details");
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_validation());
assert_eq!(parsed.validation_details(), Some(&ValidationError::Parse));
assert_eq!(parsed.message(), "Failed to parse query");
}
#[test]
fn test_error_wire_validation_invalid_params() {
let err = Error::validation("Invalid parameters".into(), ValidationError::InvalidParams);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32603))));
assert_eq!(obj.get("kind"), Some(&Value::String("Validation".into())));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_validation());
assert_eq!(parsed.validation_details(), Some(&ValidationError::InvalidParams));
}
#[test]
fn test_error_wire_validation_invalid_parameter_with_name() {
let err = Error::validation(
"Invalid parameter 'limit'".into(),
ValidationError::InvalidParameter {
name: "limit".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("Validation".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!();
};
assert_eq!(details.get("kind"), Some(&Value::String("InvalidParameter".into())));
let Some(Value::Object(inner)) = details.get("details") else {
panic!();
};
assert_eq!(inner.get("name"), Some(&Value::String("limit".into())));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_validation());
assert_eq!(
parsed.validation_details(),
Some(&ValidationError::InvalidParameter {
name: "limit".into()
})
);
}
#[test]
fn test_error_wire_validation_no_details() {
let err = Error::validation("Something invalid".into(), None);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("Validation".into())));
assert!(!obj.contains_key("details"), "No details when None");
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_validation());
assert_eq!(parsed.validation_details(), None);
}
#[test]
fn test_error_wire_not_allowed_auth_session_expired() {
let err = Error::not_allowed("Session has expired".into(), AuthError::SessionExpired);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("NotAllowed".into())));
let Some(Value::Object(details)) = obj.get("details") else {
panic!();
};
assert_eq!(details.get("kind"), Some(&Value::String("Auth".into())));
let Some(Value::Object(auth)) = details.get("details") else {
panic!();
};
assert_eq!(auth.get("kind"), Some(&Value::String("SessionExpired".into())));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
assert!(matches!(
parsed.not_allowed_details(),
Some(NotAllowedError::Auth(AuthError::SessionExpired))
));
}
#[test]
fn test_error_wire_not_allowed_auth_not_allowed_iam() {
let err = Error::not_allowed(
"Not enough permissions".into(),
AuthError::NotAllowed {
actor: "user:john".into(),
action: "edit".into(),
resource: "table:secrets".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("kind"), Some(&Value::String("NotAllowed".into())));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
match parsed.not_allowed_details() {
Some(NotAllowedError::Auth(AuthError::NotAllowed {
actor,
action,
resource,
})) => {
assert_eq!(actor, "user:john");
assert_eq!(action, "edit");
assert_eq!(resource, "table:secrets");
}
other => panic!("Expected Auth::NotAllowed, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_allowed_function() {
let err = Error::not_allowed(
"Function not allowed".into(),
NotAllowedError::Function {
name: "fn::custom".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
match parsed.not_allowed_details() {
Some(NotAllowedError::Function {
name,
}) => assert_eq!(name, "fn::custom"),
other => panic!("Expected Function, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_allowed_target() {
let err = Error::not_allowed(
"Net target not allowed".into(),
NotAllowedError::Target {
name: "example.com".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
match parsed.not_allowed_details() {
Some(NotAllowedError::Target {
name,
}) => assert_eq!(name, "example.com"),
other => panic!("Expected Target, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_allowed_no_details() {
let err = Error::not_allowed("Not allowed".into(), None);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert!(!obj.contains_key("details"));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_allowed());
assert_eq!(parsed.not_allowed_details(), None);
}
#[test]
fn test_error_wire_not_found_record() {
let err = Error::not_found(
"Record not found".into(),
NotFoundError::Record {
id: "person:jane".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
match parsed.not_found_details() {
Some(NotFoundError::Record {
id,
}) => assert_eq!(id, "person:jane"),
other => panic!("Expected Record, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_found_namespace() {
let err = Error::not_found(
"Namespace not found".into(),
NotFoundError::Namespace {
name: "test_ns".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
match parsed.not_found_details() {
Some(NotFoundError::Namespace {
name,
}) => assert_eq!(name, "test_ns"),
other => panic!("Expected Namespace, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_found_database() {
let err = Error::not_found(
"Database not found".into(),
NotFoundError::Database {
name: "test_db".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
match parsed.not_found_details() {
Some(NotFoundError::Database {
name,
}) => assert_eq!(name, "test_db"),
other => panic!("Expected Database, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_found_method() {
let err = Error::not_found(
"Method not found".into(),
NotFoundError::Method {
name: "unknown_method".into(),
},
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32601))));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
match parsed.not_found_details() {
Some(NotFoundError::Method {
name,
}) => assert_eq!(name, "unknown_method"),
other => panic!("Expected Method, got {other:?}"),
}
}
#[test]
fn test_error_wire_not_found_transaction() {
let err = Error::not_found("Transaction not found".into(), NotFoundError::Transaction);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
let Some(Value::Object(details)) = obj.get("details") else {
panic!();
};
assert_eq!(details.get("kind"), Some(&Value::String("Transaction".into())));
assert!(!details.contains_key("details"), "Unit variant has no inner details");
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
assert_eq!(parsed.not_found_details(), Some(&NotFoundError::Transaction));
}
#[test]
fn test_error_wire_not_found_session() {
let err = Error::not_found(
"Session not found".into(),
NotFoundError::Session {
id: Some("abc-123".into()),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_not_found());
match parsed.not_found_details() {
Some(NotFoundError::Session {
id,
}) => assert_eq!(id, &Some("abc-123".to_string())),
other => panic!("Expected Session, got {other:?}"),
}
}
#[test]
fn test_error_wire_query_cancelled() {
let err = Error::query("Query was cancelled".into(), QueryError::Cancelled);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32005))));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_query());
assert_eq!(parsed.query_details(), Some(&QueryError::Cancelled));
}
#[test]
fn test_error_wire_query_transaction_conflict() {
let err = Error::query("Transaction conflict".into(), QueryError::TransactionConflict);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32009))));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_query());
assert_eq!(parsed.query_details(), Some(&QueryError::TransactionConflict));
}
#[test]
fn test_error_wire_query_not_executed() {
let err = Error::query("Query not executed".into(), QueryError::NotExecuted);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_query());
assert_eq!(parsed.query_details(), Some(&QueryError::NotExecuted));
}
#[test]
fn test_error_wire_query_timed_out() {
use std::time::Duration;
let err = Error::query(
"Query timed out".into(),
QueryError::TimedOut {
duration: Duration::from_secs(30),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_query());
match parsed.query_details() {
Some(QueryError::TimedOut {
duration,
}) => assert_eq!(*duration, Duration::from_secs(30)),
other => panic!("Expected TimedOut, got {other:?}"),
}
}
#[test]
fn test_error_wire_already_exists_table() {
let err = Error::already_exists(
"Table already exists".into(),
AlreadyExistsError::Table {
name: "users".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_already_exists());
match parsed.already_exists_details() {
Some(AlreadyExistsError::Table {
name,
}) => assert_eq!(name, "users"),
other => panic!("Expected Table, got {other:?}"),
}
}
#[test]
fn test_error_wire_already_exists_namespace() {
let err = Error::already_exists(
"Namespace already exists".into(),
AlreadyExistsError::Namespace {
name: "prod".into(),
},
);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_already_exists());
match parsed.already_exists_details() {
Some(AlreadyExistsError::Namespace {
name,
}) => assert_eq!(name, "prod"),
other => panic!("Expected Namespace, got {other:?}"),
}
}
#[test]
fn test_error_wire_configuration_live_query() {
let err = Error::configuration(
"Live queries not supported".into(),
ConfigurationError::LiveQueryNotSupported,
);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32604))));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_configuration());
assert_eq!(parsed.configuration_details(), Some(&ConfigurationError::LiveQueryNotSupported));
}
#[test]
fn test_error_wire_configuration_bad_graphql() {
let err =
Error::configuration("Bad GraphQL config".into(), ConfigurationError::BadGraphqlConfig);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_configuration());
assert_eq!(parsed.configuration_details(), Some(&ConfigurationError::BadGraphqlConfig));
}
#[test]
fn test_error_wire_serialization_error() {
let err = Error::serialization("Failed to serialize".into(), SerializationError::Serialization);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_serialization());
assert_eq!(parsed.serialization_details(), Some(&SerializationError::Serialization));
}
#[test]
fn test_error_wire_deserialization_error() {
let err =
Error::serialization("Failed to deserialize".into(), SerializationError::Deserialization);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32008))));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_serialization());
assert_eq!(parsed.serialization_details(), Some(&SerializationError::Deserialization));
}
#[test]
fn test_error_wire_connection_uninitialised() {
let err =
Error::connection("Connection not initialised".into(), ConnectionError::Uninitialised);
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32001))));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_connection());
assert_eq!(parsed.connection_details(), Some(&ConnectionError::Uninitialised));
}
#[test]
fn test_error_wire_connection_already_connected() {
let err = Error::connection("Already connected".into(), ConnectionError::AlreadyConnected);
let val = err.into_value();
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_connection());
assert_eq!(parsed.connection_details(), Some(&ConnectionError::AlreadyConnected));
}
#[test]
fn test_error_wire_thrown() {
let err = Error::thrown("An error occurred: user validation failed".into());
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32006))));
assert_eq!(obj.get("kind"), Some(&Value::String("Thrown".into())));
assert!(!obj.contains_key("details"));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_thrown());
assert_eq!(parsed.message(), "An error occurred: user validation failed");
}
#[test]
fn test_error_wire_internal() {
let err = Error::internal("An unexpected error occurred".into());
let val = err.into_value();
let Value::Object(ref obj) = val else {
panic!();
};
assert_eq!(obj.get("code"), Some(&Value::Number(Number::Int(-32000))));
assert_eq!(obj.get("kind"), Some(&Value::String("Internal".into())));
assert!(!obj.contains_key("details"));
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_internal());
assert_eq!(parsed.message(), "An unexpected error occurred");
}
#[test]
fn test_error_wire_unknown_kind_falls_back_to_internal() {
let mut obj = Object::new();
obj.insert("kind", Value::String("RateLimit".into()));
obj.insert("message", Value::String("Too many requests".into()));
let val = Value::Object(obj);
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_internal()); assert_eq!(parsed.message(), "Too many requests");
}
#[test]
fn test_error_wire_missing_kind_defaults_to_internal() {
let mut obj = Object::new();
obj.insert("code", Value::Number(Number::Int(-32000)));
obj.insert("message", Value::String("Legacy error".into()));
let val = Value::Object(obj);
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_internal());
assert_eq!(parsed.message(), "Legacy error");
}
#[test]
fn test_error_wire_missing_code_defaults_to_internal_code() {
let mut obj = Object::new();
obj.insert("kind", Value::String("Thrown".into()));
obj.insert("message", Value::String("Custom error".into()));
let val = Value::Object(obj);
let parsed = Error::from_value(val).unwrap();
assert!(parsed.is_thrown());
assert_eq!(parsed.message(), "Custom error");
}
#[test]
fn test_error_wire_details_with_unknown_inner_kind() {
let err = Error::from_parts(
"Not found".into(),
Some("NotFound"),
Some({
let mut details_obj = Object::new();
details_obj.insert("kind", Value::String("FutureResource".into()));
let mut inner = Object::new();
inner.insert("id", Value::String("xyz".into()));
details_obj.insert("details", Value::Object(inner));
Value::Object(details_obj)
}),
);
assert!(err.is_not_found());
assert_eq!(err.not_found_details(), None);
}
#[test]
fn test_error_utility_methods() {
let validation = Error::validation("test".into(), None);
assert!(validation.is_validation());
assert!(!validation.is_not_allowed());
assert!(!validation.is_internal());
assert_eq!(validation.kind_str(), "Validation");
let not_allowed = Error::not_allowed("test".into(), None);
assert!(not_allowed.is_not_allowed());
assert!(!not_allowed.is_validation());
assert_eq!(not_allowed.kind_str(), "NotAllowed");
let not_found = Error::not_found("test".into(), None);
assert!(not_found.is_not_found());
assert_eq!(not_found.kind_str(), "NotFound");
let already_exists = Error::already_exists("test".into(), None);
assert!(already_exists.is_already_exists());
assert_eq!(already_exists.kind_str(), "AlreadyExists");
let query = Error::query("test".into(), None);
assert!(query.is_query());
assert_eq!(query.kind_str(), "Query");
let config = Error::configuration("test".into(), None);
assert!(config.is_configuration());
assert_eq!(config.kind_str(), "Configuration");
let serialization = Error::serialization("test".into(), None);
assert!(serialization.is_serialization());
assert_eq!(serialization.kind_str(), "Serialization");
let connection = Error::connection("test".into(), None);
assert!(connection.is_connection());
assert_eq!(connection.kind_str(), "Connection");
let thrown = Error::thrown("test".into());
assert!(thrown.is_thrown());
assert_eq!(thrown.kind_str(), "Thrown");
let internal = Error::internal("test".into());
assert!(internal.is_internal());
assert_eq!(internal.kind_str(), "Internal");
}
#[test]
fn test_error_from_parts_with_details() {
let mut details_obj = Object::new();
details_obj.insert("kind", Value::String("Table".into()));
let mut inner = Object::new();
inner.insert("name", Value::String("users".into()));
details_obj.insert("details", Value::Object(inner));
let err = Error::from_parts(
"Table not found".into(),
Some("NotFound"),
Some(Value::Object(details_obj)),
);
assert!(err.is_not_found());
assert_eq!(err.message(), "Table not found");
match err.not_found_details() {
Some(NotFoundError::Table {
name,
}) => assert_eq!(name, "users"),
other => panic!("Expected Table, got {other:?}"),
}
}
#[test]
fn test_error_from_parts_without_details() {
let err = Error::from_parts("Internal error".into(), Some("Internal"), None);
assert!(err.is_internal());
assert_eq!(err.message(), "Internal error");
}
#[test]
fn test_error_from_parts_unknown_kind() {
let err = Error::from_parts("Unknown".into(), Some("FutureKind"), None);
assert!(err.is_internal()); }
#[test]
fn test_error_from_parts_no_kind() {
let err = Error::from_parts("No kind".into(), None, None);
assert!(err.is_internal()); }
#[test]
fn test_error_cause_accessor() {
let root = Error::internal("root cause".into());
let top = Error::validation("validation failed".into(), None).with_cause(root);
assert!(top.cause().is_some());
let cause = top.cause().unwrap();
assert_eq!(cause.message(), "root cause");
assert!(cause.is_internal());
}
#[test]
fn test_error_cause_wire_roundtrip() {
use surrealdb_types::SurrealValue;
let root = Error::internal("root cause".into());
let top = Error::validation("validation failed".into(), None).with_cause(root);
let value = top.into_value();
let parsed = Error::from_value(value).unwrap();
assert_eq!(parsed.message(), "validation failed");
let cause = parsed.cause().expect("cause should be present");
assert_eq!(cause.message(), "root cause");
assert!(cause.is_internal());
}
#[test]
fn test_error_cause_chain_three_levels() {
use surrealdb_types::SurrealValue;
let innermost = Error::not_found("record missing".into(), None);
let middle = Error::query("query failed".into(), None).with_cause(innermost);
let top = Error::internal("operation failed".into()).with_cause(middle);
assert_eq!(top.message(), "operation failed");
let c1 = top.cause().unwrap();
assert_eq!(c1.message(), "query failed");
let c2 = c1.cause().unwrap();
assert_eq!(c2.message(), "record missing");
assert!(c2.is_not_found());
let value = top.clone().into_value();
let parsed = Error::from_value(value).unwrap();
assert_eq!(parsed.message(), "operation failed");
assert_eq!(parsed.cause().unwrap().message(), "query failed");
assert_eq!(parsed.cause().unwrap().cause().unwrap().message(), "record missing");
}
#[test]
fn test_error_without_cause_omitted_on_wire() {
use surrealdb_types::SurrealValue;
let err = Error::internal("no cause".into());
let value = err.into_value();
let parsed = Error::from_value(value).unwrap();
assert_eq!(parsed.message(), "no cause");
assert!(parsed.cause().is_none());
}
#[test]
fn test_error_from_anyhow_does_not_leak_chain() {
let root = anyhow::anyhow!("raw internal value: secret=hunter2");
let middle = root.context("Failed to coerce computed field 'x'");
let top = middle.context("Operation failed");
let err = Error::from_anyhow_with_chain(top);
assert_eq!(err.message(), "Operation failed");
assert!(err.cause().is_none(), "anyhow source chain must not leak as cause");
}
#[test]
fn test_chain_trait_result() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
let result: Result<(), std::io::Error> = Err(io_err);
let err = result.chain("Failed to load config").unwrap_err();
assert!(err.is_context());
assert_eq!(err.message(), "Failed to load config");
let cause = err.cause().expect("chained error should have cause");
assert!(cause.is_not_found());
}
#[test]
fn test_chain_trait_result_with_lazy_context() {
let io_err = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "connection refused");
let result: Result<(), std::io::Error> = Err(io_err);
let err = result.chain_with(|| format!("connecting to host {}", "localhost:8080")).unwrap_err();
assert!(err.is_context());
assert_eq!(err.message(), "connecting to host localhost:8080");
let cause = err.cause().expect("chained error should have cause");
assert!(cause.is_connection());
}
#[test]
fn test_chain_trait_option() {
let opt: Option<u32> = None;
let err = opt.chain("missing user id").unwrap_err();
assert!(err.is_context());
assert_eq!(err.message(), "missing user id");
assert!(err.cause().is_none());
}
#[test]
fn test_chain_trait_option_with_lazy_context() {
let opt: Option<u32> = None;
let err = opt.chain_with(|| format!("user {} not found", 42)).unwrap_err();
assert!(err.is_context());
assert_eq!(err.message(), "user 42 not found");
assert!(err.cause().is_none());
}
#[test]
fn test_chain_trait_ok_passthrough() {
let result: Result<u32, std::io::Error> = Ok(42);
let val = result.chain("should not trigger").unwrap();
assert_eq!(val, 42);
}
#[test]
fn test_chain_trait_some_passthrough() {
let opt: Option<u32> = Some(42);
let val = opt.chain("should not trigger").unwrap();
assert_eq!(val, 42);
}
#[test]
fn test_error_query_none_round_trip() {
use surrealdb_types::SurrealValue;
let err = Error::query("The record access signup query failed".into(), None);
assert!(err.is_query(), "before round-trip: expected Query, got {}", err.kind_str());
let value = err.clone().into_value();
let parsed = Error::from_value(value).unwrap();
assert!(parsed.is_query(), "after round-trip: expected Query, got {}", parsed.kind_str());
assert_eq!(parsed.message(), "The record access signup query failed");
let encoded = surrealdb_types::encode(&err.into_value()).expect("encode should succeed");
let decoded: surrealdb_types::Value =
surrealdb_types::decode(&encoded).expect("decode should succeed");
let parsed = Error::from_value(decoded).unwrap();
assert!(
parsed.is_query(),
"after flatbuffers round-trip: expected Query, got {}",
parsed.kind_str()
);
}