Skip to main content

deckyfx_dioxus_ipc_bridge/
request.rs

1//! IPC Request Types
2//!
3//! Defines the core request types for HTTP-like IPC communication between JavaScript and Rust.
4//! Supports JSON, URL-encoded, and multipart/form-data request bodies.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9
10/// HTTP-like IPC request from JavaScript to Rust
11///
12/// # Example
13/// ```rust
14/// use dioxus_ipc_bridge::request::*;
15/// use std::collections::HashMap;
16/// use serde_json::json;
17///
18/// let request = IpcRequest {
19///     id: 12345,
20///     method: "POST".to_string(),
21///     url: "ipc://form/submit?redirect=true".to_string(),
22///     headers: HashMap::from([("Content-Type".to_string(), "application/json".to_string())]),
23///     body: Some(RequestBody::Json(json!({"name": "John", "email": "john@example.com"}))),
24/// };
25/// ```
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct IpcRequest {
28    /// Unique request ID for promise resolution
29    pub id: u64,
30
31    /// HTTP method (GET, POST, PUT, DELETE, etc.)
32    pub method: String,
33
34    /// Full IPC URL including scheme, path, and query string
35    /// Example: "ipc://calculator/fibonacci?number=10"
36    pub url: String,
37
38    /// Request headers (Content-Type, Authorization, etc.)
39    pub headers: HashMap<String, String>,
40
41    /// Request body (optional for GET-like operations)
42    pub body: Option<RequestBody>,
43}
44
45/// Request body types
46///
47/// Supports three common encoding formats for maximum compatibility.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(tag = "type", content = "data")]
50pub enum RequestBody {
51    /// JSON body (application/json)
52    ///
53    /// Most common format for modern APIs. Supports nested objects and arrays.
54    /// # Example
55    /// ```json
56    /// { "name": "John", "age": 25, "address": { "city": "NYC" } }
57    /// ```
58    Json(Value),
59
60    /// URL-encoded body (application/x-www-form-urlencoded)
61    ///
62    /// Traditional HTML form submission format.
63    /// # Example
64    /// ```
65    /// name=John&age=25&city=NYC
66    /// ```
67    UrlEncoded(HashMap<String, String>),
68
69    /// Multipart form data (multipart/form-data)
70    ///
71    /// Supports file uploads with content-disposition headers.
72    /// # Example
73    /// ```
74    /// Content-Disposition: form-data; name="file"; filename="photo.jpg"
75    /// Content-Type: image/jpeg
76    /// [binary data]
77    /// ```
78    Multipart {
79        /// Text fields (key-value pairs)
80        fields: HashMap<String, String>,
81
82        /// File uploads
83        files: Vec<FileUpload>,
84    },
85}
86
87/// File upload data
88///
89/// Represents a single file in a multipart/form-data request.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct FileUpload {
92    /// Form field name
93    pub name: String,
94
95    /// Original filename
96    pub filename: String,
97
98    /// MIME type (e.g., "image/jpeg", "application/pdf")
99    pub content_type: String,
100
101    /// File data (base64-encoded for JavaScript transport)
102    pub data: String,
103}
104
105/// Enriched request with parsed URL components
106///
107/// This is what route handlers receive after the router parses the URL
108/// and extracts path parameters and query strings.
109///
110/// # Example
111/// ```rust
112/// use dioxus_ipc_bridge::prelude::*;
113/// use std::collections::HashMap;
114///
115/// fn handler(req: &EnrichedRequest) -> Result<IpcResponse, IpcError> {
116///     let user_id = req.path_params.get("id").unwrap();
117///     let page = req.query_params.get("page").unwrap_or(&"1".to_string());
118///     Ok(IpcResponse::ok(serde_json::json!({
119///         "user_id": user_id,
120///         "page": page
121///     })))
122/// }
123/// ```
124#[derive(Debug, Clone)]
125pub struct EnrichedRequest {
126    /// Original request
127    pub original: IpcRequest,
128
129    /// Parsed path parameters (e.g., /user/:id → {"id": "123"})
130    pub path_params: HashMap<String, String>,
131
132    /// Parsed query parameters (e.g., ?page=2&sort=name → {"page": "2", "sort": "name"})
133    pub query_params: HashMap<String, String>,
134
135    /// Extracted URL path without query string
136    /// Example: "ipc://calculator/fibonacci?number=10" → "/calculator/fibonacci"
137    pub path: String,
138
139    /// Request headers (convenience accessor)
140    pub headers: HashMap<String, String>,
141
142    /// Request body (convenience accessor)
143    pub body: Option<RequestBody>,
144}
145
146impl EnrichedRequest {
147    /// Create a new enriched request from a basic IPC request
148    pub fn new(
149        original: IpcRequest,
150        path: String,
151        path_params: HashMap<String, String>,
152        query_params: HashMap<String, String>,
153    ) -> Self {
154        let headers = original.headers.clone();
155        let body = original.body.clone();
156
157        Self {
158            original,
159            path_params,
160            query_params,
161            path,
162            headers,
163            body,
164        }
165    }
166
167    /// Get a path parameter by name
168    pub fn path_param(&self, name: &str) -> Option<&String> {
169        self.path_params.get(name)
170    }
171
172    /// Get a query parameter by name
173    pub fn query_param(&self, name: &str) -> Option<&String> {
174        self.query_params.get(name)
175    }
176
177    /// Get a header by name (case-insensitive)
178    pub fn header(&self, name: &str) -> Option<&String> {
179        self.headers
180            .iter()
181            .find(|(k, _)| k.eq_ignore_ascii_case(name))
182            .map(|(_, v)| v)
183    }
184
185    /// Get the request method
186    pub fn method(&self) -> &str {
187        &self.original.method
188    }
189
190    /// Get the request ID
191    pub fn id(&self) -> u64 {
192        self.original.id
193    }
194}