Skip to main content

lores_p2panda_client/
lib.rs

1use tonic::transport::Channel;
2
3pub mod proto {
4    tonic::include_proto!("lores.panda.v1");
5}
6
7use proto::{
8    ListRegionsRequest, ListRegionsResponse, OperationEvent, PublishRequest, PublishResponse,
9    SubscribeRequest, panda_client::PandaClient as TonicPandaClient,
10};
11use tonic::{Response, Status, Streaming};
12
13/// Client for the lores-p2panda-server gRPC API.
14pub struct PandaClient {
15    inner: TonicPandaClient<Channel>,
16}
17
18impl PandaClient {
19    /// Connect to a lores-p2panda-server at the given endpoint URI.
20    ///
21    /// # Example
22    /// ```no_run
23    /// # tokio_test::block_on(async {
24    /// let client = lores_p2panda_client::PandaClient::connect("http://[::1]:50051").await.unwrap();
25    /// # });
26    /// ```
27    pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
28    where
29        D: TryInto<tonic::transport::Endpoint>,
30        D::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
31    {
32        let inner = TonicPandaClient::connect(dst).await?;
33        Ok(Self { inner })
34    }
35
36    /// Create a client with a lazy channel — no connection is made until the
37    /// first RPC call, so the process starts cleanly even if the gRPC server
38    /// is not yet available.
39    pub fn connect_lazy<D>(dst: D) -> Result<Self, tonic::transport::Error>
40    where
41        D: TryInto<tonic::transport::Endpoint>,
42        D::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
43    {
44        let endpoint = tonic::transport::Endpoint::new(dst)?;
45        let inner = TonicPandaClient::new(endpoint.connect_lazy());
46        Ok(Self { inner })
47    }
48
49    /// Publish an operation to a region+namespace topic.
50    ///
51    /// Returns only after the operation has been persisted by the remote
52    /// p2panda node, guaranteeing eventual propagation to peers.
53    pub async fn publish(
54        &mut self,
55        region_id: [u8; 32],
56        app_namespace: impl Into<String>,
57        payload: impl Into<Vec<u8>>,
58    ) -> Result<Response<PublishResponse>, Status> {
59        let request = PublishRequest {
60            region_id: region_id.to_vec(),
61            app_namespace: app_namespace.into(),
62            payload: payload.into(),
63        };
64        self.inner.publish(request).await
65    }
66
67    /// Subscribe to a region+namespace topic and receive a stream of
68    /// [`OperationEvent`]s.
69    ///
70    /// HTTP/2 flow control provides natural backpressure.
71    pub async fn subscribe(
72        &mut self,
73        region_id: [u8; 32],
74        app_namespace: impl Into<String>,
75    ) -> Result<Response<Streaming<OperationEvent>>, Status> {
76        let request = SubscribeRequest {
77            region_id: region_id.to_vec(),
78            app_namespace: app_namespace.into(),
79        };
80        self.inner.subscribe(request).await
81    }
82
83    /// List all regions the remote node knows about.
84    pub async fn list_regions(&mut self) -> Result<Response<ListRegionsResponse>, Status> {
85        self.inner.list_regions(ListRegionsRequest {}).await
86    }
87}