Skip to main content

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}