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}