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_error_code_is_fatal() {
120 let json_str = r#"{
122 "data": null,
123 "errors": [
124 {
125 "message": "User not found",
126 "extensions": {
127 "error_code": 404,
128 "is_summary": false,
129 "is_retryable": false,
130 "severity": "WARNING"
131 }
132 }
133 ]
134 }"#;
135
136 let response: MexResponse = serde_json::from_str(json_str).unwrap();
137 assert!(!response.has_data());
138 assert!(response.has_errors());
139
140 let fatal = response.fatal_error();
141 assert!(fatal.is_some());
142 assert_eq!(fatal.unwrap().error_code(), Some(404));
143 }
144
145 #[test]
146 fn test_mex_response_with_fatal_error() {
147 let json_str = r#"{
148 "data": null,
149 "errors": [
150 {
151 "message": "Fatal server error",
152 "extensions": {
153 "error_code": 500,
154 "is_summary": true,
155 "severity": "CRITICAL"
156 }
157 }
158 ]
159 }"#;
160
161 let response: MexResponse = serde_json::from_str(json_str).unwrap();
162 assert!(!response.has_data());
163 assert!(response.has_errors());
164
165 let fatal = response.fatal_error();
166 assert!(fatal.is_some());
167
168 let fatal = fatal.unwrap();
169 assert_eq!(fatal.message, "Fatal server error");
170 assert_eq!(fatal.error_code(), Some(500));
171 assert!(fatal.is_summary());
172 }
173
174 #[test]
175 fn test_mex_response_real_world() {
176 let json_str = r#"{
177 "data": {
178 "xwa2_fetch_wa_users": [
179 {
180 "__typename": "XWA2User",
181 "about_status_info": {
182 "__typename": "XWA2AboutStatus",
183 "text": "Hello",
184 "timestamp": "1766267670"
185 },
186 "country_code": "BR",
187 "id": null,
188 "jid": "551199887766@s.whatsapp.net",
189 "username_info": {
190 "__typename": "XWA2ResponseStatus",
191 "status": "EMPTY"
192 }
193 }
194 ]
195 }
196 }"#;
197
198 let response: MexResponse = serde_json::from_str(json_str).unwrap();
199 assert!(response.has_data());
200 assert!(!response.has_errors());
201
202 let data = response.data.unwrap();
203 let users = data["xwa2_fetch_wa_users"].as_array().unwrap();
204 assert_eq!(users.len(), 1);
205 assert_eq!(users[0]["country_code"], "BR");
206 assert_eq!(users[0]["jid"], "551199887766@s.whatsapp.net");
207 }
208
209 #[test]
210 fn test_mex_error_extensions_all_fields() {
211 let json_str = r#"{
212 "error_code": 400,
213 "is_summary": false,
214 "is_retryable": true,
215 "severity": "WARNING"
216 }"#;
217
218 let ext: MexErrorExtensions = serde_json::from_str(json_str).unwrap();
219 assert_eq!(ext.error_code, Some(400));
220 assert_eq!(ext.is_summary, Some(false));
221 assert_eq!(ext.is_retryable, Some(true));
222 assert_eq!(ext.severity, Some("WARNING".to_string()));
223 }
224
225 #[test]
226 fn test_mex_error_extensions_minimal() {
227 let json_str = r#"{}"#;
228
229 let ext: MexErrorExtensions = serde_json::from_str(json_str).unwrap();
230 assert!(ext.error_code.is_none());
231 assert!(ext.is_summary.is_none());
232 assert!(ext.is_retryable.is_none());
233 assert!(ext.severity.is_none());
234 }
235}