xmtp_api_backend/queries/
builder.rs1use crate::{
2 AuthCallback, AuthHandle, AuthMiddleware, BackendClient, ReadonlyClient, TrackedStatsClient,
3 XmtpApiClient,
4};
5use std::sync::Arc;
6use xmtp_api_grpc::GrpcClient;
7use xmtp_proto::{
8 api::ToBoxedClient,
9 api_client::{ApiBuilder, NetConnectConfig},
10 types::AppVersion,
11};
12
13#[derive(Default, Clone)]
14pub struct MessageBackendBuilder {
15 host: Option<String>,
16 app_version: Option<AppVersion>,
17 readonly: bool,
18 auth_callback: Option<Arc<dyn AuthCallback>>,
19 auth_handle: Option<AuthHandle>,
20}
21
22#[derive(Debug, thiserror::Error)]
23pub enum MessageBackendBuilderError {
24 #[error("backend URL is required")]
25 MissingHost,
26 #[error(transparent)]
27 InvalidUrl(#[from] url::ParseError),
28 #[error(transparent)]
29 Grpc(#[from] xmtp_api_grpc::error::GrpcBuilderError),
30}
31
32impl MessageBackendBuilder {
33 pub fn new() -> Self {
34 Self::default()
35 }
36 pub fn host(&mut self, host: impl AsRef<str>) -> &mut Self {
37 self.host = Some(host.as_ref().into());
38 self
39 }
40 pub fn app_version(&mut self, version: impl Into<AppVersion>) -> &mut Self {
41 self.app_version = Some(version.into());
42 self
43 }
44 pub fn readonly(&mut self, value: bool) -> &mut Self {
45 self.readonly = value;
46 self
47 }
48 pub fn maybe_auth_callback(&mut self, value: Option<Arc<dyn AuthCallback>>) -> &mut Self {
49 self.auth_callback = value;
50 self
51 }
52 pub fn maybe_auth_handle(&mut self, value: Option<AuthHandle>) -> &mut Self {
53 self.auth_handle = value;
54 self
55 }
56 pub fn build(&mut self) -> Result<XmtpApiClient, MessageBackendBuilderError> {
57 let host = self
58 .host
59 .as_ref()
60 .ok_or(MessageBackendBuilderError::MissingHost)?;
61 let mut builder = GrpcClient::builder();
62 builder.set_host(url::Url::parse(host)?);
63 if let Some(version) = self.app_version.clone() {
64 builder.set_app_version(version)?;
65 }
66 let client = builder.build()?;
67 let client = if self.auth_callback.is_some() || self.auth_handle.is_some() {
68 AuthMiddleware::new(client, self.auth_callback.clone(), self.auth_handle.clone())
69 .arced()
70 } else {
71 client.arced()
72 };
73 let client = if self.readonly {
74 ReadonlyClient { inner: client }.arced()
75 } else {
76 client
77 };
78 Ok(Arc::new(TrackedStatsClient::new(BackendClient::new(
79 client,
80 ))))
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use xmtp_proto::{api::ApiClientError, api_client::XmtpBackendClient};
88
89 #[xmtp_common::test(unwrap_try = true)]
90 async fn builder_requires_a_host_and_attaches_readonly_policy() {
91 assert!(matches!(
92 MessageBackendBuilder::default().build(),
93 Err(MessageBackendBuilderError::MissingHost)
94 ));
95 let client = MessageBackendBuilder::default()
96 .host(xmtp_configuration::backend_test_url())
97 .readonly(true)
98 .build()?;
99 assert!(matches!(
100 client.publish(Default::default()).await,
101 Err(ApiClientError::WritesDisabled)
102 ));
103 }
104}