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 auth_handle = (self.auth_callback.is_some() || self.auth_handle.is_some())
68 .then(|| self.auth_handle.clone().unwrap_or_default());
69 let client = if let Some(handle) = auth_handle.clone() {
70 AuthMiddleware::new(client, self.auth_callback.clone(), Some(handle)).arced()
71 } else {
72 client.arced()
73 };
74 let client = if self.readonly {
75 ReadonlyClient { inner: client }.arced()
76 } else {
77 client
78 };
79 Ok(Arc::new(TrackedStatsClient::new(
80 BackendClient::new(client).with_auth_handle(auth_handle),
81 )))
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88 use xmtp_proto::{api::ApiClientError, api_client::XmtpBackendClient};
89
90 #[xmtp_common::test(unwrap_try = true)]
91 async fn builder_requires_a_host_and_attaches_readonly_policy() {
92 assert!(matches!(
93 MessageBackendBuilder::default().build(),
94 Err(MessageBackendBuilderError::MissingHost)
95 ));
96 let client = MessageBackendBuilder::default()
97 .host(xmtp_configuration::backend_test_url())
98 .readonly(true)
99 .build()?;
100 assert!(matches!(
101 client.publish(Default::default()).await,
102 Err(ApiClientError::WritesDisabled)
103 ));
104 }
105}