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