1#[allow(clippy::all)]
2#[allow(warnings)]
3mod generated {
4 include!(concat!(env!("OUT_DIR"), "/mod.rs"));
7 pub const FILE_DESCRIPTOR_SET: &'static [u8] =
8 include_bytes!(concat!(env!("OUT_DIR"), "/proto_descriptor.bin"));
9}
10
11pub mod api_client;
12pub mod codec;
13mod convert;
14mod error;
15mod impls;
16mod traits;
17pub mod types;
18
19pub use error::*;
20pub use generated::*;
21pub use impls::update_dedupe::GroupUpdateDeduper;
22pub use traits::short_hex::ShortHex;
23
24pub mod api {
25 pub use super::traits::combinators::*;
26 pub use super::traits::stream::*;
27 pub use super::traits::*;
28}
29
30#[cfg(test)]
31pub mod test {
32 xmtp_common::if_native! {
33 #[cfg(test)]
34 #[ctor::ctor(unsafe)]
35 fn _setup() {
36 xmtp_common::logger()
37 }
38 }
39}
40
41pub mod prelude {
42 pub use super::FILE_DESCRIPTOR_SET;
43 xmtp_common::if_test! {
44 pub use super::api_client::XmtpTestClient;
45 }
46 pub use super::api_client::{
47 ApiBuilder, ArcedXmtpApi, BoxedXmtpApi, NetConnectConfig, XmtpBackendClient, XmtpMlsStreams,
48 };
49 pub use super::traits::{ApiClientError, BytesStream, Client, Endpoint, Query, QueryStream};
50}
51
52pub mod identity_v1 {
53 pub use super::xmtp::identity::api::v1::*;
54}
55
56pub mod backend_v1 {
57 pub use super::xmtp::backend::v1::*;
58}
59
60#[cfg(test)]
61mod descriptor_tests {
62 use prost::Message;
63 use prost_types::field_descriptor_proto::{Label, Type};
64
65 type ExpectedField = (&'static str, i32, Type, Label, Option<&'static str>);
66
67 fn assert_fields(message: &prost_types::DescriptorProto, expected: &[ExpectedField]) {
68 let message_name = message.name.as_deref().expect("message name");
69 let mut numbers = std::collections::HashSet::new();
70 for field in &message.field {
71 assert!(
72 numbers.insert(field.number),
73 "{message_name} reuses a field number"
74 );
75 }
76 for &(name, number, kind, label, type_name) in expected {
77 let field = message
78 .field
79 .iter()
80 .find(|field| field.name.as_deref() == Some(name))
81 .unwrap_or_else(|| panic!("{message_name}.{name} descriptor"));
82 assert_eq!(field.number, Some(number), "{message_name}.{name}");
83 assert_eq!(field.r#type(), kind, "{message_name}.{name}");
84 assert_eq!(field.label(), label, "{message_name}.{name}");
85 assert_eq!(
86 field.type_name.as_deref(),
87 type_name,
88 "{message_name}.{name}"
89 );
90 }
91 }
92
93 #[xmtp_common::test(unwrap_try = true)]
95 fn public_configuration_fields_match_the_wire_contract() {
96 use Label::{Optional, Repeated};
97 use Type::{Bool, Message, String, Uint32, Uint64};
98
99 let descriptors = prost_types::FileDescriptorSet::decode(crate::FILE_DESCRIPTOR_SET)?;
100 let file = descriptors
101 .file
102 .iter()
103 .find(|file| {
104 file.package.as_deref() == Some("xmtp.backend.v1")
105 && file
106 .message_type
107 .iter()
108 .any(|message| message.name.as_deref() == Some("GetConfigurationResponse"))
109 })
110 .expect("backend configuration file descriptor");
111 let message = |name: &str| {
112 file.message_type
113 .iter()
114 .find(|message| message.name.as_deref() == Some(name))
115 .unwrap_or_else(|| panic!("{name} descriptor"))
116 };
117
118 assert!(message("GetConfigurationRequest").field.is_empty());
119 let auth = message("AuthConfiguration");
120 let signing_key = auth
121 .nested_type
122 .iter()
123 .find(|message| message.name.as_deref() == Some("SigningKey"))
124 .expect("AuthConfiguration.SigningKey descriptor");
125 assert_fields(
126 signing_key,
127 &[
128 ("kid", 1, String, Optional, None),
129 ("alg", 2, String, Optional, None),
130 ],
131 );
132 assert_fields(
133 auth,
134 &[
135 ("enabled", 1, Bool, Optional, None),
136 (
137 "keys",
138 2,
139 Message,
140 Repeated,
141 Some(".xmtp.backend.v1.AuthConfiguration.SigningKey"),
142 ),
143 ("audiences", 3, String, Repeated, None),
144 ("issuers", 4, String, Repeated, None),
145 ("required_scopes", 5, String, Repeated, None),
146 ],
147 );
148 assert_fields(
149 message("RetentionConfiguration"),
150 &[
151 ("group_message_seconds", 1, Uint64, Optional, None),
152 ("welcome_seconds", 2, Uint64, Optional, None),
153 ("key_package_seconds", 3, Uint64, Optional, None),
154 ],
155 );
156 assert_fields(
157 message("LimitsConfiguration"),
158 &[
159 ("max_envelope_bytes", 1, Uint64, Optional, None),
160 ("max_request_bytes", 2, Uint64, Optional, None),
161 ("max_response_bytes", 3, Uint64, Optional, None),
162 ("max_publish_topics", 4, Uint32, Optional, None),
163 ("max_query_topics", 5, Uint32, Optional, None),
164 ("max_query_limit", 6, Uint32, Optional, None),
165 ("default_query_limit", 7, Uint32, Optional, None),
166 ("max_newest_metadata_topics", 8, Uint32, Optional, None),
167 ("max_newest_full_topics", 9, Uint32, Optional, None),
168 ("max_update_adds", 10, Uint32, Optional, None),
169 ("max_update_removes", 11, Uint32, Optional, None),
170 ("max_stream_topics", 12, Uint32, Optional, None),
171 ("max_static_topics", 13, Uint32, Optional, None),
172 ("max_lookup_identifiers", 14, Uint32, Optional, None),
173 ("max_scw_signatures", 15, Uint32, Optional, None),
174 ("max_identity_entries", 16, Uint32, Optional, None),
175 ("max_update_frames_per_second", 17, Uint32, Optional, None),
176 ("max_update_burst", 18, Uint32, Optional, None),
177 ("max_ping_frames_per_second", 19, Uint32, Optional, None),
178 ("max_ping_burst", 20, Uint32, Optional, None),
179 ],
180 );
181 let mls = message("MlsConfiguration");
182 assert_fields(
183 mls,
184 &[
185 ("max_group_members", 1, Uint32, Optional, None),
186 ("max_installations_per_inbox", 2, Uint32, Optional, None),
187 ("commit_log_enabled", 3, Bool, Optional, None),
188 ],
189 );
190 let commit_log = mls
191 .field
192 .iter()
193 .find(|field| field.name.as_deref() == Some("commit_log_enabled"))
194 .expect("commit_log_enabled descriptor");
195 assert_eq!(commit_log.proto3_optional, Some(true));
196 assert_fields(
197 message("HandlesConfiguration"),
198 &[("min_name_length", 1, Uint32, Optional, None)],
199 );
200 assert_fields(
201 message("GetConfigurationResponse"),
202 &[
203 ("identifier", 1, String, Optional, None),
204 ("server_version", 2, String, Optional, None),
205 ("min_libxmtp_version", 3, String, Optional, None),
206 (
207 "auth",
208 4,
209 Message,
210 Optional,
211 Some(".xmtp.backend.v1.AuthConfiguration"),
212 ),
213 (
214 "retention",
215 5,
216 Message,
217 Optional,
218 Some(".xmtp.backend.v1.RetentionConfiguration"),
219 ),
220 (
221 "limits",
222 6,
223 Message,
224 Optional,
225 Some(".xmtp.backend.v1.LimitsConfiguration"),
226 ),
227 (
228 "mls",
229 7,
230 Message,
231 Optional,
232 Some(".xmtp.backend.v1.MlsConfiguration"),
233 ),
234 ("smart_contract_wallet_chains", 8, String, Repeated, None),
235 (
236 "attachments",
237 9,
238 Message,
239 Optional,
240 Some(".xmtp.backend.v1.AttachmentsConfiguration"),
241 ),
242 (
243 "handles",
244 11,
245 Message,
246 Optional,
247 Some(".xmtp.backend.v1.HandlesConfiguration"),
248 ),
249 ],
250 );
251 }
252
253 #[xmtp_common::test(unwrap_try = true)]
257 fn publish_error_reasons_match_the_wire_contract() {
258 let descriptors = prost_types::FileDescriptorSet::decode(crate::FILE_DESCRIPTOR_SET)?;
259 let reason = descriptors
260 .file
261 .iter()
262 .filter(|file| file.package.as_deref() == Some("xmtp.backend.v1"))
263 .flat_map(|file| &file.message_type)
264 .find(|message| message.name.as_deref() == Some("PublishError"))
265 .and_then(|message| {
266 message
267 .enum_type
268 .iter()
269 .find(|kind| kind.name.as_deref() == Some("Reason"))
270 })
271 .expect("PublishError.Reason descriptor");
272 let values: Vec<_> = reason
273 .value
274 .iter()
275 .map(|value| (value.name.as_deref().unwrap_or_default(), value.number()))
276 .collect();
277 assert_eq!(
278 values,
279 [
280 ("REASON_UNSPECIFIED", 0),
281 ("REASON_MALFORMED_PAYLOAD", 1),
282 ("REASON_INVALID_KEY_PACKAGE", 2),
283 ("REASON_INVALID_IDENTITY_UPDATE", 3),
284 ("REASON_INVALID_SIGNATURE", 4),
285 ("REASON_TOO_LARGE", 5),
286 ("REASON_HANDLE_TAKEN", 6),
287 ("REASON_HANDLE_NOT_ALLOWED", 7),
288 ("REASON_HANDLE_TOO_SOON", 8),
289 ]
290 );
291 }
292
293 #[xmtp_common::test(unwrap_try = true)]
295 fn create_upload_method_matches_wire_contract() {
296 let descriptors = prost_types::FileDescriptorSet::decode(crate::FILE_DESCRIPTOR_SET)?;
297 let file = descriptors
298 .file
299 .iter()
300 .find(|file| {
301 file.package.as_deref() == Some("xmtp.backend.v1")
302 && file
303 .service
304 .iter()
305 .any(|service| service.name.as_deref() == Some("AttachmentService"))
306 })
307 .expect("backend AttachmentService file descriptor");
308 let service = file
309 .service
310 .iter()
311 .find(|service| service.name.as_deref() == Some("AttachmentService"))
312 .expect("AttachmentService descriptor");
313 let method = service
314 .method
315 .iter()
316 .find(|method| method.name.as_deref() == Some("CreateUpload"))
317 .expect("CreateUpload descriptor");
318
319 assert_eq!(
320 format!(
321 "{}.{}.{}",
322 file.package.as_deref().expect("package name"),
323 service.name.as_deref().expect("service name"),
324 method.name.as_deref().expect("method name")
325 ),
326 "xmtp.backend.v1.AttachmentService.CreateUpload"
327 );
328 assert_eq!(
329 method.input_type.as_deref(),
330 Some(".xmtp.backend.v1.CreateUploadRequest")
331 );
332 assert_eq!(
333 method.output_type.as_deref(),
334 Some(".xmtp.backend.v1.CreateUploadResponse")
335 );
336 assert!(!method.client_streaming.unwrap_or(false));
337 assert!(!method.server_streaming.unwrap_or(false));
338 }
339}