use serde::{de::DeserializeOwned, Serialize};
use crate::error::ClientError;
use crate::transport::HolochainTransport;
use crate::types::{decode, encode, ConnectConfig, ConnectionStatus};
pub struct HolochainClient<T: HolochainTransport> {
transport: T,
app_id: String,
default_role: String,
}
impl<T: HolochainTransport> HolochainClient<T> {
pub fn new(transport: T, app_id: impl Into<String>, default_role: impl Into<String>) -> Self {
Self {
transport,
app_id: app_id.into(),
default_role: default_role.into(),
}
}
pub async fn connect(&self, url: &str, auth_token: Option<Vec<u8>>) -> Result<(), ClientError> {
let config = ConnectConfig {
url: url.to_string(),
app_id: self.app_id.clone(),
auth_token,
reconnect: None,
request_timeout_ms: None,
};
self.transport.connect(config).await
}
pub fn disconnect(&self) {
self.transport.disconnect();
}
pub fn status(&self) -> ConnectionStatus {
self.transport.status()
}
pub fn app_id(&self) -> &str {
&self.app_id
}
pub fn default_role(&self) -> &str {
&self.default_role
}
pub fn transport(&self) -> &T {
&self.transport
}
pub async fn call_zome<I: Serialize, O: DeserializeOwned>(
&self,
zome_name: &str,
fn_name: &str,
input: &I,
) -> Result<O, ClientError> {
self.call_zome_on_role(&self.default_role, zome_name, fn_name, input)
.await
}
pub async fn call_zome_on_role<I: Serialize, O: DeserializeOwned>(
&self,
role_name: &str,
zome_name: &str,
fn_name: &str,
input: &I,
) -> Result<O, ClientError> {
let payload = encode(input)?;
let response_bytes = self
.transport
.call_zome(role_name, zome_name, fn_name, payload)
.await?;
decode(&response_bytes)
}
pub async fn call_zome_raw(
&self,
role_name: &str,
zome_name: &str,
fn_name: &str,
payload: Vec<u8>,
) -> Result<Vec<u8>, ClientError> {
self.transport
.call_zome(role_name, zome_name, fn_name, payload)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::future::Future;
use std::pin::Pin;
struct MockTransport {
response: Vec<u8>,
}
impl MockTransport {
fn responding_with<T: Serialize>(value: &T) -> Self {
Self {
response: encode(value).unwrap(),
}
}
}
impl HolochainTransport for MockTransport {
fn call_zome(
&self,
_role_name: &str,
_zome_name: &str,
_fn_name: &str,
_payload: Vec<u8>,
) -> Pin<Box<dyn Future<Output = Result<Vec<u8>, ClientError>>>> {
let resp = self.response.clone();
Box::pin(async move { Ok(resp) })
}
fn status(&self) -> ConnectionStatus {
ConnectionStatus::Connected
}
fn connect(
&self,
_config: ConnectConfig,
) -> Pin<Box<dyn Future<Output = Result<(), ClientError>>>> {
Box::pin(async { Ok(()) })
}
fn disconnect(&self) {}
}
#[test]
fn client_creation() {
let transport = MockTransport::responding_with(&"ok");
let client = HolochainClient::new(transport, "test-app", "test-role");
assert_eq!(client.app_id(), "test-app");
assert_eq!(client.default_role(), "test-role");
assert_eq!(client.status(), ConnectionStatus::Connected);
}
}