dioxus_inspector/types.rs
1//! Request and response types for the inspector bridge.
2//!
3//! This module contains all the types used for communication between the HTTP bridge
4//! and the Dioxus application, as well as the JSON request/response types for the API.
5
6use serde::{Deserialize, Serialize};
7use tokio::sync::oneshot;
8
9/// Command sent from HTTP server to Dioxus app for JavaScript evaluation.
10///
11/// When the bridge receives an eval request, it creates an `EvalCommand` and sends it
12/// through the channel. The Dioxus app should poll this channel and execute the script.
13///
14/// # Example
15///
16/// ```rust,ignore
17/// while let Some(cmd) = eval_rx.recv().await {
18/// let result = document::eval(&cmd.script).await;
19/// let response = match result {
20/// Ok(val) => EvalResponse::success(val.to_string()),
21/// Err(e) => EvalResponse::error(e.to_string()),
22/// };
23/// let _ = cmd.response_tx.send(response);
24/// }
25/// ```
26pub struct EvalCommand {
27 /// The JavaScript code to execute in the webview.
28 pub script: String,
29 /// Channel to send the evaluation result back to the HTTP handler.
30 pub response_tx: oneshot::Sender<EvalResponse>,
31}
32
33/// Request to evaluate JavaScript in the webview.
34///
35/// # JSON Format
36///
37/// ```json
38/// { "script": "return document.title" }
39/// ```
40#[derive(Debug, Deserialize)]
41pub struct EvalRequest {
42 /// The JavaScript code to execute. Should return a value.
43 pub script: String,
44}
45
46/// Response from JavaScript evaluation.
47///
48/// # JSON Format
49///
50/// Success:
51/// ```json
52/// { "success": true, "result": "Page Title" }
53/// ```
54///
55/// Error:
56/// ```json
57/// { "success": false, "error": "ReferenceError: x is not defined" }
58/// ```
59#[derive(Debug, Serialize, Clone)]
60pub struct EvalResponse {
61 /// Whether the evaluation succeeded.
62 pub success: bool,
63 /// The result of the evaluation, if successful.
64 #[serde(skip_serializing_if = "Option::is_none")]
65 pub result: Option<String>,
66 /// The error message, if evaluation failed.
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub error: Option<String>,
69}
70
71impl EvalResponse {
72 /// Create a successful response with the given result.
73 ///
74 /// # Example
75 ///
76 /// ```
77 /// use dioxus_inspector::EvalResponse;
78 ///
79 /// let resp = EvalResponse::success("42");
80 /// assert!(resp.success);
81 /// assert_eq!(resp.result, Some("42".to_string()));
82 /// ```
83 pub fn success(result: impl Into<String>) -> Self {
84 Self {
85 success: true,
86 result: Some(result.into()),
87 error: None,
88 }
89 }
90
91 /// Create an error response with the given message.
92 ///
93 /// # Example
94 ///
95 /// ```
96 /// use dioxus_inspector::EvalResponse;
97 ///
98 /// let resp = EvalResponse::error("Script timeout");
99 /// assert!(!resp.success);
100 /// assert_eq!(resp.error, Some("Script timeout".to_string()));
101 /// ```
102 pub fn error(message: impl Into<String>) -> Self {
103 Self {
104 success: false,
105 result: None,
106 error: Some(message.into()),
107 }
108 }
109}
110
111/// Query request for CSS selector.
112///
113/// # JSON Format
114///
115/// ```json
116/// { "selector": ".my-button", "property": "text" }
117/// ```
118///
119/// # Supported Properties
120///
121/// - `text` (default) - Element's `textContent`
122/// - `html` - Element's `innerHTML`
123/// - `outerHTML` - Element's `outerHTML`
124/// - `value` - Element's `value` (for inputs)
125/// - Any other string - Treated as an attribute name
126#[derive(Debug, Deserialize)]
127pub struct QueryRequest {
128 /// CSS selector to find the element.
129 pub selector: String,
130 /// Property to extract. Defaults to "text" if not specified.
131 #[serde(default)]
132 pub property: Option<String>,
133}
134
135/// Status response showing bridge health.
136///
137/// Returned by `GET /status` to check if the bridge is running.
138#[derive(Debug, Serialize)]
139pub struct StatusResponse {
140 /// Always "ok" when the bridge is healthy.
141 pub status: &'static str,
142 /// The application name provided to [`start_bridge`](crate::start_bridge).
143 pub app: String,
144 /// Process ID of the Dioxus application.
145 pub pid: u32,
146 /// Uptime in seconds since the bridge started.
147 pub uptime_secs: u64,
148 /// Human-readable uptime (e.g., "5m 30s", "2h 15m").
149 pub uptime_human: String,
150}
151
152/// Request for element inspection.
153///
154/// Returns detailed visibility and position information for an element.
155///
156/// # JSON Format
157///
158/// ```json
159/// { "selector": ".modal" }
160/// ```
161#[derive(Debug, Deserialize)]
162pub struct InspectRequest {
163 /// CSS selector to find the element to inspect.
164 pub selector: String,
165}
166
167/// Request to validate CSS classes.
168///
169/// Checks which CSS classes are available in the document's stylesheets.
170///
171/// # JSON Format
172///
173/// ```json
174/// { "classes": ["flex", "p-4", "bg-white"] }
175/// ```
176#[derive(Debug, Deserialize)]
177pub struct ValidateClassesRequest {
178 /// List of CSS class names to validate.
179 pub classes: Vec<String>,
180}
181
182/// Screenshot request.
183///
184/// Captures a screenshot of the application window (macOS only).
185///
186/// # JSON Format
187///
188/// ```json
189/// { "path": "/tmp/screenshot.png" }
190/// ```
191#[derive(Debug, Deserialize, Default)]
192pub struct ScreenshotRequest {
193 /// Output path for the screenshot. Defaults to `/tmp/dioxus-screenshot.png`.
194 #[serde(default)]
195 pub path: Option<String>,
196}
197
198/// Screenshot response.
199#[derive(Debug, Serialize)]
200pub struct ScreenshotResponse {
201 /// Whether the screenshot was captured successfully.
202 pub success: bool,
203 /// Path where the screenshot was saved.
204 #[serde(skip_serializing_if = "Option::is_none")]
205 pub path: Option<String>,
206 /// Error message if capture failed.
207 #[serde(skip_serializing_if = "Option::is_none")]
208 pub error: Option<String>,
209}
210
211/// Request to resize the window.
212///
213/// # JSON Format
214///
215/// ```json
216/// { "width": 1024, "height": 768 }
217/// ```
218///
219/// # Note
220///
221/// The application must handle the resize command. The bridge sends a special
222/// script pattern that the app can intercept to apply the resize.
223#[derive(Debug, Deserialize)]
224pub struct ResizeRequest {
225 /// Target width in pixels.
226 pub width: u32,
227 /// Target height in pixels.
228 pub height: u32,
229}
230
231/// Response from resize operation.
232#[derive(Debug, Serialize)]
233pub struct ResizeResponse {
234 /// Whether the resize command was sent successfully.
235 pub success: bool,
236 /// The requested width.
237 pub width: u32,
238 /// The requested height.
239 pub height: u32,
240 /// Error message if the resize failed.
241 #[serde(skip_serializing_if = "Option::is_none")]
242 pub error: Option<String>,
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn test_eval_response_success() {
251 let resp = EvalResponse::success("42");
252 assert!(resp.success);
253 assert_eq!(resp.result, Some("42".to_string()));
254 assert!(resp.error.is_none());
255 }
256
257 #[test]
258 fn test_eval_response_error() {
259 let resp = EvalResponse::error("failed");
260 assert!(!resp.success);
261 assert!(resp.result.is_none());
262 assert_eq!(resp.error, Some("failed".to_string()));
263 }
264
265 #[test]
266 fn test_eval_request_deserialize() {
267 let json = r#"{"script": "return 1 + 1"}"#;
268 let req: EvalRequest = serde_json::from_str(json).unwrap();
269 assert_eq!(req.script, "return 1 + 1");
270 }
271
272 #[test]
273 fn test_query_request_deserialize() {
274 let json = r#"{"selector": ".button", "property": "text"}"#;
275 let req: QueryRequest = serde_json::from_str(json).unwrap();
276 assert_eq!(req.selector, ".button");
277 assert_eq!(req.property, Some("text".to_string()));
278 }
279
280 #[test]
281 fn test_query_request_without_property() {
282 let json = r##"{"selector": "#main"}"##;
283 let req: QueryRequest = serde_json::from_str(json).unwrap();
284 assert_eq!(req.selector, "#main");
285 assert!(req.property.is_none());
286 }
287
288 #[test]
289 fn test_inspect_request_deserialize() {
290 let json = r#"{"selector": ".modal"}"#;
291 let req: InspectRequest = serde_json::from_str(json).unwrap();
292 assert_eq!(req.selector, ".modal");
293 }
294
295 #[test]
296 fn test_validate_classes_request_deserialize() {
297 let json = r#"{"classes": ["flex", "p-4", "bg-white"]}"#;
298 let req: ValidateClassesRequest = serde_json::from_str(json).unwrap();
299 assert_eq!(req.classes, vec!["flex", "p-4", "bg-white"]);
300 }
301
302 #[test]
303 fn test_screenshot_request_default() {
304 let req = ScreenshotRequest::default();
305 assert!(req.path.is_none());
306 }
307
308 #[test]
309 fn test_status_response_serialize() {
310 let resp = StatusResponse {
311 status: "ok",
312 app: "test".to_string(),
313 pid: 1234,
314 uptime_secs: 60,
315 uptime_human: "1m 0s".to_string(),
316 };
317 let json = serde_json::to_string(&resp).unwrap();
318 assert!(json.contains("\"status\":\"ok\""));
319 assert!(json.contains("\"app\":\"test\""));
320 }
321
322 #[test]
323 fn test_resize_request_deserialize() {
324 let json = r#"{"width": 800, "height": 600}"#;
325 let req: ResizeRequest = serde_json::from_str(json).unwrap();
326 assert_eq!(req.width, 800);
327 assert_eq!(req.height, 600);
328 }
329
330 #[test]
331 fn test_resize_response_serialize() {
332 let resp = ResizeResponse {
333 success: true,
334 width: 1024,
335 height: 768,
336 error: None,
337 };
338 let json = serde_json::to_string(&resp).unwrap();
339 assert!(json.contains("\"width\":1024"));
340 assert!(json.contains("\"height\":768"));
341 assert!(!json.contains("error"));
342 }
343}