use query_flow::{query, Db, QueryError, QueryRuntime};
#[test]
fn test_user_error_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let query_err: QueryError = anyhow::Error::from(io_err).into();
assert!(matches!(query_err, QueryError::UserError(_)));
assert!(query_err.to_string().contains("file not found"));
}
#[test]
fn test_user_error_from_anyhow() {
let anyhow_err = anyhow::anyhow!("something went wrong");
let query_err: QueryError = anyhow_err.into();
assert!(matches!(query_err, QueryError::UserError(_)));
assert!(query_err.to_string().contains("something went wrong"));
}
#[derive(Debug, Clone, PartialEq)]
struct CustomError {
code: i32,
message: String,
}
impl std::fmt::Display for CustomError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "CustomError({}): {}", self.code, self.message)
}
}
impl std::error::Error for CustomError {}
#[test]
fn test_user_error_from_custom_error() {
let custom_err = CustomError {
code: 42,
message: "custom error".to_string(),
};
let query_err: QueryError = anyhow::Error::from(custom_err).into();
assert!(matches!(query_err, QueryError::UserError(_)));
assert!(query_err.to_string().contains("custom error"));
}
#[query]
fn parse_number(db: &impl Db, input: String) -> Result<i32, QueryError> {
let _ = db;
let num: i32 = input.parse()?;
Ok(num)
}
#[test]
fn test_question_mark_propagation_success() {
let runtime = QueryRuntime::new();
let result = runtime.query(ParseNumber::new("42".to_string()));
assert_eq!(*result.unwrap(), 42);
}
#[test]
fn test_question_mark_propagation_error() {
let runtime = QueryRuntime::new();
let result = runtime.query(ParseNumber::new("not_a_number".to_string()));
match result {
Err(QueryError::UserError(e)) => {
assert!(e.to_string().contains("invalid digit"));
}
other => panic!("Expected UserError, got {:?}", other),
}
}
#[query]
fn fallible_io(db: &impl Db, should_fail: bool) -> Result<String, QueryError> {
let _ = db;
if should_fail {
return Err(anyhow::anyhow!("not found").into());
}
Ok("success".to_string())
}
#[test]
fn test_io_error_propagation() {
let runtime = QueryRuntime::new();
let result = runtime.query(FallibleIo::new(false));
assert_eq!(*result.unwrap(), "success");
let result = runtime.query(FallibleIo::new(true));
assert!(matches!(result, Err(QueryError::UserError(_))));
}
mod error_caching_error {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static FALLIBLE_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn fallible_cached(db: &impl Db, id: u32) -> Result<i32, QueryError> {
let _ = db;
FALLIBLE_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
if id == 0 {
return Err(anyhow::anyhow!("id cannot be zero").into());
}
Ok(id as i32 * 10)
}
#[test]
fn test_user_error_cached() {
FALLIBLE_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::new();
let result1 = runtime.query(FallibleCached::new(0));
assert!(matches!(result1, Err(QueryError::UserError(_))));
assert_eq!(FALLIBLE_CALL_COUNT.load(Ordering::SeqCst), 1);
let result2 = runtime.query(FallibleCached::new(0));
assert!(matches!(result2, Err(QueryError::UserError(_))));
assert_eq!(FALLIBLE_CALL_COUNT.load(Ordering::SeqCst), 1); }
}
mod error_caching_success {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static FALLIBLE_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn fallible_cached(db: &impl Db, id: u32) -> Result<i32, QueryError> {
let _ = db;
FALLIBLE_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
if id == 0 {
return Err(anyhow::anyhow!("id cannot be zero").into());
}
Ok(id as i32 * 10)
}
#[test]
fn test_success_cached() {
FALLIBLE_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::new();
let result1 = runtime.query(FallibleCached::new(5));
assert_eq!(*result1.unwrap(), 50);
assert_eq!(FALLIBLE_CALL_COUNT.load(Ordering::SeqCst), 1);
let result2 = runtime.query(FallibleCached::new(5));
assert_eq!(*result2.unwrap(), 50);
assert_eq!(FALLIBLE_CALL_COUNT.load(Ordering::SeqCst), 1); }
}
mod error_comparator_default {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static ERROR_LEVEL1_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static ERROR_LEVEL2_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static ERROR_LEVEL3_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn error_level1(db: &impl Db, code: i32) -> Result<i32, QueryError> {
let _ = db;
ERROR_LEVEL1_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
if code < 0 {
return Err(CustomError {
code,
message: "negative code".to_string(),
}
.into());
}
Ok(code * 2)
}
#[query]
fn error_level2(db: &impl Db, code: i32) -> Result<i32, QueryError> {
ERROR_LEVEL2_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(ErrorLevel1::new(code))?;
Ok(*base + 1)
}
#[query]
fn error_level3(db: &impl Db, code: i32) -> Result<i32, QueryError> {
ERROR_LEVEL3_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(ErrorLevel2::new(code))?;
Ok(*base + 10)
}
#[test]
fn test_error_comparator_default_false() {
ERROR_LEVEL1_CALL_COUNT.store(0, Ordering::SeqCst);
ERROR_LEVEL2_CALL_COUNT.store(0, Ordering::SeqCst);
ERROR_LEVEL3_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::new();
let result1 = runtime.query(ErrorLevel3::new(-1));
assert!(matches!(result1, Err(QueryError::UserError(_))));
assert_eq!(ERROR_LEVEL1_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(ERROR_LEVEL2_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(ERROR_LEVEL3_CALL_COUNT.load(Ordering::SeqCst), 1);
runtime.invalidate(&ErrorLevel1::new(-1));
let result2 = runtime.query(ErrorLevel3::new(-1));
assert!(matches!(result2, Err(QueryError::UserError(_))));
assert_eq!(ERROR_LEVEL1_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(ERROR_LEVEL2_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(ERROR_LEVEL3_CALL_COUNT.load(Ordering::SeqCst), 2);
}
}
mod error_comparator_custom {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static ERROR_LEVEL1_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static ERROR_LEVEL2_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static ERROR_LEVEL3_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn error_level1(db: &impl Db, code: i32) -> Result<i32, QueryError> {
let _ = db;
ERROR_LEVEL1_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
if code < 0 {
return Err(CustomError {
code,
message: "negative code".to_string(),
}
.into());
}
Ok(code * 2)
}
#[query]
fn error_level2(db: &impl Db, code: i32) -> Result<i32, QueryError> {
ERROR_LEVEL2_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(ErrorLevel1::new(code))?;
Ok(*base + 1)
}
#[query]
fn error_level3(db: &impl Db, code: i32) -> Result<i32, QueryError> {
ERROR_LEVEL3_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(ErrorLevel2::new(code))?;
Ok(*base + 10)
}
#[test]
fn test_error_comparator_custom() {
ERROR_LEVEL1_CALL_COUNT.store(0, Ordering::SeqCst);
ERROR_LEVEL2_CALL_COUNT.store(0, Ordering::SeqCst);
ERROR_LEVEL3_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::builder()
.error_comparator(|a, b| {
match (
a.downcast_ref::<CustomError>(),
b.downcast_ref::<CustomError>(),
) {
(Some(a), Some(b)) => a.code == b.code,
_ => false,
}
})
.build();
let result1 = runtime.query(ErrorLevel3::new(-1));
assert!(matches!(result1, Err(QueryError::UserError(_))));
assert_eq!(ERROR_LEVEL1_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(ERROR_LEVEL2_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(ERROR_LEVEL3_CALL_COUNT.load(Ordering::SeqCst), 1);
runtime.invalidate(&ErrorLevel1::new(-1));
let result2 = runtime.query(ErrorLevel3::new(-1));
assert!(matches!(result2, Err(QueryError::UserError(_))));
assert_eq!(ERROR_LEVEL1_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(ERROR_LEVEL2_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(ERROR_LEVEL3_CALL_COUNT.load(Ordering::SeqCst), 1);
}
}
mod error_comparator_always_equal {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static ERROR_LEVEL1_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static ERROR_LEVEL2_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static ERROR_LEVEL3_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn error_level1(db: &impl Db, code: i32) -> Result<i32, QueryError> {
let _ = db;
ERROR_LEVEL1_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
if code < 0 {
return Err(CustomError {
code,
message: "negative code".to_string(),
}
.into());
}
Ok(code * 2)
}
#[query]
fn error_level2(db: &impl Db, code: i32) -> Result<i32, QueryError> {
ERROR_LEVEL2_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(ErrorLevel1::new(code))?;
Ok(*base + 1)
}
#[query]
fn error_level3(db: &impl Db, code: i32) -> Result<i32, QueryError> {
ERROR_LEVEL3_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(ErrorLevel2::new(code))?;
Ok(*base + 10)
}
#[test]
fn test_error_comparator_always_equal() {
ERROR_LEVEL1_CALL_COUNT.store(0, Ordering::SeqCst);
ERROR_LEVEL2_CALL_COUNT.store(0, Ordering::SeqCst);
ERROR_LEVEL3_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::builder()
.error_comparator(|_, _| true)
.build();
let result1 = runtime.query(ErrorLevel3::new(-1));
assert!(matches!(result1, Err(QueryError::UserError(_))));
assert_eq!(ERROR_LEVEL1_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(ERROR_LEVEL2_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(ERROR_LEVEL3_CALL_COUNT.load(Ordering::SeqCst), 1);
runtime.invalidate(&ErrorLevel1::new(-1));
let result2 = runtime.query(ErrorLevel3::new(-1));
assert!(matches!(result2, Err(QueryError::UserError(_))));
assert_eq!(ERROR_LEVEL1_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(ERROR_LEVEL2_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(ERROR_LEVEL3_CALL_COUNT.load(Ordering::SeqCst), 1);
}
}
mod mixed_chain {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static MIXED_BASE_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static MIXED_MIDDLE_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static MIXED_TOP_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn mixed_base(db: &impl Db, value: i32) -> Result<i32, QueryError> {
let _ = db;
MIXED_BASE_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
Ok(value * 2)
}
#[query]
fn mixed_middle(db: &impl Db, value: i32) -> Result<i32, QueryError> {
MIXED_MIDDLE_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let base = db.query(MixedBase::new(value))?;
if *base > 100 {
return Err(anyhow::anyhow!("value too large: {}", base).into());
}
Ok(*base + 10)
}
#[query]
fn mixed_top(db: &impl Db, value: i32) -> Result<String, QueryError> {
MIXED_TOP_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let middle = db.query(MixedMiddle::new(value))?;
Ok(format!("result: {}", middle))
}
#[test]
fn test_mixed_ok_and_error_chain() {
MIXED_BASE_CALL_COUNT.store(0, Ordering::SeqCst);
MIXED_MIDDLE_CALL_COUNT.store(0, Ordering::SeqCst);
MIXED_TOP_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::new();
let result = runtime.query(MixedTop::new(10));
assert_eq!(*result.unwrap(), "result: 30");
let result = runtime.query(MixedTop::new(100));
match result {
Err(QueryError::UserError(e)) => {
assert!(e.to_string().contains("value too large"));
}
other => panic!("Expected UserError, got {:?}", other),
}
}
}
mod error_downcast {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static ERROR_LEVEL1_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn error_level1(db: &impl Db, code: i32) -> Result<i32, QueryError> {
let _ = db;
ERROR_LEVEL1_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
if code < 0 {
return Err(CustomError {
code,
message: "negative code".to_string(),
}
.into());
}
Ok(code * 2)
}
#[test]
fn test_error_downcast() {
let runtime = QueryRuntime::new();
let result = runtime.query(ErrorLevel1::new(-42));
match result {
Err(QueryError::UserError(e)) => {
let custom = e.downcast_ref::<CustomError>();
assert!(custom.is_some());
let custom = custom.unwrap();
assert_eq!(custom.code, -42);
assert_eq!(custom.message, "negative code");
}
other => panic!("Expected UserError, got {:?}", other),
}
}
}
mod transition_ok_to_error {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static TRANSITION_VALUE: AtomicU32 = AtomicU32::new(10);
static TRANSITION_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static TRANSITION_DEPENDENT_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn transition_source(db: &impl Db) -> Result<i32, QueryError> {
let _ = db;
TRANSITION_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let value = TRANSITION_VALUE.load(Ordering::SeqCst) as i32;
if value < 0 {
return Err(anyhow::anyhow!("negative value").into());
}
Ok(value)
}
#[query]
fn transition_dependent(db: &impl Db) -> Result<i32, QueryError> {
TRANSITION_DEPENDENT_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let source = db.query(TransitionSource::new())?;
Ok(*source * 2)
}
#[test]
fn test_ok_to_error_transition() {
TRANSITION_VALUE.store(10, Ordering::SeqCst);
TRANSITION_CALL_COUNT.store(0, Ordering::SeqCst);
TRANSITION_DEPENDENT_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::new();
let result = runtime.query(TransitionDependent::new());
assert_eq!(*result.unwrap(), 20);
assert_eq!(TRANSITION_CALL_COUNT.load(Ordering::SeqCst), 1);
assert_eq!(TRANSITION_DEPENDENT_CALL_COUNT.load(Ordering::SeqCst), 1);
TRANSITION_VALUE.store(u32::MAX, Ordering::SeqCst); runtime.invalidate(&TransitionSource {});
let result = runtime.query(TransitionDependent::new());
assert!(matches!(result, Err(QueryError::UserError(_))));
assert_eq!(TRANSITION_CALL_COUNT.load(Ordering::SeqCst), 2);
assert_eq!(TRANSITION_DEPENDENT_CALL_COUNT.load(Ordering::SeqCst), 2);
}
}
mod transition_error_to_ok {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
static TRANSITION_VALUE: AtomicU32 = AtomicU32::new(10);
static TRANSITION_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
static TRANSITION_DEPENDENT_CALL_COUNT: AtomicU32 = AtomicU32::new(0);
#[query]
fn transition_source(db: &impl Db) -> Result<i32, QueryError> {
let _ = db;
TRANSITION_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let value = TRANSITION_VALUE.load(Ordering::SeqCst) as i32;
if value < 0 {
return Err(anyhow::anyhow!("negative value").into());
}
Ok(value)
}
#[query]
fn transition_dependent(db: &impl Db) -> Result<i32, QueryError> {
TRANSITION_DEPENDENT_CALL_COUNT.fetch_add(1, Ordering::SeqCst);
let source = db.query(TransitionSource::new())?;
Ok(*source * 2)
}
#[test]
fn test_error_to_ok_transition() {
TRANSITION_VALUE.store(u32::MAX, Ordering::SeqCst); TRANSITION_CALL_COUNT.store(0, Ordering::SeqCst);
TRANSITION_DEPENDENT_CALL_COUNT.store(0, Ordering::SeqCst);
let runtime = QueryRuntime::new();
let result = runtime.query(TransitionDependent::new());
assert!(matches!(result, Err(QueryError::UserError(_))));
TRANSITION_VALUE.store(5, Ordering::SeqCst);
runtime.invalidate(&TransitionSource {});
let result = runtime.query(TransitionDependent::new());
assert_eq!(*result.unwrap(), 10);
}
}
#[test]
fn test_error_display() {
let err: QueryError = anyhow::Error::from(std::io::Error::new(
std::io::ErrorKind::NotFound,
"test error",
))
.into();
let display = err.to_string();
assert!(display.contains("user error"));
assert!(display.contains("test error"));
}
#[test]
fn test_error_source() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "original error");
let anyhow_err = anyhow::Error::from(io_err).context("wrapped error");
let _query_err: QueryError = anyhow_err.into();
}
mod downcast_err_tests {
use query_flow::{query, Db, QueryError, QueryResultExt, QueryRuntime};
#[derive(Debug, Clone, PartialEq)]
struct MyError {
code: i32,
message: String,
}
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MyError({}): {}", self.code, self.message)
}
}
impl std::error::Error for MyError {}
#[derive(Debug, Clone, PartialEq)]
struct OtherError {
reason: String,
}
impl std::fmt::Display for OtherError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "OtherError: {}", self.reason)
}
}
impl std::error::Error for OtherError {}
#[query]
fn may_fail(db: &impl Db, code: i32) -> Result<String, QueryError> {
let _ = db;
if code < 0 {
return Err(MyError {
code,
message: "negative code".to_string(),
}
.into());
}
if code == 0 {
return Err(OtherError {
reason: "zero is not allowed".to_string(),
}
.into());
}
Ok(format!("success: {}", code))
}
#[test]
fn test_downcast_err_success() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(42)).downcast_err::<MyError>();
let inner = result.expect("should not be system error");
let value = inner.expect("should be success");
assert_eq!(*value, "success: 42");
}
#[test]
fn test_downcast_err_matching_error() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(-1)).downcast_err::<MyError>();
let inner = result.expect("should not be system error");
let typed_err = inner.expect_err("should be user error");
assert_eq!(typed_err.code, -1);
assert_eq!(typed_err.message, "negative code");
assert_eq!(typed_err.get().code, -1);
}
#[test]
fn test_downcast_err_non_matching_error() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(0)).downcast_err::<MyError>();
let err = result.expect_err("should return error for non-matching type");
assert!(matches!(err, QueryError::UserError(_)));
assert!(err.to_string().contains("zero is not allowed"));
}
#[test]
fn test_downcast_err_with_question_mark() {
fn handle_query(runtime: &QueryRuntime) -> Result<String, QueryError> {
let result = runtime.query(MayFail::new(-5)).downcast_err::<MyError>()?;
match result {
Ok(value) => Ok(format!("got value: {}", value)),
Err(my_err) => Ok(format!("handled MyError with code {}", my_err.code)),
}
}
let runtime = QueryRuntime::new();
let result = handle_query(&runtime);
assert_eq!(result.unwrap(), "handled MyError with code -5");
}
#[test]
fn test_downcast_err_propagate_non_matching() {
fn handle_query(runtime: &QueryRuntime) -> Result<String, QueryError> {
let result = runtime.query(MayFail::new(0)).downcast_err::<MyError>()?;
match result {
Ok(value) => Ok(format!("got value: {}", value)),
Err(my_err) => Ok(format!("handled MyError with code {}", my_err.code)),
}
}
let runtime = QueryRuntime::new();
let result = handle_query(&runtime);
let err = result.expect_err("should propagate non-matching error");
assert!(err.to_string().contains("zero is not allowed"));
}
#[test]
fn test_downcast_err_map_err_pattern() {
fn handle_query(runtime: &QueryRuntime, code: i32) -> Result<String, String> {
let result = runtime
.query(MayFail::new(code))
.downcast_err::<MyError>()
.map_err(|e| format!("system or other error: {}", e))?;
match result {
Ok(value) => Ok((*value).clone()),
Err(e) => Err(format!("MyError: code={}", e.code)),
}
}
let runtime = QueryRuntime::new();
assert_eq!(handle_query(&runtime, 10).unwrap(), "success: 10");
assert_eq!(handle_query(&runtime, -1).unwrap_err(), "MyError: code=-1");
assert!(handle_query(&runtime, 0)
.unwrap_err()
.contains("system or other error"));
}
#[test]
fn test_typed_err_display_and_debug() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(-42)).downcast_err::<MyError>();
let typed_err = result.unwrap().unwrap_err();
let display = format!("{}", typed_err);
assert!(display.contains("MyError"));
assert!(display.contains("-42"));
let debug = format!("{:?}", typed_err);
assert!(debug.contains("MyError"));
assert!(debug.contains("-42"));
}
#[test]
fn test_typed_err_clone() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(-1)).downcast_err::<MyError>();
let typed_err = result.unwrap().unwrap_err();
let cloned = typed_err.clone();
assert_eq!(typed_err.code, cloned.code);
assert_eq!(typed_err.message, cloned.message);
}
#[test]
fn test_query_error_downcast_ref() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(-1));
let err = result.unwrap_err();
assert!(err.is::<MyError>());
assert!(!err.is::<OtherError>());
let my_err = err.downcast_ref::<MyError>().unwrap();
assert_eq!(my_err.code, -1);
}
#[test]
fn test_query_error_user_error() {
let runtime = QueryRuntime::new();
let result = runtime.query(MayFail::new(-1));
let err = result.unwrap_err();
let arc = err.user_error().expect("should be UserError");
assert!(arc.downcast_ref::<MyError>().is_some());
}
}