whatsapp_rust/features/
mex.rs1use crate::client::Client;
6use crate::request::IqError;
7use serde_json::Value;
8use thiserror::Error;
9use wacore::iq::mex::MexQuerySpec;
10
11pub use wacore::iq::mex::{MexErrorExtensions, MexGraphQLError, MexResponse};
13
14#[derive(Debug, Error)]
16pub enum MexError {
17 #[error("MEX payload parsing error: {0}")]
18 PayloadParsing(String),
19
20 #[error("MEX extension error: code={code}, message='{message}'")]
21 ExtensionError { code: i32, message: String },
22
23 #[error("IQ request failed: {0}")]
24 Request(#[from] IqError),
25
26 #[error("JSON error: {0}")]
27 Json(#[from] serde_json::Error),
28}
29
30#[derive(Debug, Clone)]
32pub struct MexRequest<'a> {
33 pub doc_id: &'a str,
35 pub variables: Value,
37}
38
39pub struct Mex<'a> {
41 client: &'a Client,
42}
43
44impl<'a> Mex<'a> {
45 pub(crate) fn new(client: &'a Client) -> Self {
46 Self { client }
47 }
48
49 #[inline]
51 pub async fn query(&self, request: MexRequest<'_>) -> Result<MexResponse, MexError> {
52 self.execute_request(request).await
53 }
54
55 #[inline]
57 pub async fn mutate(&self, request: MexRequest<'_>) -> Result<MexResponse, MexError> {
58 self.execute_request(request).await
59 }
60
61 async fn execute_request(&self, request: MexRequest<'_>) -> Result<MexResponse, MexError> {
62 let spec = MexQuerySpec::new(request.doc_id, request.variables);
63
64 let response = self.client.execute(spec).await?;
65
66 if let Some(fatal) = response.fatal_error() {
68 let code = fatal.error_code().unwrap_or(500);
69 return Err(MexError::ExtensionError {
70 code,
71 message: fatal.message.clone(),
72 });
73 }
74
75 Ok(response)
76 }
77}
78
79impl Client {
80 #[inline]
81 pub fn mex(&self) -> Mex<'_> {
82 Mex::new(self)
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use serde_json::json;
90
91 #[test]
92 fn test_mex_request_borrows_doc_id() {
93 let doc_id = "29829202653362039";
94 let request = MexRequest {
95 doc_id,
96 variables: json!({}),
97 };
98
99 assert_eq!(request.doc_id, "29829202653362039");
100 }
101
102 #[test]
103 fn test_mex_response_deserialization() {
104 let json_str = r#"{
105 "data": {
106 "xwa2_fetch_wa_users": [
107 {"jid": "1234567890@s.whatsapp.net", "country_code": "1"}
108 ]
109 }
110 }"#;
111
112 let response: MexResponse = serde_json::from_str(json_str).unwrap();
113 assert!(response.has_data());
114 assert!(!response.has_errors());
115 assert!(response.fatal_error().is_none());
116 }
117
118 #[test]
119 fn test_mex_response_with_non_fatal_errors() {
120 let json_str = r#"{
121 "data": null,
122 "errors": [
123 {
124 "message": "User not found",
125 "extensions": {
126 "error_code": 404,
127 "is_summary": false,
128 "is_retryable": false,
129 "severity": "WARNING"
130 }
131 }
132 ]
133 }"#;
134
135 let response: MexResponse = serde_json::from_str(json_str).unwrap();
136 assert!(!response.has_data());
137 assert!(response.has_errors());
138 assert!(response.fatal_error().is_none());
139
140 let errors = response.errors.as_ref().unwrap();
141 assert_eq!(errors.len(), 1);
142 assert_eq!(errors[0].message, "User not found");
143 assert_eq!(errors[0].error_code(), Some(404));
144 assert!(!errors[0].is_fatal());
145 }
146
147 #[test]
148 fn test_mex_response_with_fatal_error() {
149 let json_str = r#"{
150 "data": null,
151 "errors": [
152 {
153 "message": "Fatal server error",
154 "extensions": {
155 "error_code": 500,
156 "is_summary": true,
157 "severity": "CRITICAL"
158 }
159 }
160 ]
161 }"#;
162
163 let response: MexResponse = serde_json::from_str(json_str).unwrap();
164 assert!(!response.has_data());
165 assert!(response.has_errors());
166
167 let fatal = response.fatal_error();
168 assert!(fatal.is_some());
169
170 let fatal = fatal.unwrap();
171 assert_eq!(fatal.message, "Fatal server error");
172 assert_eq!(fatal.error_code(), Some(500));
173 assert!(fatal.is_fatal());
174 }
175
176 #[test]
177 fn test_mex_response_real_world() {
178 let json_str = r#"{
179 "data": {
180 "xwa2_fetch_wa_users": [
181 {
182 "__typename": "XWA2User",
183 "about_status_info": {
184 "__typename": "XWA2AboutStatus",
185 "text": "Hello",
186 "timestamp": "1766267670"
187 },
188 "country_code": "BR",
189 "id": null,
190 "jid": "559984726662@s.whatsapp.net",
191 "username_info": {
192 "__typename": "XWA2ResponseStatus",
193 "status": "EMPTY"
194 }
195 }
196 ]
197 }
198 }"#;
199
200 let response: MexResponse = serde_json::from_str(json_str).unwrap();
201 assert!(response.has_data());
202 assert!(!response.has_errors());
203
204 let data = response.data.unwrap();
205 let users = data["xwa2_fetch_wa_users"].as_array().unwrap();
206 assert_eq!(users.len(), 1);
207 assert_eq!(users[0]["country_code"], "BR");
208 assert_eq!(users[0]["jid"], "559984726662@s.whatsapp.net");
209 }
210
211 #[test]
212 fn test_mex_error_extensions_all_fields() {
213 let json_str = r#"{
214 "error_code": 400,
215 "is_summary": false,
216 "is_retryable": true,
217 "severity": "WARNING"
218 }"#;
219
220 let ext: MexErrorExtensions = serde_json::from_str(json_str).unwrap();
221 assert_eq!(ext.error_code, Some(400));
222 assert_eq!(ext.is_summary, Some(false));
223 assert_eq!(ext.is_retryable, Some(true));
224 assert_eq!(ext.severity, Some("WARNING".to_string()));
225 }
226
227 #[test]
228 fn test_mex_error_extensions_minimal() {
229 let json_str = r#"{}"#;
230
231 let ext: MexErrorExtensions = serde_json::from_str(json_str).unwrap();
232 assert!(ext.error_code.is_none());
233 assert!(ext.is_summary.is_none());
234 assert!(ext.is_retryable.is_none());
235 assert!(ext.severity.is_none());
236 }
237}