kiteconnect_async_wasm/connect/utils.rs
1//! # Utility Functions
2//!
3//! This module contains utility functions and platform-specific implementations
4//! used throughout the KiteConnect library. It provides cross-platform abstractions
5//! for HTTP requests, CSV parsing, and other common operations.
6//!
7//! ## Platform Support
8//!
9//! The utilities in this module are designed to work across different platforms:
10//! - **Native**: Full functionality with optimized implementations
11//! - **WASM**: Browser-compatible implementations using Web APIs
12//!
13//! ## Key Features
14//!
15//! - **Cross-platform HTTP handling**: Abstract interface for HTTP requests
16//! - **CSV parsing**: Platform-specific CSV parsing (native: `csv`, WASM: `csv-core`)
17//! - **URL management**: Centralized API endpoint configuration
18//! - **Error handling**: Consistent error patterns across platforms
19//!
20//! ## Example
21//!
22//! ```rust,no_run
23//! // CSV parsing is handled internally by the KiteConnect client
24//! use kiteconnect_async_wasm::connect::KiteConnect;
25//!
26//! # #[tokio::main]
27//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
28//! let client = KiteConnect::new("api_key", "access_token");
29//! // CSV parsing happens automatically when fetching instruments
30//! let instruments = client.instruments(None).await?;
31//! println!("Parsed {} instruments", instruments.as_array().unwrap().len());
32//! # Ok(())
33//! # }
34//! ```
35
36use anyhow::Result;
37#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
38use serde_json::Value as JsonValue;
39use std::collections::HashMap;
40
41// WASM platform imports
42#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
43use web_sys::window;
44
45#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
46use js_sys::Uint8Array;
47
48#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
49use wasm_bindgen_futures::JsFuture;
50
51#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
52use csv_core::{ReadFieldResult, Reader};
53
54/// Base URL for KiteConnect API in production
55#[cfg(not(test))]
56pub const URL: &str = "https://api.kite.trade";
57
58/// Base URL for KiteConnect API in test environment
59///
60/// Used during testing to point to a local mock server for reliable,
61/// offline testing without making actual API calls.
62#[cfg(test)]
63pub const URL: &str = "http://127.0.0.1:1234";
64
65/// Async trait for handling HTTP requests across different platforms
66///
67/// This trait provides a platform-agnostic interface for making HTTP requests.
68/// Implementations handle the specifics of each platform (native vs WASM)
69/// while providing a consistent API for the rest of the library.
70///
71/// # Platform Implementations
72///
73/// - **Native**: Uses `reqwest` for full HTTP client functionality
74/// - **WASM**: Uses `fetch` API for browser-compatible requests
75///
76/// # Example
77///
78/// ```rust,no_run
79/// use kiteconnect_async_wasm::connect::utils::RequestHandler;
80/// use std::collections::HashMap;
81///
82/// # struct MyClient;
83/// # impl RequestHandler for MyClient {
84/// # async fn send_request(
85/// # &self,
86/// # url: reqwest::Url,
87/// # method: &str,
88/// # data: Option<HashMap<&str, &str>>,
89/// # ) -> anyhow::Result<reqwest::Response> {
90/// # unimplemented!()
91/// # }
92/// # }
93///
94/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
95/// let client = MyClient;
96/// let url = reqwest::Url::parse("https://api.example.com/data")?;
97/// let mut params = HashMap::new();
98/// params.insert("key", "value");
99///
100/// let response = client.send_request(url, "GET", Some(params)).await?;
101/// # Ok(())
102/// # }
103/// ```
104pub trait RequestHandler {
105 /// Send an HTTP request with the specified parameters
106 ///
107 /// # Arguments
108 ///
109 /// * `url` - The complete URL to send the request to
110 /// * `method` - HTTP method ("GET", "POST", "PUT", "DELETE")
111 /// * `data` - Optional form data to include in the request
112 ///
113 /// # Returns
114 ///
115 /// A `Result` containing the HTTP response or an error
116 ///
117 /// # Errors
118 ///
119 /// Returns an error if:
120 /// - The network request fails
121 /// - The URL is malformed
122 /// - Authentication is required but missing
123 /// - The server returns an error status
124 fn send_request(
125 &self,
126 url: reqwest::Url,
127 method: &str,
128 data: Option<HashMap<&str, &str>>,
129 ) -> impl std::future::Future<Output = Result<reqwest::Response>> + Send;
130}
131
132/// Parse CSV data using csv-core for WASM compatibility
133///
134/// This function provides CSV parsing capability in WASM environments where
135/// the standard `csv` crate is not available. It uses `csv-core` which is
136/// a no-std implementation suitable for WebAssembly.
137///
138/// # Arguments
139///
140/// * `csv_data` - Raw CSV data as a string
141///
142/// # Returns
143///
144/// A `Result` containing the parsed CSV data as a JSON array of objects,
145/// where each object represents a row with column headers as keys.
146///
147/// # Errors
148///
149/// Returns an error if:
150/// - The CSV data is malformed
151/// - Memory allocation fails during parsing
152/// - JSON serialization fails
153///
154/// # Example
155///
156/// ```rust,no_run
157/// # #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
158/// use kiteconnect_async_wasm::connect::utils::parse_csv_with_core;
159///
160/// # #[cfg(all(feature = "wasm", target_arch = "wasm32"))]
161/// # fn example() -> Result<(), Box<dyn std::error::Error>> {
162/// let csv_data = r#"symbol,exchange,price
163/// RELIANCE,NSE,2500.00
164/// TCS,NSE,3200.50"#;
165///
166/// let parsed = parse_csv_with_core(csv_data)?;
167///
168/// // parsed is a JSON array:
169/// // [
170/// // {"symbol": "RELIANCE", "exchange": "NSE", "price": "2500.00"},
171/// // {"symbol": "TCS", "exchange": "NSE", "price": "3200.50"}
172/// // ]
173/// # Ok(())
174/// # }
175/// ```
176///
177/// # Platform Availability
178///
179/// This function is only available on WASM targets. On native platforms,
180/// use the standard `csv` crate which provides better performance and features.
181#[cfg(all(feature = "wasm", target_arch = "wasm32"))]
182pub fn parse_csv_with_core(csv_data: &str) -> Result<JsonValue> {
183 let mut reader = Reader::new();
184 let mut output = vec![0; 1024];
185 let mut field = Vec::new();
186 let mut input = csv_data.as_bytes();
187
188 let mut headers: Vec<String> = Vec::new();
189 let mut records: Vec<Vec<String>> = Vec::new();
190 let mut current_record: Vec<String> = Vec::new();
191 let mut is_first_row = true;
192
193 loop {
194 let (result, input_consumed, output_written) = reader.read_field(input, &mut output);
195 input = &input[input_consumed..];
196
197 match result {
198 ReadFieldResult::InputEmpty => {
199 if !current_record.is_empty() {
200 if is_first_row {
201 headers = current_record.clone();
202 is_first_row = false;
203 } else {
204 records.push(current_record.clone());
205 }
206 }
207 break;
208 }
209 ReadFieldResult::OutputFull => {
210 field.extend_from_slice(&output[..output_written]);
211 // Continue reading with same input
212 }
213 ReadFieldResult::Field { record_end } => {
214 field.extend_from_slice(&output[..output_written]);
215 let field_str = String::from_utf8_lossy(&field).to_string();
216 current_record.push(field_str);
217 field.clear();
218
219 if record_end {
220 if is_first_row {
221 headers = current_record.clone();
222 is_first_row = false;
223 } else {
224 records.push(current_record.clone());
225 }
226 current_record.clear();
227 }
228 }
229 ReadFieldResult::Record => {
230 // This case should not happen based on the API, but we handle it for completeness
231 continue;
232 }
233 }
234 }
235
236 // Convert to JSON format
237 let mut result: Vec<JsonValue> = Vec::new();
238 for record in records {
239 let mut obj = serde_json::Map::new();
240 for (i, value) in record.iter().enumerate() {
241 if let Some(header) = headers.get(i) {
242 obj.insert(header.clone(), JsonValue::String(value.clone()));
243 }
244 }
245 result.push(JsonValue::Object(obj));
246 }
247
248 Ok(JsonValue::Array(result))
249}