Skip to main content

crafty_client/
typed.rs

1//! Typed client wrapper (client-api `TypedClient<M>`, backlog F3).
2
3use std::marker::PhantomData;
4
5use crafty_core::{Command, Query, StateMachine};
6
7use crate::error::ClientError;
8use crate::remote::{Client, KeyedClient};
9
10/// A strongly-typed view over any [`Client`], carrying a
11/// [`StateMachine`]'s command/query/response types so callers work with real
12/// Rust values instead of `postcard` byte vectors.
13///
14/// ```ignore
15/// let typed: TypedClient<RemoteClient, KvMachine> = TypedClient::new(remote);
16/// let resp = typed.propose(&KvCommand::Set { key, value }).await?;
17/// ```
18pub struct TypedClient<C, M> {
19    inner: C,
20    _marker: PhantomData<fn() -> M>,
21}
22
23impl<C, M> TypedClient<C, M> {
24    /// Wrap a raw [`Client`] with `M`'s types.
25    pub fn new(inner: C) -> Self {
26        Self {
27            inner,
28            _marker: PhantomData,
29        }
30    }
31
32    /// Borrow the underlying raw client.
33    pub fn inner(&self) -> &C {
34        &self.inner
35    }
36
37    /// Unwrap back to the raw client.
38    pub fn into_inner(self) -> C {
39        self.inner
40    }
41}
42
43impl<C: Client, M: StateMachine> TypedClient<C, M> {
44    /// Propose a typed command and decode the typed response.
45    ///
46    /// # Errors
47    /// [`ClientError::Codec`] if the command cannot be encoded or the response
48    /// cannot be decoded, otherwise any error from the underlying [`Client`].
49    pub async fn propose(&self, command: &M::Command) -> Result<M::Response, ClientError> {
50        let payload = Command::to_bytes(command).map_err(|e| ClientError::Codec(e.to_string()))?;
51        let bytes = self.inner.propose(payload).await?;
52        crafty_proto::decode(&bytes).map_err(|e| ClientError::Codec(e.to_string()))
53    }
54
55    /// Run a typed linearizable query and decode the typed response.
56    ///
57    /// # Errors
58    /// [`ClientError::Codec`] if the query cannot be encoded or the response
59    /// cannot be decoded, otherwise any error from the underlying [`Client`].
60    pub async fn query(&self, query: &M::Query) -> Result<M::Response, ClientError> {
61        let payload = Query::to_bytes(query).map_err(|e| ClientError::Codec(e.to_string()))?;
62        let bytes = self.inner.query(payload).await?;
63        crafty_proto::decode(&bytes).map_err(|e| ClientError::Codec(e.to_string()))
64    }
65}
66
67impl<C: KeyedClient, M: StateMachine> TypedClient<C, M> {
68    /// Propose a typed command to the Raft group that owns `key`.
69    ///
70    /// # Errors
71    /// [`ClientError::Codec`] if the command cannot be encoded or the response
72    /// cannot be decoded, otherwise any error from the underlying [`KeyedClient`].
73    pub async fn propose_keyed(
74        &self,
75        key: &[u8],
76        command: &M::Command,
77    ) -> Result<M::Response, ClientError> {
78        let payload = Command::to_bytes(command).map_err(|e| ClientError::Codec(e.to_string()))?;
79        let bytes = self.inner.propose_keyed(key.to_vec(), payload).await?;
80        crafty_proto::decode(&bytes).map_err(|e| ClientError::Codec(e.to_string()))
81    }
82
83    /// Run a typed linearizable query against the Raft group that owns `key`.
84    ///
85    /// # Errors
86    /// [`ClientError::Codec`] if the query cannot be encoded or the response
87    /// cannot be decoded, otherwise any error from the underlying [`KeyedClient`].
88    pub async fn query_keyed(
89        &self,
90        key: &[u8],
91        query: &M::Query,
92    ) -> Result<M::Response, ClientError> {
93        let payload = Query::to_bytes(query).map_err(|e| ClientError::Codec(e.to_string()))?;
94        let bytes = self.inner.query_keyed(key.to_vec(), payload).await?;
95        crafty_proto::decode(&bytes).map_err(|e| ClientError::Codec(e.to_string()))
96    }
97}