heddle_thread_api/
transport.rs1use std::future::Future;
4
5use api::{
6 framing,
7 heddle::api::common::{CallContext, CallFailure},
8 v2::MethodDescriptor,
9};
10use prost::Message;
11
12#[cfg(feature = "iroh")]
13mod iroh;
14#[cfg(feature = "iroh")]
15pub use iroh::{IrohTransport, Reader, Writer, accepted_stream};
16
17#[derive(Debug, thiserror::Error)]
18pub enum Error {
19 #[error("transport I/O: {0}")]
20 Io(String),
21 #[error("RPC made no progress before its timeout")]
22 Timeout,
23 #[error("invalid v2 transport: {0}")]
24 Protocol(&'static str),
25 #[error(
26 "original and matched evidence exceed batch budget: operation {operation} requires {bytes} bytes (limit {limit})"
27 )]
28 OriginalOperationTooLarge {
29 operation: usize,
30 bytes: usize,
31 limit: usize,
32 },
33 #[error("RPC failed: {0:?}")]
34 Remote(RemoteFailure),
35 #[error(transparent)]
36 Framing(#[from] framing::FrameError),
37 #[error(transparent)]
38 Metadata(#[from] api::RequestMetadataError),
39 #[error(transparent)]
40 Decode(#[from] prost::DecodeError),
41}
42
43#[derive(Debug)]
47pub struct RemoteFailure {
48 pub code: i32,
49 pub message: String,
50 detail: Option<Vec<u8>>,
51}
52impl RemoteFailure {
53 pub fn detail(
54 &self,
55 ) -> Result<Option<api::heddle::api::common::ErrorDetail>, prost::DecodeError> {
56 self.detail
57 .as_deref()
58 .map(prost::Message::decode)
59 .transpose()
60 }
61}
62impl From<CallFailure> for RemoteFailure {
63 fn from(failure: CallFailure) -> Self {
64 Self {
65 code: failure.code,
66 message: failure.message,
67 detail: failure.error.map(|e| e.encode_to_vec()),
68 }
69 }
70}
71
72pub trait Authorize: Send + Sync {
76 fn context(
77 &self,
78 method: &'static MethodDescriptor,
79 body: &[u8],
80 ) -> impl Future<Output = Result<CallContext, Error>> + Send;
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn remote_failure_retains_typed_details_without_boxing_the_error_path() {
89 use api::heddle::api::common::{ErrorDetail, ErrorReason};
90 let detail = ErrorDetail {
91 reason: ErrorReason::PolicyDenied as i32,
92 resource: "thread".into(),
93 ..Default::default()
94 };
95 let failure = RemoteFailure::from(CallFailure {
96 code: 7,
97 message: "review required".into(),
98 error: Some(detail.clone()),
99 });
100 assert_eq!(failure.detail().expect("typed detail"), Some(detail));
101 assert_eq!(failure.message, "review required");
102 }
103}