1use axum::{
14 Json,
15 http::StatusCode,
16 response::{IntoResponse, Response},
17};
18use serde::Serialize;
19use std::{
20 any::Any,
21 backtrace::Backtrace,
22 error::Error as StdError,
23 fmt::{Debug, Display},
24 sync::{Arc, Mutex},
25};
26
27pub trait HostError: Any + StdError + Send + Sync + 'static {}
28impl<T: Any + StdError + Send + Sync + 'static> HostError for T {}
29
30pub enum Error {
31 Context { msg: String, source: Box<SError> },
32 HostLang(Box<dyn HostError>),
33 Client { msg: String, bt: Backtrace },
34 Internal(anyhow::Error),
35}
36
37impl Display for Error {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 match self.format_context(f)? {
40 Error::Context { .. } => Ok(()),
41 Error::HostLang(e) => write!(f, "{}", e),
42 Error::Client { msg, .. } => write!(f, "Invalid Request: {}", msg),
43 Error::Internal(e) => write!(f, "{}", e),
44 }
45 }
46}
47impl Debug for Error {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 match self.format_context(f)? {
50 Error::Context { .. } => Ok(()),
51 Error::HostLang(e) => write!(f, "{:?}", e),
52 Error::Client { msg, bt } => {
53 write!(f, "Invalid Request: {msg}\n\n{bt}\n")
54 }
55 Error::Internal(e) => write!(f, "{e:?}"),
56 }
57 }
58}
59
60pub type Result<T, E = Error> = std::result::Result<T, E>;
61
62pub type CError = Error;
64pub type CResult<T> = Result<T>;
65
66impl Error {
67 pub fn host(e: impl HostError) -> Self {
68 Self::HostLang(Box::new(e))
69 }
70
71 pub fn client(msg: impl Into<String>) -> Self {
72 Self::Client {
73 msg: msg.into(),
74 bt: Backtrace::capture(),
75 }
76 }
77
78 pub fn internal(e: impl Into<anyhow::Error>) -> Self {
79 Self::Internal(e.into())
80 }
81
82 pub fn internal_msg(msg: impl Into<String>) -> Self {
83 Self::Internal(anyhow::anyhow!("{}", msg.into()))
84 }
85
86 pub fn backtrace(&self) -> Option<&Backtrace> {
87 match self {
88 Error::Client { bt, .. } => Some(bt),
89 Error::Internal(e) => Some(e.backtrace()),
90 Error::Context { source, .. } => source.0.backtrace(),
91 Error::HostLang(_) => None,
92 }
93 }
94
95 pub fn without_contexts(&self) -> &Error {
96 match self {
97 Error::Context { source, .. } => source.0.without_contexts(),
98 other => other,
99 }
100 }
101
102 pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
103 match self {
104 Error::Context { source, .. } => Some(source.as_ref()),
105 Error::HostLang(e) => Some(e.as_ref()),
106 Error::Internal(e) => e.source(),
107 Error::Client { .. } => None,
108 }
109 }
110
111 pub fn context<C: Into<String>>(self, context: C) -> Self {
112 Self::Context {
113 msg: context.into(),
114 source: Box::new(SError(self)),
115 }
116 }
117
118 pub fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Self {
119 Self::Context {
120 msg: f().into(),
121 source: Box::new(SError(self)),
122 }
123 }
124
125 pub fn std_error(self) -> SError {
126 SError(self)
127 }
128
129 fn format_context(&self, f: &mut std::fmt::Formatter<'_>) -> Result<&Error, std::fmt::Error> {
130 let mut current = self;
131 if matches!(current, Error::Context { .. }) {
132 write!(f, "\nContext:\n")?;
133 let mut next_id = 1;
134 while let Error::Context { msg, source } = current {
135 writeln!(f, " {next_id}: {msg}")?;
136 current = source.inner();
137 next_id += 1;
138 }
139 }
140 Ok(current)
141 }
142}
143
144impl StdError for Error {
145 fn source(&self) -> Option<&(dyn StdError + 'static)> {
146 self.source()
147 }
148}
149
150impl From<anyhow::Error> for Error {
164 fn from(e: anyhow::Error) -> Self {
165 Error::Internal(e)
166 }
167}
168
169impl From<std::io::Error> for Error {
170 fn from(e: std::io::Error) -> Self {
171 Error::Internal(e.into())
172 }
173}
174
175impl From<tokio::task::JoinError> for Error {
176 fn from(e: tokio::task::JoinError) -> Self {
177 Error::Internal(e.into())
178 }
179}
180
181impl From<tokio::sync::oneshot::error::RecvError> for Error {
182 fn from(e: tokio::sync::oneshot::error::RecvError) -> Self {
183 Error::Internal(e.into())
184 }
185}
186
187impl From<base64::DecodeError> for Error {
188 fn from(e: base64::DecodeError) -> Self {
189 Error::Internal(e.into())
190 }
191}
192
193impl From<hex::FromHexError> for Error {
194 fn from(e: hex::FromHexError) -> Self {
195 Error::Internal(e.into())
196 }
197}
198
199impl From<ResidualError> for Error {
200 fn from(e: ResidualError) -> Self {
201 Error::Internal(anyhow::Error::from(e))
202 }
203}
204
205impl From<crate::fingerprint::FingerprinterError> for Error {
206 fn from(e: crate::fingerprint::FingerprinterError) -> Self {
207 Error::Internal(anyhow::Error::new(e))
208 }
209}
210
211impl From<ApiError> for Error {
212 fn from(e: ApiError) -> Self {
213 Error::Internal(e.err)
214 }
215}
216
217impl From<serde_json::Error> for Error {
218 fn from(e: serde_json::Error) -> Self {
219 Error::Internal(e.into())
220 }
221}
222
223impl From<globset::Error> for Error {
224 fn from(e: globset::Error) -> Self {
225 Error::Internal(e.into())
226 }
227}
228
229impl From<regex::Error> for Error {
230 fn from(e: regex::Error) -> Self {
231 Error::Internal(e.into())
232 }
233}
234
235impl<T> From<std::sync::PoisonError<T>> for Error {
236 fn from(e: std::sync::PoisonError<T>) -> Self {
237 Error::Internal(anyhow::anyhow!("Mutex poison error: {}", e))
238 }
239}
240
241impl From<chrono::ParseError> for Error {
242 fn from(e: chrono::ParseError) -> Self {
243 Error::Internal(e.into())
244 }
245}
246
247impl From<uuid::Error> for Error {
248 fn from(e: uuid::Error) -> Self {
249 Error::Internal(e.into())
250 }
251}
252
253impl From<http::header::InvalidHeaderValue> for Error {
254 fn from(e: http::header::InvalidHeaderValue) -> Self {
255 Error::Internal(e.into())
256 }
257}
258
259impl From<std::num::ParseIntError> for Error {
260 fn from(e: std::num::ParseIntError) -> Self {
261 Error::Internal(e.into())
262 }
263}
264
265impl From<std::str::ParseBoolError> for Error {
266 fn from(e: std::str::ParseBoolError) -> Self {
267 Error::Internal(e.into())
268 }
269}
270
271impl From<std::fmt::Error> for Error {
272 fn from(e: std::fmt::Error) -> Self {
273 Error::Internal(e.into())
274 }
275}
276
277impl From<std::string::FromUtf8Error> for Error {
278 fn from(e: std::string::FromUtf8Error) -> Self {
279 Error::Internal(e.into())
280 }
281}
282
283impl From<std::borrow::Cow<'_, str>> for Error {
284 fn from(e: std::borrow::Cow<'_, str>) -> Self {
285 Error::Internal(anyhow::anyhow!("{}", e))
286 }
287}
288
289impl From<tokio::sync::AcquireError> for Error {
290 fn from(e: tokio::sync::AcquireError) -> Self {
291 Error::Internal(e.into())
292 }
293}
294
295impl From<tokio::sync::watch::error::RecvError> for Error {
296 fn from(e: tokio::sync::watch::error::RecvError) -> Self {
297 Error::Internal(e.into())
298 }
299}
300
301#[cfg(feature = "yaml")]
302impl From<yaml_rust2::EmitError> for Error {
303 fn from(e: yaml_rust2::EmitError) -> Self {
304 Error::Internal(e.into())
305 }
306}
307
308#[cfg(feature = "yaml")]
309impl From<crate::yaml_ser::YamlSerializerError> for Error {
310 fn from(e: crate::yaml_ser::YamlSerializerError) -> Self {
311 Error::Internal(anyhow::Error::new(e))
312 }
313}
314
315#[cfg(feature = "reqwest")]
316impl From<reqwest::Error> for Error {
317 fn from(e: reqwest::Error) -> Self {
318 Error::Internal(e.into())
319 }
320}
321
322#[cfg(feature = "sqlx")]
323impl From<sqlx::Error> for Error {
324 fn from(e: sqlx::Error) -> Self {
325 Error::Internal(e.into())
326 }
327}
328
329#[cfg(feature = "neo4rs")]
330impl From<neo4rs::Error> for Error {
331 fn from(e: neo4rs::Error) -> Self {
332 Error::Internal(e.into())
333 }
334}
335
336#[cfg(feature = "openai")]
337impl From<async_openai::error::OpenAIError> for Error {
338 fn from(e: async_openai::error::OpenAIError) -> Self {
339 Error::Internal(e.into())
340 }
341}
342
343#[cfg(feature = "qdrant")]
344impl From<qdrant_client::QdrantError> for Error {
345 fn from(e: qdrant_client::QdrantError) -> Self {
346 Error::Internal(anyhow::Error::msg(e.to_string()))
347 }
348}
349
350#[cfg(feature = "redis")]
351impl From<redis::RedisError> for Error {
352 fn from(e: redis::RedisError) -> Self {
353 Error::Internal(e.into())
354 }
355}
356
357#[cfg(feature = "azure")]
358impl From<azure_storage::Error> for Error {
359 fn from(e: azure_storage::Error) -> Self {
360 Error::Internal(anyhow::Error::msg(e.to_string()))
361 }
362}
363
364#[cfg(feature = "google-drive")]
365impl From<google_drive3::Error> for Error {
366 fn from(e: google_drive3::Error) -> Self {
367 Error::Internal(anyhow::Error::msg(e.to_string()))
368 }
369}
370
371#[cfg(feature = "google-drive")]
372impl From<google_drive3::hyper::Error> for Error {
373 fn from(e: google_drive3::hyper::Error) -> Self {
374 Error::Internal(e.into())
375 }
376}
377
378pub trait ContextExt<T> {
379 fn context<C: Into<String>>(self, context: C) -> Result<T>;
380 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T>;
381}
382
383impl<T> ContextExt<T> for Result<T> {
384 fn context<C: Into<String>>(self, context: C) -> Result<T> {
385 self.map_err(|e| e.context(context))
386 }
387
388 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T> {
389 self.map_err(|e| e.with_context(f))
390 }
391}
392
393pub trait StdContextExt<T, E> {
394 fn context<C: Into<String>>(self, context: C) -> Result<T>;
395 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T>;
396}
397
398impl<T, E: StdError + Send + Sync + 'static> StdContextExt<T, E> for Result<T, E> {
399 fn context<C: Into<String>>(self, context: C) -> Result<T> {
400 self.map_err(|e| Error::internal(e).context(context))
401 }
402
403 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T> {
404 self.map_err(|e| Error::internal(e).with_context(f))
405 }
406}
407
408impl<T> ContextExt<T> for Option<T> {
409 fn context<C: Into<String>>(self, context: C) -> Result<T> {
410 self.ok_or_else(|| Error::client(context))
411 }
412
413 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T> {
414 self.ok_or_else(|| Error::client(f()))
415 }
416}
417
418impl IntoResponse for Error {
419 fn into_response(self) -> Response {
420 tracing::debug!("Error response:\n{:?}", self);
421
422 let (status_code, error_msg) = match &self {
423 Error::Client { msg, .. } => (StatusCode::BAD_REQUEST, msg.clone()),
424 Error::HostLang(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()),
425 Error::Context { .. } | Error::Internal(_) => {
426 (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", self))
427 }
428 };
429
430 let error_response = ErrorResponse { error: error_msg };
431 (status_code, Json(error_response)).into_response()
432 }
433}
434
435#[macro_export]
436macro_rules! client_bail {
437 ( $fmt:literal $(, $($arg:tt)*)?) => {
438 return Err($crate::error::Error::client(format!($fmt $(, $($arg)*)?)))
439 };
440}
441
442#[macro_export]
443macro_rules! client_error {
444 ( $fmt:literal $(, $($arg:tt)*)?) => {
445 $crate::error::Error::client(format!($fmt $(, $($arg)*)?))
446 };
447}
448
449#[macro_export]
450macro_rules! internal_bail {
451 ( $fmt:literal $(, $($arg:tt)*)?) => {
452 return Err($crate::error::Error::internal_msg(format!($fmt $(, $($arg)*)?)))
453 };
454}
455
456#[macro_export]
457macro_rules! internal_error {
458 ( $fmt:literal $(, $($arg:tt)*)?) => {
459 $crate::error::Error::internal_msg(format!($fmt $(, $($arg)*)?))
460 };
461}
462
463pub struct SError(Error);
465
466impl SError {
467 pub fn inner(&self) -> &Error {
468 &self.0
469 }
470}
471
472impl Display for SError {
473 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
474 Display::fmt(&self.0, f)
475 }
476}
477
478impl Debug for SError {
479 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
480 Debug::fmt(&self.0, f)
481 }
482}
483
484impl std::error::Error for SError {
485 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
486 self.0.source()
487 }
488}
489
490struct ResidualErrorData {
493 message: String,
494 debug: String,
495}
496
497#[derive(Clone)]
498pub struct ResidualError(Arc<ResidualErrorData>);
499
500impl ResidualError {
501 pub fn new<Err: Display + Debug>(err: &Err) -> Self {
502 Self(Arc::new(ResidualErrorData {
503 message: err.to_string(),
504 debug: err.to_string(),
505 }))
506 }
507}
508
509impl Display for ResidualError {
510 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
511 write!(f, "{}", self.0.message)
512 }
513}
514
515impl Debug for ResidualError {
516 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
517 write!(f, "{}", self.0.debug)
518 }
519}
520
521impl StdError for ResidualError {}
522
523enum SharedErrorState {
524 Error(Error),
525 ResidualErrorMessage(ResidualError),
526}
527
528#[derive(Clone)]
529pub struct SharedError(Arc<Mutex<SharedErrorState>>);
530
531impl SharedError {
532 pub fn new(err: Error) -> Self {
533 Self(Arc::new(Mutex::new(SharedErrorState::Error(err))))
534 }
535
536 fn extract_error(&self) -> Error {
537 let mut state = self.0.lock().unwrap();
538 let mut_state = &mut *state;
539
540 let residual_err = match mut_state {
541 SharedErrorState::ResidualErrorMessage(err) => {
542 return Error::internal(err.clone());
544 }
545 SharedErrorState::Error(err) => ResidualError::new(err),
546 };
547
548 let orig_state = std::mem::replace(
549 mut_state,
550 SharedErrorState::ResidualErrorMessage(residual_err),
551 );
552 let SharedErrorState::Error(err) = orig_state else {
553 panic!("Expected shared error state to hold Error");
554 };
555 err
556 }
557}
558
559impl Debug for SharedError {
560 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
561 let state = self.0.lock().unwrap();
562 match &*state {
563 SharedErrorState::Error(err) => Debug::fmt(err, f),
564 SharedErrorState::ResidualErrorMessage(err) => Debug::fmt(err, f),
565 }
566 }
567}
568
569impl Display for SharedError {
570 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
571 let state = self.0.lock().unwrap();
572 match &*state {
573 SharedErrorState::Error(err) => Display::fmt(err, f),
574 SharedErrorState::ResidualErrorMessage(err) => Display::fmt(err, f),
575 }
576 }
577}
578
579impl From<Error> for SharedError {
580 fn from(err: Error) -> Self {
581 Self(Arc::new(Mutex::new(SharedErrorState::Error(err))))
582 }
583}
584
585pub fn shared_ok<T>(value: T) -> std::result::Result<T, SharedError> {
586 Ok(value)
587}
588
589pub type SharedResult<T> = std::result::Result<T, SharedError>;
590
591pub trait SharedResultExt<T> {
592 fn into_result(self) -> Result<T>;
593}
594
595impl<T> SharedResultExt<T> for std::result::Result<T, SharedError> {
596 fn into_result(self) -> Result<T> {
597 match self {
598 Ok(value) => Ok(value),
599 Err(err) => Err(err.extract_error()),
600 }
601 }
602}
603
604pub trait SharedResultExtRef<'a, T> {
605 fn into_result(self) -> Result<&'a T>;
606}
607
608impl<'a, T> SharedResultExtRef<'a, T> for &'a std::result::Result<T, SharedError> {
609 fn into_result(self) -> Result<&'a T> {
610 match self {
611 Ok(value) => Ok(value),
612 Err(err) => Err(err.extract_error()),
613 }
614 }
615}
616
617pub fn invariance_violation() -> anyhow::Error {
618 anyhow::anyhow!("Invariance violation")
619}
620
621#[derive(Debug)]
622pub struct ApiError {
623 pub err: anyhow::Error,
624 pub status_code: StatusCode,
625}
626
627impl ApiError {
628 pub fn new(message: &str, status_code: StatusCode) -> Self {
629 Self {
630 err: anyhow::anyhow!("{}", message),
631 status_code,
632 }
633 }
634}
635
636impl Display for ApiError {
637 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
638 Display::fmt(&self.err, f)
639 }
640}
641
642impl StdError for ApiError {
643 fn source(&self) -> Option<&(dyn StdError + 'static)> {
644 self.err.source()
645 }
646}
647
648#[derive(Serialize)]
649struct ErrorResponse {
650 error: String,
651}
652
653impl IntoResponse for ApiError {
654 fn into_response(self) -> Response {
655 tracing::debug!("Internal server error:\n{:?}", self.err);
656 let error_response = ErrorResponse {
657 error: format!("{:?}", self.err),
658 };
659 (self.status_code, Json(error_response)).into_response()
660 }
661}
662
663impl From<anyhow::Error> for ApiError {
664 fn from(err: anyhow::Error) -> ApiError {
665 if err.is::<ApiError>() {
666 return err.downcast::<ApiError>().unwrap();
667 }
668 Self {
669 err,
670 status_code: StatusCode::INTERNAL_SERVER_ERROR,
671 }
672 }
673}
674
675impl From<Error> for ApiError {
676 fn from(err: Error) -> ApiError {
677 let status_code = match err.without_contexts() {
678 Error::Client { .. } => StatusCode::BAD_REQUEST,
679 _ => StatusCode::INTERNAL_SERVER_ERROR,
680 };
681 ApiError {
682 err: anyhow::Error::from(err.std_error()),
683 status_code,
684 }
685 }
686}
687
688#[macro_export]
689macro_rules! api_bail {
690 ( $fmt:literal $(, $($arg:tt)*)?) => {
691 return Err($crate::error::ApiError::new(&format!($fmt $(, $($arg)*)?), axum::http::StatusCode::BAD_REQUEST).into())
692 };
693}
694
695#[macro_export]
696macro_rules! api_error {
697 ( $fmt:literal $(, $($arg:tt)*)?) => {
698 $crate::error::ApiError::new(&format!($fmt $(, $($arg)*)?), axum::http::StatusCode::BAD_REQUEST)
699 };
700}
701
702#[cfg(test)]
703mod tests {
704 use super::*;
705 use std::backtrace::BacktraceStatus;
706 use std::io;
707
708 #[derive(Debug)]
709 struct MockHostError(String);
710
711 impl Display for MockHostError {
712 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
713 write!(f, "MockHostError: {}", self.0)
714 }
715 }
716
717 impl StdError for MockHostError {}
718
719 #[test]
720 fn test_client_error_creation() {
721 let err = Error::client("invalid input");
722 assert!(matches!(&err, Error::Client { msg, .. } if msg == "invalid input"));
723 assert!(matches!(err.without_contexts(), Error::Client { .. }));
724 }
725
726 #[test]
727 fn test_internal_error_creation() {
728 let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
729 let err: Error = io_err.into();
730 assert!(matches!(err, Error::Internal { .. }));
731 }
732
733 #[test]
734 fn test_internal_msg_error_creation() {
735 let err = Error::internal_msg("something went wrong");
736 assert!(matches!(err, Error::Internal { .. }));
737 assert_eq!(err.to_string(), "something went wrong");
738 }
739
740 #[test]
741 fn test_host_error_creation_and_detection() {
742 let mock = MockHostError("test error".to_string());
743 let err = Error::host(mock);
744 assert!(matches!(err.without_contexts(), Error::HostLang(_)));
745
746 if let Error::HostLang(host_err) = err.without_contexts() {
747 let any: &dyn Any = host_err.as_ref();
748 let downcasted = any.downcast_ref::<MockHostError>();
749 assert!(downcasted.is_some());
750 assert_eq!(downcasted.unwrap().0, "test error");
751 } else {
752 panic!("Expected HostLang variant");
753 }
754 }
755
756 #[test]
757 fn test_context_chaining() {
758 let inner = Error::client("base error");
759 let with_context: Result<()> = Err(inner);
760 let wrapped = ContextExt::context(
761 ContextExt::context(ContextExt::context(with_context, "layer 1"), "layer 2"),
762 "layer 3",
763 );
764
765 let err = wrapped.unwrap_err();
766 assert!(matches!(&err, Error::Context { msg, .. } if msg == "layer 3"));
767
768 if let Error::Context { source, .. } = &err {
769 assert!(
770 matches!(source.as_ref(), SError(Error::Context { msg, .. }) if msg == "layer 2")
771 );
772 }
773 assert_eq!(
774 err.to_string(),
775 "\nContext:\
776 \n 1: layer 3\
777 \n 2: layer 2\
778 \n 3: layer 1\
779 \nInvalid Request: base error"
780 );
781 }
782
783 #[test]
784 fn test_context_preserves_host_error() {
785 let mock = MockHostError("original python error".to_string());
786 let err = Error::host(mock);
787 let wrapped: Result<()> = Err(err);
788 let with_context = ContextExt::context(wrapped, "while processing request");
789
790 let final_err = with_context.unwrap_err();
791 assert!(matches!(final_err.without_contexts(), Error::HostLang(_)));
792
793 if let Error::HostLang(host_err) = final_err.without_contexts() {
794 let any: &dyn Any = host_err.as_ref();
795 let downcasted = any.downcast_ref::<MockHostError>();
796 assert!(downcasted.is_some());
797 assert_eq!(downcasted.unwrap().0, "original python error");
798 } else {
799 panic!("Expected HostLang variant");
800 }
801 }
802
803 #[test]
804 fn test_backtrace_captured_for_client_error() {
805 let err = Error::client("test");
806 let bt = err.backtrace();
807 assert!(bt.is_some());
808 let status = bt.unwrap().status();
809 assert!(
810 status == BacktraceStatus::Captured
811 || status == BacktraceStatus::Disabled
812 || status == BacktraceStatus::Unsupported
813 );
814 }
815
816 #[test]
817 fn test_backtrace_captured_for_internal_error() {
818 let err = Error::internal_msg("test internal");
819 let bt = err.backtrace();
820 assert!(bt.is_some());
821 }
822
823 #[test]
824 fn test_backtrace_traverses_context() {
825 let inner = Error::internal_msg("base");
826 let wrapped: Result<()> = Err(inner);
827 let with_context = ContextExt::context(wrapped, "context");
828
829 let err = with_context.unwrap_err();
830 let bt = err.backtrace();
831 assert!(bt.is_some());
832 }
833
834 #[test]
835 fn test_option_context_ext() {
836 let opt: Option<i32> = None;
837 let result = opt.context("value was missing");
838
839 assert!(result.is_err());
840 let err = result.unwrap_err();
841 assert!(matches!(err.without_contexts(), Error::Client { .. }));
842 assert!(matches!(&err, Error::Client { msg, .. } if msg == "value was missing"));
843 }
844
845 #[test]
846 fn test_error_display_formats() {
847 let client_err = Error::client("bad input");
848 assert_eq!(client_err.to_string(), "Invalid Request: bad input");
849
850 let internal_err = Error::internal_msg("db connection failed");
851 assert_eq!(internal_err.to_string(), "db connection failed");
852
853 let host_err = Error::host(MockHostError("py error".to_string()));
854 assert_eq!(host_err.to_string(), "MockHostError: py error");
855 }
856
857 #[test]
858 fn test_error_source_chain() {
859 let inner = Error::internal_msg("root cause");
860 let wrapped: Result<()> = Err(inner);
861 let outer = ContextExt::context(wrapped, "outer context").unwrap_err();
862
863 let source = outer.source();
864 assert!(source.is_some());
865 }
866}