@@ -10,7 +10,7 @@ pyo3::create_exception!(hiroz_py, TypeMismatchError, HirozError);
1010
1111/// Render an error and its full source chain as `outer: inner: root`.
1212///
13- /// Matches anyhow's `{:#}` output, which we lose once the error is boxed .
13+ /// Matches anyhow's `{:#}` output, which a bare `Box<dyn Error>` does not give us .
1414fn format_chain ( err : & ( dyn std:: error:: Error + ' static ) ) -> String {
1515 let mut msg = err. to_string ( ) ;
1616 let mut source = err. source ( ) ;
@@ -21,31 +21,36 @@ fn format_chain(err: &(dyn std::error::Error + 'static)) -> String {
2121 msg
2222}
2323
24- /// Map a core error to the right Python exception.
25- ///
26- /// Timeout-shaped errors become `hiroz_py.TimeoutError`; everything else
27- /// becomes `hiroz_py.HirozError`. Use this for blocking calls that raise on
28- /// failure (e.g. `ZClient.call`, `send_goal`, `get_result`). Methods whose
29- /// documented contract is to return `None` on timeout should keep doing so
30- /// rather than calling this.
31- ///
32- /// Generic over the error type because the service path yields `anyhow::Error`
33- /// while the action path yields `zenoh::Error`; both classify identically via
34- /// the core's structured detector, which walks the whole source chain — do not
35- /// string-match on the message.
36- pub ( crate ) fn map_call_error < E > ( e : E ) -> PyErr
37- where
38- E : Into < Box < dyn std:: error:: Error + Send + Sync + ' static > > ,
39- {
40- let err = e. into ( ) ;
41- let msg = format_chain ( & * err) ;
42- if hiroz:: error:: is_timeout ( & * err) {
24+ fn classify ( is_timeout : bool , msg : String ) -> PyErr {
25+ if is_timeout {
4326 TimeoutError :: new_err ( msg)
4427 } else {
4528 HirozError :: new_err ( msg)
4629 }
4730}
4831
32+ /// Map an `anyhow` error to the right Python exception.
33+ ///
34+ /// Timeout-shaped errors become `hiroz_py.TimeoutError`; everything else
35+ /// becomes `hiroz_py.HirozError`. Use this for blocking calls that raise on
36+ /// failure (e.g. `ZClient.call`). Methods whose documented contract is to
37+ /// return `None` on timeout should keep doing so rather than calling this.
38+ ///
39+ /// Classification goes through the core's structured detector, which walks the
40+ /// whole source chain — do not string-match on the message.
41+ pub ( crate ) fn map_call_error ( e : anyhow:: Error ) -> PyErr {
42+ // Deref rather than boxing: `Box<dyn Error>::from(anyhow::Error)` wraps the
43+ // value so `is_timeout`'s downcast no longer sees the real error and every
44+ // timeout is misreported as a plain HirozError.
45+ classify ( hiroz:: error:: is_timeout ( & * e) , format ! ( "{e:#}" ) )
46+ }
47+
48+ /// Same mapping for the action paths, which yield `zenoh::Error`
49+ /// (`Box<dyn Error + Send + Sync>`) rather than `anyhow::Error`.
50+ pub ( crate ) fn map_zenoh_error ( e : zenoh:: Error ) -> PyErr {
51+ classify ( hiroz:: error:: is_timeout ( & * e) , format_chain ( & * e) )
52+ }
53+
4954/// Trait for converting Rust errors to Python exceptions
5055pub ( crate ) trait IntoPyErr {
5156 fn into_pyerr ( self ) -> PyErr ;
0 commit comments