1#[cfg(feature = "native")]
5pub mod authority;
6#[cfg(feature = "replication")]
7pub mod authority_admission;
8#[cfg(feature = "semantic-analysis")]
9pub mod behavior;
10#[cfg(feature = "replication")]
11pub mod boundary_acceptance;
12#[cfg(feature = "replication")]
13pub mod collaboration;
14pub mod content;
15#[cfg(feature = "replication")]
16pub mod creation;
17#[cfg(feature = "signing")]
18pub mod credentials;
19#[cfg(feature = "replication")]
20pub mod evidence;
21#[cfg(feature = "source-transfer")]
22pub mod fetch;
23#[cfg(feature = "replication")]
24pub mod live_replication;
25pub mod observation;
26#[cfg(feature = "root-attachment")]
27pub mod pairing;
28#[cfg(any(feature = "native", feature = "replication", feature = "iroh"))]
29pub mod publication;
30mod reopen;
31#[cfg(feature = "replication")]
32pub mod replication;
33#[cfg(all(feature = "native", feature = "iroh"))]
34pub mod replication_rpc;
35#[cfg(feature = "signing")]
36pub mod request_proof;
37#[cfg(feature = "root-attachment")]
38pub mod root_attachment;
39#[cfg(feature = "replication")]
40pub mod thread_control;
41#[cfg(feature = "replication")]
42pub mod thread_ownership;
43pub mod transport;
44
45use api::v2::client::{Client, ClientError, RpcTransport};
46pub use api::{
47 heddle::api::v1alpha2 as contract,
48 v2::{client::Rpc, rpc},
49};
50use contract::{DescribeEndpointRequest, DescribeEndpointResponse, EndpointKind, ThreadRef};
51pub use reopen::is_reopen_retryable;
52use transport::Error;
53
54pub struct Remote<T: RpcTransport<Error = Error>> {
57 pub api: Client<T>,
58 pub description: DescribeEndpointResponse,
59}
60
61impl<T: RpcTransport<Error = Error>> Remote<T> {
62 pub async fn discover(
65 transport: T,
66 endpoint_key: [u8; 32],
67 kind: EndpointKind,
68 ) -> Result<Self, ClientError<Error>> {
69 let bytes = transport
70 .unary(
71 rpc::EndpointServiceDescribeEndpoint::METHOD,
72 prost::Message::encode_to_vec(&DescribeEndpointRequest {
73 understood_packages: vec!["heddle.api.v1alpha2".into()],
74 }),
75 )
76 .await
77 .map_err(ClientError::Transport)?;
78 let description: DescribeEndpointResponse = prost::Message::decode(bytes.as_slice())?;
79 if description
80 .endpoint
81 .as_ref()
82 .is_none_or(|source| source.public_key != endpoint_key || source.kind != kind as i32)
83 || !description
84 .supported_packages
85 .iter()
86 .any(|p| p == "heddle.api.v1alpha2")
87 {
88 return Err(ClientError::Transport(Error::Protocol(
89 "endpoint identity/package mismatch",
90 )));
91 }
92 let api = Client::new(transport, description.implemented_methods.clone());
93 Ok(Self { api, description })
94 }
95
96 pub async fn observe<M>(
99 &self,
100 mut request: M::Request,
101 resume: Option<observation::Resume>,
102 ) -> Result<observation::Observation<T::Reader, M::Response>, observation::Error>
103 where
104 M: api::v2::client::ServerStreamingRpc,
105 M::Request: observation::ObservationRequest,
106 M::Response: observation::ObservedEvent,
107 {
108 use crate::reopen::ReopenRetryable as _;
109 use observation::ObservationRequest as _;
110 let budget = observation::budget(&self.description)?;
111 let options = request.options_mut();
112 options.budget = Some(budget);
113 options.after_cursor.clear();
114 let mut query = b"heddle-observation-query-v2\0".to_vec();
115 query.extend_from_slice(M::METHOD.path.as_bytes());
116 query.push(0);
117 query.extend_from_slice(&prost::Message::encode_to_vec(&request));
118 observation::validate_resume(&resume, &self.description, &query)?;
119 if let Some(resume) = &resume {
120 request.options_mut().after_cursor = resume.cursor.clone();
121 }
122 let mut attempt = 0;
123 loop {
124 let messages = match self.api.observe::<M>(&request).await {
125 Ok(messages) => messages,
126 Err(error)
127 if reopen::client_error_is_reopen_retryable(&error)
128 && attempt + 1 < reopen::ATTEMPTS =>
129 {
130 attempt += 1;
131 reopen::backoff(attempt).await;
132 continue;
133 }
134 Err(error) => return Err(error.into()),
135 };
136 let mut observation = observation::Observation::new(
137 messages,
138 &self.description,
139 budget,
140 resume.clone(),
141 query.clone(),
142 )?;
143 match observation.consume_open().await {
144 Ok(()) => return Ok(observation),
145 Err(error) if error.is_reopen_retryable() && attempt + 1 < reopen::ATTEMPTS => {
146 attempt += 1;
147 reopen::backoff(attempt).await;
148 }
149 Err(error) => {
150 observation.prime_error(error);
151 return Ok(observation);
152 }
153 }
154 }
155 }
156
157 pub async fn observe_analysis(
160 &self,
161 request: contract::ObserveAnalysisRequest,
162 resume: Option<observation::Resume>,
163 ) -> Result<observation::AnalysisObservation<T::Reader>, observation::Error> {
164 self.observe::<rpc::AnalysisServiceObserveAnalysis>(request, resume)
165 .await
166 }
167
168 pub fn thread(&self, thread: ThreadRef) -> Thread<'_, T> {
171 Thread {
172 remote: self,
173 reference: thread,
174 }
175 }
176}
177
178pub struct Thread<'a, T: RpcTransport<Error = Error>> {
179 remote: &'a Remote<T>,
180 pub reference: ThreadRef,
181}
182
183impl<T: RpcTransport<Error = Error>> Thread<'_, T> {
184 #[cfg(feature = "replication")]
187 pub async fn revise_intent(
188 &self,
189 command: &thread_control::PreparedControl,
190 ) -> Result<contract::ThreadMutationResponse, ClientError<Error>> {
191 let request = command.revise_intent().map_err(ClientError::Transport)?;
192 if request.thread.as_ref() != Some(&self.reference) {
193 return Err(ClientError::Transport(Error::Protocol(
194 "prepared command belongs to another Thread",
195 )));
196 }
197 self.remote
198 .api
199 .call::<rpc::ThreadServiceReviseIntent>(&request)
200 .await
201 }
202
203 pub async fn observe(
204 &self,
205 sections: &[contract::ThreadSection],
206 mode: contract::ObservationMode,
207 resume: Option<observation::Resume>,
208 ) -> Result<observation::ThreadObservation<T::Reader>, observation::Error> {
209 self.remote
210 .observe::<rpc::ThreadServiceObserveThread>(
211 contract::ObserveThreadRequest {
212 thread: Some(self.reference.clone()),
213 sections: sections.iter().map(|section| *section as i32).collect(),
214 observe: Some(contract::ObserveOptions {
215 mode: mode as i32,
216 ..Default::default()
217 }),
218 ..Default::default()
219 },
220 resume,
221 )
222 .await
223 }
224}