1use std::{
27 any::Any,
28 backtrace::Backtrace,
29 error::Error as StdError,
30 fmt::{Debug, Display},
31 sync::{Arc, Mutex},
32};
33
34pub trait HostError: Any + StdError + Send + Sync + 'static {}
37impl<T: Any + StdError + Send + Sync + 'static> HostError for T {}
38
39pub enum Error {
41 Context { msg: String, source: Box<SError> },
43 HostLang(Box<dyn HostError>),
45 Client { msg: String, bt: Backtrace },
48 Internal(anyhow::Error),
50}
51
52impl Display for Error {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self.format_context(f)? {
55 Error::Context { .. } => Ok(()),
56 Error::HostLang(e) => write!(f, "{}", e),
57 Error::Client { msg, .. } => write!(f, "Invalid Request: {}", msg),
58 Error::Internal(e) => write!(f, "{}", e),
59 }
60 }
61}
62impl Debug for Error {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self.format_context(f)? {
65 Error::Context { .. } => Ok(()),
66 Error::HostLang(e) => write!(f, "{:?}", e),
67 Error::Client { msg, bt } => {
68 write!(f, "Invalid Request: {msg}\n\n{bt}\n")
69 }
70 Error::Internal(e) => write!(f, "{e:?}"),
71 }
72 }
73}
74
75pub type Result<T, E = Error> = std::result::Result<T, E>;
77
78pub type CError = Error;
80pub type CResult<T> = Result<T>;
82
83impl Error {
84 pub fn host(e: impl HostError) -> Self {
86 Self::HostLang(Box::new(e))
87 }
88
89 pub fn client(msg: impl Into<String>) -> Self {
91 Self::Client {
92 msg: msg.into(),
93 bt: Backtrace::capture(),
94 }
95 }
96
97 pub fn internal(e: impl Into<anyhow::Error>) -> Self {
99 Self::Internal(e.into())
100 }
101
102 pub fn internal_msg(msg: impl Into<String>) -> Self {
104 Self::Internal(anyhow::anyhow!("{}", msg.into()))
105 }
106
107 pub fn backtrace(&self) -> Option<&Backtrace> {
110 match self {
111 Error::Client { bt, .. } => Some(bt),
112 Error::Internal(e) => Some(e.backtrace()),
113 Error::Context { source, .. } => source.0.backtrace(),
114 Error::HostLang(_) => None,
115 }
116 }
117
118 pub fn without_contexts(&self) -> &Error {
121 match self {
122 Error::Context { source, .. } => source.0.without_contexts(),
123 other => other,
124 }
125 }
126
127 pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
129 match self {
130 Error::Context { source, .. } => Some(source.as_ref()),
131 Error::HostLang(e) => Some(e.as_ref()),
132 Error::Internal(e) => e.source(),
133 Error::Client { .. } => None,
134 }
135 }
136
137 pub fn context<C: Into<String>>(self, context: C) -> Self {
139 Self::Context {
140 msg: context.into(),
141 source: Box::new(SError(self)),
142 }
143 }
144
145 pub fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Self {
148 Self::Context {
149 msg: f().into(),
150 source: Box::new(SError(self)),
151 }
152 }
153
154 pub fn std_error(self) -> SError {
157 SError(self)
158 }
159
160 fn format_context(&self, f: &mut std::fmt::Formatter<'_>) -> Result<&Error, std::fmt::Error> {
161 let mut current = self;
162 if matches!(current, Error::Context { .. }) {
163 write!(f, "\nContext:\n")?;
164 let mut next_id = 1;
165 while let Error::Context { msg, source } = current {
166 writeln!(f, " {next_id}: {msg}")?;
167 current = source.inner();
168 next_id += 1;
169 }
170 }
171 Ok(current)
172 }
173}
174
175impl StdError for Error {
176 fn source(&self) -> Option<&(dyn StdError + 'static)> {
177 self.source()
178 }
179}
180
181impl From<anyhow::Error> for Error {
185 fn from(e: anyhow::Error) -> Self {
186 Error::Internal(e)
187 }
188}
189
190impl From<std::io::Error> for Error {
191 fn from(e: std::io::Error) -> Self {
192 Error::Internal(e.into())
193 }
194}
195#[cfg(any(
196 feature = "concur_control",
197 feature = "retryable",
198 feature = "batching"
199))]
200impl From<tokio::task::JoinError> for Error {
201 fn from(e: tokio::task::JoinError) -> Self {
202 Error::Internal(e.into())
203 }
204}
205#[cfg(any(
206 feature = "concur_control",
207 feature = "retryable",
208 feature = "batching"
209))]
210impl From<tokio::sync::oneshot::error::RecvError> for Error {
211 fn from(e: tokio::sync::oneshot::error::RecvError) -> Self {
212 Error::Internal(e.into())
213 }
214}
215#[cfg(feature = "fingerprint")]
216impl From<base64::DecodeError> for Error {
217 fn from(e: base64::DecodeError) -> Self {
218 Error::Internal(e.into())
219 }
220}
221
222impl From<ResidualError> for Error {
223 fn from(e: ResidualError) -> Self {
224 Error::Internal(anyhow::Error::from(e))
225 }
226}
227#[cfg(feature = "fingerprint")]
228impl From<crate::fingerprint::FingerprinterError> for Error {
229 fn from(e: crate::fingerprint::FingerprinterError) -> Self {
230 Error::Internal(anyhow::Error::new(e))
231 }
232}
233
234impl From<ApiError> for Error {
235 fn from(e: ApiError) -> Self {
236 Error::Internal(e.err)
237 }
238}
239
240impl<T> From<std::sync::PoisonError<T>> for Error {
241 fn from(e: std::sync::PoisonError<T>) -> Self {
242 Error::Internal(anyhow::anyhow!("Mutex poison error: {}", e))
243 }
244}
245impl From<std::num::ParseIntError> for Error {
246 fn from(e: std::num::ParseIntError) -> Self {
247 Error::Internal(e.into())
248 }
249}
250
251impl From<std::str::ParseBoolError> for Error {
252 fn from(e: std::str::ParseBoolError) -> Self {
253 Error::Internal(e.into())
254 }
255}
256
257impl From<std::fmt::Error> for Error {
258 fn from(e: std::fmt::Error) -> Self {
259 Error::Internal(e.into())
260 }
261}
262
263impl From<std::string::FromUtf8Error> for Error {
264 fn from(e: std::string::FromUtf8Error) -> Self {
265 Error::Internal(e.into())
266 }
267}
268
269impl From<std::borrow::Cow<'_, str>> for Error {
270 fn from(e: std::borrow::Cow<'_, str>) -> Self {
271 Error::Internal(anyhow::anyhow!("{}", e))
272 }
273}
274#[cfg(any(
275 feature = "concur_control",
276 feature = "retryable",
277 feature = "batching"
278))]
279impl From<tokio::sync::AcquireError> for Error {
280 fn from(e: tokio::sync::AcquireError) -> Self {
281 Error::Internal(e.into())
282 }
283}
284#[cfg(any(
285 feature = "concur_control",
286 feature = "retryable",
287 feature = "batching"
288))]
289impl From<tokio::sync::watch::error::RecvError> for Error {
290 fn from(e: tokio::sync::watch::error::RecvError) -> Self {
291 Error::Internal(e.into())
292 }
293}
294
295pub trait ContextExt<T> {
300 fn context<C: Into<String>>(self, context: C) -> Result<T>;
302 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T>;
304}
305
306impl<T> ContextExt<T> for Result<T> {
307 fn context<C: Into<String>>(self, context: C) -> Result<T> {
308 self.map_err(|e| e.context(context))
309 }
310
311 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T> {
312 self.map_err(|e| e.with_context(f))
313 }
314}
315
316pub trait StdContextExt<T, E> {
319 fn context<C: Into<String>>(self, context: C) -> Result<T>;
321 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T>;
324}
325
326impl<T, E: StdError + Send + Sync + 'static> StdContextExt<T, E> for Result<T, E> {
327 fn context<C: Into<String>>(self, context: C) -> Result<T> {
328 self.map_err(|e| Error::internal(e).context(context))
329 }
330
331 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T> {
332 self.map_err(|e| Error::internal(e).with_context(f))
333 }
334}
335
336impl<T> ContextExt<T> for Option<T> {
337 fn context<C: Into<String>>(self, context: C) -> Result<T> {
338 self.ok_or_else(|| Error::client(context))
339 }
340
341 fn with_context<C: Into<String>, F: FnOnce() -> C>(self, f: F) -> Result<T> {
342 self.ok_or_else(|| Error::client(f()))
343 }
344}
345
346#[macro_export]
348macro_rules! client_bail {
349 ( $fmt:literal $(, $($arg:tt)*)?) => {
350 return Err($crate::error::Error::client(format!($fmt $(, $($arg)*)?)))
351 };
352}
353
354#[macro_export]
356macro_rules! client_error {
357 ( $fmt:literal $(, $($arg:tt)*)?) => {
358 $crate::error::Error::client(format!($fmt $(, $($arg)*)?))
359 };
360}
361
362#[macro_export]
364macro_rules! internal_bail {
365 ( $fmt:literal $(, $($arg:tt)*)?) => {
366 return Err($crate::error::Error::internal_msg(format!($fmt $(, $($arg)*)?)))
367 };
368}
369
370#[macro_export]
372macro_rules! internal_error {
373 ( $fmt:literal $(, $($arg:tt)*)?) => {
374 $crate::error::Error::internal_msg(format!($fmt $(, $($arg)*)?))
375 };
376}
377
378pub struct SError(Error);
381
382impl SError {
383 pub fn inner(&self) -> &Error {
385 &self.0
386 }
387}
388
389impl Display for SError {
390 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
391 Display::fmt(&self.0, f)
392 }
393}
394
395impl Debug for SError {
396 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397 Debug::fmt(&self.0, f)
398 }
399}
400
401impl std::error::Error for SError {
402 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
403 self.0.source()
404 }
405}
406
407struct ResidualErrorData {
408 message: String,
409 debug: String,
410}
411
412#[derive(Clone)]
418pub struct ResidualError(Arc<ResidualErrorData>);
419
420impl ResidualError {
421 pub fn new<Err: Display + Debug>(err: &Err) -> Self {
423 Self(Arc::new(ResidualErrorData {
424 message: err.to_string(),
425 debug: err.to_string(),
426 }))
427 }
428}
429
430impl Display for ResidualError {
431 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
432 write!(f, "{}", self.0.message)
433 }
434}
435
436impl Debug for ResidualError {
437 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
438 write!(f, "{}", self.0.debug)
439 }
440}
441
442impl StdError for ResidualError {}
443
444enum SharedErrorState {
445 Error(Error),
446 ResidualErrorMessage(ResidualError),
447}
448
449#[derive(Clone)]
455pub struct SharedError(Arc<Mutex<SharedErrorState>>);
456
457impl SharedError {
458 pub fn new(err: Error) -> Self {
460 Self(Arc::new(Mutex::new(SharedErrorState::Error(err))))
461 }
462
463 fn extract_error(&self) -> Error {
464 let mut state = self.0.lock().unwrap();
465 let mut_state = &mut *state;
466
467 let residual_err = match mut_state {
468 SharedErrorState::ResidualErrorMessage(err) => {
469 return Error::internal(err.clone());
471 }
472 SharedErrorState::Error(err) => ResidualError::new(err),
473 };
474
475 let orig_state = std::mem::replace(
476 mut_state,
477 SharedErrorState::ResidualErrorMessage(residual_err),
478 );
479 let SharedErrorState::Error(err) = orig_state else {
480 panic!("Expected shared error state to hold Error");
481 };
482 err
483 }
484}
485
486impl Debug for SharedError {
487 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
488 let state = self.0.lock().unwrap();
489 match &*state {
490 SharedErrorState::Error(err) => Debug::fmt(err, f),
491 SharedErrorState::ResidualErrorMessage(err) => Debug::fmt(err, f),
492 }
493 }
494}
495
496impl Display for SharedError {
497 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
498 let state = self.0.lock().unwrap();
499 match &*state {
500 SharedErrorState::Error(err) => Display::fmt(err, f),
501 SharedErrorState::ResidualErrorMessage(err) => Display::fmt(err, f),
502 }
503 }
504}
505
506impl From<Error> for SharedError {
507 fn from(err: Error) -> Self {
508 Self(Arc::new(Mutex::new(SharedErrorState::Error(err))))
509 }
510}
511
512pub fn shared_ok<T>(value: T) -> std::result::Result<T, SharedError> {
515 Ok(value)
516}
517
518pub type SharedResult<T> = std::result::Result<T, SharedError>;
520
521pub trait SharedResultExt<T> {
524 fn into_result(self) -> Result<T>;
526}
527
528impl<T> SharedResultExt<T> for std::result::Result<T, SharedError> {
529 fn into_result(self) -> Result<T> {
530 match self {
531 Ok(value) => Ok(value),
532 Err(err) => Err(err.extract_error()),
533 }
534 }
535}
536
537pub trait SharedResultExtRef<'a, T> {
540 fn into_result(self) -> Result<&'a T>;
542}
543
544impl<'a, T> SharedResultExtRef<'a, T> for &'a std::result::Result<T, SharedError> {
545 fn into_result(self) -> Result<&'a T> {
546 match self {
547 Ok(value) => Ok(value),
548 Err(err) => Err(err.extract_error()),
549 }
550 }
551}
552
553pub fn invariance_violation() -> anyhow::Error {
556 anyhow::anyhow!("Invariance violation")
557}
558
559#[derive(Debug)]
561pub struct ApiError {
562 pub err: anyhow::Error,
563}
564
565impl ApiError {
566 pub fn new(message: &str) -> Self {
568 Self {
569 err: anyhow::anyhow!("{}", message),
570 }
571 }
572}
573
574impl Display for ApiError {
575 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
576 Display::fmt(&self.err, f)
577 }
578}
579
580impl StdError for ApiError {
581 fn source(&self) -> Option<&(dyn StdError + 'static)> {
582 self.err.source()
583 }
584}
585
586impl From<anyhow::Error> for ApiError {
587 fn from(err: anyhow::Error) -> ApiError {
588 if err.is::<ApiError>() {
589 return err.downcast::<ApiError>().unwrap();
590 }
591 Self { err }
592 }
593}
594impl From<Error> for ApiError {
595 fn from(err: Error) -> ApiError {
596 ApiError {
597 err: anyhow::Error::from(err.std_error()),
598 }
599 }
600}
601#[macro_export]
604macro_rules! api_bail {
605 ( $fmt:literal $(, $($arg:tt)*)?) => {
606 return Err($crate::error::ApiError::new(&format!($fmt $(, $($arg)*)?)).into())
607 };
608}
609
610#[macro_export]
612macro_rules! api_error {
613 ( $fmt:literal $(, $($arg:tt)*)?) => {
614 $crate::error::ApiError::new(&format!($fmt $(, $($arg)*)?))
615 };
616}
617
618#[cfg(test)]
619#[cfg_attr(coverage_nightly, coverage(off))]
620mod tests {
621 use super::*;
622 use std::backtrace::BacktraceStatus;
623 use std::io;
624
625 #[derive(Debug)]
626 struct MockHostError(String);
627
628 impl Display for MockHostError {
629 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630 write!(f, "MockHostError: {}", self.0)
631 }
632 }
633
634 impl StdError for MockHostError {}
635
636 #[test]
637 fn test_client_error_creation() {
638 let err = Error::client("invalid input");
639 assert!(matches!(&err, Error::Client { msg, .. } if msg == "invalid input"));
640 assert!(matches!(err.without_contexts(), Error::Client { .. }));
641 }
642
643 #[test]
644 fn test_internal_error_creation() {
645 let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
646 let err: Error = io_err.into();
647 assert!(matches!(err, Error::Internal { .. }));
648 }
649
650 #[test]
651 fn test_internal_msg_error_creation() {
652 let err = Error::internal_msg("something went wrong");
653 assert!(matches!(err, Error::Internal { .. }));
654 assert_eq!(err.to_string(), "something went wrong");
655 }
656
657 #[test]
658 fn test_host_error_creation_and_detection() {
659 let mock = MockHostError("test error".to_string());
660 let err = Error::host(mock);
661 assert!(matches!(err.without_contexts(), Error::HostLang(_)));
662
663 if let Error::HostLang(host_err) = err.without_contexts() {
664 let any: &dyn Any = host_err.as_ref();
665 let downcasted = any.downcast_ref::<MockHostError>();
666 assert!(downcasted.is_some());
667 assert_eq!(downcasted.unwrap().0, "test error");
668 } else {
669 panic!("Expected HostLang variant");
670 }
671 }
672
673 #[test]
674 fn test_context_chaining() {
675 let inner = Error::client("base error");
676 let with_context: Result<()> = Err(inner);
677 let wrapped = ContextExt::context(
678 ContextExt::context(ContextExt::context(with_context, "layer 1"), "layer 2"),
679 "layer 3",
680 );
681
682 let err = wrapped.unwrap_err();
683 assert!(matches!(&err, Error::Context { msg, .. } if msg == "layer 3"));
684
685 if let Error::Context { source, .. } = &err {
686 assert!(
687 matches!(source.as_ref(), SError(Error::Context { msg, .. }) if msg == "layer 2")
688 );
689 }
690 assert_eq!(
691 err.to_string(),
692 "\nContext:\
693 \n 1: layer 3\
694 \n 2: layer 2\
695 \n 3: layer 1\
696 \nInvalid Request: base error"
697 );
698 }
699
700 #[test]
701 fn test_context_preserves_host_error() {
702 let mock = MockHostError("original python error".to_string());
703 let err = Error::host(mock);
704 let wrapped: Result<()> = Err(err);
705 let with_context = ContextExt::context(wrapped, "while processing request");
706
707 let final_err = with_context.unwrap_err();
708 assert!(matches!(final_err.without_contexts(), Error::HostLang(_)));
709
710 if let Error::HostLang(host_err) = final_err.without_contexts() {
711 let any: &dyn Any = host_err.as_ref();
712 let downcasted = any.downcast_ref::<MockHostError>();
713 assert!(downcasted.is_some());
714 assert_eq!(downcasted.unwrap().0, "original python error");
715 } else {
716 panic!("Expected HostLang variant");
717 }
718 }
719
720 #[test]
721 fn test_backtrace_captured_for_client_error() {
722 let err = Error::client("test");
723 let bt = err.backtrace();
724 assert!(bt.is_some());
725 let status = bt.unwrap().status();
726 assert!(
727 status == BacktraceStatus::Captured
728 || status == BacktraceStatus::Disabled
729 || status == BacktraceStatus::Unsupported
730 );
731 }
732
733 #[test]
734 fn test_backtrace_captured_for_internal_error() {
735 let err = Error::internal_msg("test internal");
736 let bt = err.backtrace();
737 assert!(bt.is_some());
738 }
739
740 #[test]
741 fn test_backtrace_traverses_context() {
742 let inner = Error::internal_msg("base");
743 let wrapped: Result<()> = Err(inner);
744 let with_context = ContextExt::context(wrapped, "context");
745
746 let err = with_context.unwrap_err();
747 let bt = err.backtrace();
748 assert!(bt.is_some());
749 }
750
751 #[test]
752 fn test_option_context_ext() {
753 let opt: Option<i32> = None;
754 let result = opt.context("value was missing");
755
756 assert!(result.is_err());
757 let err = result.unwrap_err();
758 assert!(matches!(err.without_contexts(), Error::Client { .. }));
759 assert!(matches!(&err, Error::Client { msg, .. } if msg == "value was missing"));
760 }
761
762 #[test]
763 fn test_error_display_formats() {
764 let client_err = Error::client("bad input");
765 assert_eq!(client_err.to_string(), "Invalid Request: bad input");
766
767 let internal_err = Error::internal_msg("db connection failed");
768 assert_eq!(internal_err.to_string(), "db connection failed");
769
770 let host_err = Error::host(MockHostError("py error".to_string()));
771 assert_eq!(host_err.to_string(), "MockHostError: py error");
772 }
773
774 #[test]
775 fn test_error_source_chain() {
776 let inner = Error::internal_msg("root cause");
777 let wrapped: Result<()> = Err(inner);
778 let outer = ContextExt::context(wrapped, "outer context").unwrap_err();
779
780 let source = outer.source();
781 assert!(source.is_some());
782 }
783
784 #[test]
785 fn test_internal_from_anyhow() {
786 let err = Error::internal(anyhow::anyhow!("boom"));
787 assert!(matches!(err, Error::Internal(_)));
788 assert_eq!(err.to_string(), "boom");
789 }
790
791 #[test]
792 fn test_source_per_variant() {
793 assert!(Error::client("x").source().is_none());
794 assert!(Error::host(MockHostError("x".into())).source().is_some());
795 }
796
797 #[test]
798 fn test_backtrace_none_for_host_error() {
799 let err = Error::host(MockHostError("x".into()));
800 assert!(err.backtrace().is_none());
801 }
802
803 #[test]
804 fn test_with_context_is_lazy() {
805 let inner = Error::client("base");
806 let wrapped: Result<()> = Err(inner);
807 let err = ContextExt::with_context(wrapped, || "lazy ctx").unwrap_err();
808 assert!(matches!(&err, Error::Context { msg, .. } if msg == "lazy ctx"));
809 }
810
811 #[test]
812 fn test_option_with_context_is_lazy() {
813 let opt: Option<i32> = None;
814 let err = ContextExt::with_context(opt, || "missing value").unwrap_err();
815 assert!(matches!(&err, Error::Client { msg, .. } if msg == "missing value"));
816 }
817
818 #[test]
819 fn test_std_context_ext_wraps_foreign_error() {
820 let r: std::result::Result<(), io::Error> = Err(io::Error::other("io failure"));
821 let err = StdContextExt::context(r, "while doing io").unwrap_err();
822 assert!(matches!(&err, Error::Context { msg, .. } if msg == "while doing io"));
823 assert!(matches!(err.without_contexts(), Error::Internal(_)));
824 }
825
826 #[test]
827 fn test_std_context_ext_with_context_is_lazy() {
828 let r: std::result::Result<(), io::Error> = Err(io::Error::other("io failure"));
829 let err = StdContextExt::with_context(r, || "lazy io ctx").unwrap_err();
830 assert!(matches!(&err, Error::Context { msg, .. } if msg == "lazy io ctx"));
831 }
832
833 #[test]
834 fn test_std_error_wrapper_roundtrip() {
835 let serr = Error::client("boom").std_error();
836 assert_eq!(serr.to_string(), "Invalid Request: boom");
837 assert!(matches!(serr.inner(), Error::Client { .. }));
838 assert!(format!("{serr:?}").contains("boom"));
839 }
840
841 #[test]
842 fn test_invariance_violation_message() {
843 assert_eq!(invariance_violation().to_string(), "Invariance violation");
844 }
845
846 #[test]
849 fn test_client_macros() {
850 fn bail() -> Result<()> {
851 client_bail!("bad {}", 42);
852 }
853 assert!(matches!(&bail().unwrap_err(), Error::Client { msg, .. } if msg == "bad 42"));
854
855 let e = client_error!("oops {}", 1);
856 assert!(matches!(&e, Error::Client { msg, .. } if msg == "oops 1"));
857 }
858
859 #[test]
860 fn test_internal_macros() {
861 fn bail() -> Result<()> {
862 internal_bail!("internal {}", 7);
863 }
864 let err = bail().unwrap_err();
865 assert!(matches!(err, Error::Internal(_)));
866 assert_eq!(err.to_string(), "internal 7");
867
868 let e = internal_error!("ierr {}", 2);
869 assert_eq!(e.to_string(), "ierr 2");
870 }
871
872 #[test]
873 fn test_api_macros() {
874 fn bail() -> std::result::Result<(), ApiError> {
875 api_bail!("api {}", 9);
876 }
877 assert_eq!(bail().unwrap_err().to_string(), "api 9");
878 assert_eq!(api_error!("aerr {}", 3).to_string(), "aerr 3");
879 }
880
881 #[test]
884 fn test_residual_error_formats_and_converts() {
885 let base = Error::client("residual base");
886 let residual = ResidualError::new(&base);
887 assert!(residual.to_string().contains("residual base"));
888 assert!(format!("{residual:?}").contains("residual base"));
889
890 let err: Error = residual.into();
891 assert!(matches!(err, Error::Internal(_)));
892 }
893
894 #[test]
897 fn test_shared_error_degrades_after_first_extraction() {
898 let shared = SharedError::new(Error::client("shared boom"));
899 assert!(shared.to_string().contains("shared boom"));
900 assert!(format!("{shared:?}").contains("shared boom"));
901
902 let first: SharedResult<()> = Err(shared.clone());
904 let extracted = first.into_result().unwrap_err();
905 assert!(matches!(extracted.without_contexts(), Error::Client { .. }));
906
907 assert!(shared.to_string().contains("shared boom"));
910 let second: SharedResult<()> = Err(shared.clone());
911 assert!(matches!(
912 second.into_result().unwrap_err(),
913 Error::Internal(_)
914 ));
915 }
916
917 #[test]
918 fn test_shared_ok_and_ref_extension() {
919 assert_eq!(shared_ok::<i32>(5).into_result().unwrap(), 5);
920
921 let ok: SharedResult<i32> = Ok(10);
922 assert_eq!(*(&ok).into_result().unwrap(), 10);
923
924 let errored: SharedResult<i32> = Err(SharedError::new(Error::client("e")));
925 assert!((&errored).into_result().is_err());
926 }
927
928 #[test]
929 fn test_from_error_for_shared_error() {
930 let shared: SharedError = Error::internal_msg("x").into();
931 assert!(shared.to_string().contains("x"));
932 }
933
934 #[test]
937 fn test_api_error_new_display_and_into_error() {
938 let api = ApiError::new("api boom");
939 assert_eq!(api.to_string(), "api boom");
940
941 let err: Error = api.into();
942 assert!(matches!(err, Error::Internal(_)));
943 }
944
945 #[test]
946 fn test_api_error_from_anyhow_passthrough_and_downcast() {
947 let api: ApiError = anyhow::anyhow!("plain").into();
948 assert_eq!(api.to_string(), "plain");
949
950 let any = anyhow::Error::new(ApiError::new("nested"));
952 let api2: ApiError = any.into();
953 assert_eq!(api2.to_string(), "nested");
954 }
955
956 #[test]
957 fn test_api_error_from_core_error() {
958 let api: ApiError = Error::client("bad request").into();
959 assert!(api.to_string().contains("bad request"));
960 }
961
962 #[test]
965 fn test_from_std_library_errors() {
966 let _: Error = "5x".parse::<i32>().unwrap_err().into();
967 let _: Error = "notbool".parse::<bool>().unwrap_err().into();
968 let _: Error = std::fmt::Error.into();
969 let _: Error = String::from_utf8(vec![0xff, 0xfe]).unwrap_err().into();
970
971 let cow: std::borrow::Cow<str> = std::borrow::Cow::Borrowed("cow error");
972 let e: Error = cow.into();
973 assert!(e.to_string().contains("cow error"));
974 }
975
976 #[test]
977 fn test_from_poison_error() {
978 use std::sync::{Arc, Mutex};
979 let m = Arc::new(Mutex::new(0));
980 let m2 = m.clone();
981 let _ = std::thread::spawn(move || {
983 let _guard = m2.lock().unwrap();
984 panic!("intentional poison");
985 })
986 .join();
987
988 let poison = m.lock().unwrap_err();
989 let err: Error = poison.into();
990 assert!(matches!(err, Error::Internal(_)));
991 assert!(err.to_string().contains("Mutex poison"));
992 }
993
994 #[cfg(feature = "fingerprint")]
995 #[test]
996 fn test_from_base64_decode_error() {
997 use base64::Engine as _;
998 let decode_err = base64::prelude::BASE64_STANDARD.decode("a").unwrap_err();
999 let err: Error = decode_err.into();
1000 assert!(matches!(err, Error::Internal(_)));
1001 }
1002
1003 #[cfg(feature = "fingerprint")]
1004 #[test]
1005 fn test_from_fingerprinter_error() {
1006 use serde::ser::Error as _;
1007 let fe = crate::fingerprint::FingerprinterError::custom("fp boom");
1008 let err: Error = fe.into();
1009 assert!(matches!(err, Error::Internal(_)));
1010 }
1011
1012 #[cfg(any(
1013 feature = "concur_control",
1014 feature = "retryable",
1015 feature = "batching"
1016 ))]
1017 #[tokio::test]
1018 async fn test_from_tokio_errors() {
1019 let (tx, rx) = tokio::sync::oneshot::channel::<i32>();
1021 drop(tx);
1022 let _: Error = rx.await.unwrap_err().into();
1023
1024 let sem = tokio::sync::Semaphore::new(1);
1026 sem.close();
1027 let _: Error = sem.acquire().await.unwrap_err().into();
1028
1029 let handle = tokio::spawn(async { panic!("boom") });
1031 let _: Error = handle.await.unwrap_err().into();
1032
1033 let (wtx, mut wrx) = tokio::sync::watch::channel(1);
1035 drop(wtx);
1036 let _: Error = wrx.changed().await.unwrap_err().into();
1037 }
1038}