1use std::fmt;
4
5pub use abort_signal::{AbortController, AbortSignal};
6pub use web_sys::RequestMode;
7
8#[cfg(target_arch = "wasm32")]
9mod web;
10#[cfg(target_arch = "wasm32")]
11pub use web::{RequestBuilder, Response, StreamReader};
12
13#[cfg(not(target_arch = "wasm32"))]
14mod native;
15#[cfg(not(target_arch = "wasm32"))]
16pub use native::{RequestBuilder, Response, StreamReader};
17
18pub type Result<T> = std::result::Result<T, Error>;
19
20#[derive(Debug)]
22pub enum Error {
23 Transport(String),
26 HttpError(u16, String),
28 JsonError(String),
30 Aborted,
32}
33
34impl From<serde_json::Error> for Error {
35 fn from(err: serde_json::Error) -> Self {
36 Error::JsonError(err.to_string())
37 }
38}
39
40impl fmt::Display for Error {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Error::Transport(e) => write!(f, "Transport error: {}", e),
44 Error::HttpError(status, msg) => write!(f, "HTTP error {}: {}", status, msg),
45 Error::JsonError(e) => write!(f, "JSON error: {}", e),
46 Error::Aborted => write!(f, "Request was aborted"),
47 }
48 }
49}
50
51impl std::error::Error for Error {}
52
53#[derive(Debug, Clone, Copy)]
55pub enum Method {
56 GET,
57 POST,
58 PUT,
59 DELETE,
60 PATCH,
61 HEAD,
62 OPTIONS,
63}
64
65impl Method {
66 fn as_str(&self) -> &'static str {
67 match self {
68 Method::GET => "GET",
69 Method::POST => "POST",
70 Method::PUT => "PUT",
71 Method::DELETE => "DELETE",
72 Method::PATCH => "PATCH",
73 Method::HEAD => "HEAD",
74 Method::OPTIONS => "OPTIONS",
75 }
76 }
77}
78
79pub struct Client;
81
82impl Client {
83 pub fn get(&self, url: impl Into<String>) -> RequestBuilder {
85 RequestBuilder::new(Method::GET, url)
86 }
87
88 pub fn post(&self, url: impl Into<String>) -> RequestBuilder {
90 RequestBuilder::new(Method::POST, url)
91 }
92
93 pub fn put(&self, url: impl Into<String>) -> RequestBuilder {
95 RequestBuilder::new(Method::PUT, url)
96 }
97
98 pub fn delete(&self, url: impl Into<String>) -> RequestBuilder {
100 RequestBuilder::new(Method::DELETE, url)
101 }
102
103 pub fn patch(&self, url: impl Into<String>) -> RequestBuilder {
105 RequestBuilder::new(Method::PATCH, url)
106 }
107
108 pub fn head(&self, url: impl Into<String>) -> RequestBuilder {
110 RequestBuilder::new(Method::HEAD, url)
111 }
112}
113
114pub async fn get(url: impl Into<String>) -> Result<Response> {
116 Client.get(url).send().await
117}
118
119pub async fn post_json<T: serde::Serialize>(url: impl Into<String>, json: &T) -> Result<Response> {
121 Client.post(url).json(json)?.send().await
122}
123
124#[cfg(all(feature = "examples", target_arch = "wasm32"))]
125pub mod examples {
126 use super::*;
127 use wasm_bindgen::prelude::wasm_bindgen;
128 use web_sys::console;
129
130 #[wasm_bindgen]
132 pub async fn stream_large_file() {
133 let client = Client;
134 let url = "https://raw.githubusercontent.com/yaptown/yap/refs/heads/main/out/deu/frequency_lists/combined/frequencies.jsonl";
135
136 console::log_1(&"Starting streaming download...".into());
137
138 let response = match client.get(url).send().await {
139 Ok(r) => r,
140 Err(e) => {
141 console::error_1(&format!("Request failed: {}", e).into());
142 return;
143 }
144 };
145
146 let response = match response.error_for_status() {
147 Ok(r) => r,
148 Err(e) => {
149 console::error_1(&format!("HTTP error: {}", e).into());
150 return;
151 }
152 };
153
154 let reader = match response.stream_reader() {
156 Ok(r) => r,
157 Err(e) => {
158 console::error_1(&format!("Failed to get stream reader: {}", e).into());
159 return;
160 }
161 };
162
163 let mut total_bytes = 0;
164 let mut chunk_count = 0;
165
166 loop {
168 match reader.read_chunk().await {
169 Ok(Some(chunk)) => {
170 total_bytes += chunk.len();
171 chunk_count += 1;
172 console::log_1(
173 &format!("Received chunk {}: {} bytes", chunk_count, chunk.len()).into(),
174 );
175 }
176 Ok(None) => break,
177 Err(e) => {
178 console::error_1(&format!("Error reading chunk: {}", e).into());
179 return;
180 }
181 }
182 }
183
184 console::log_1(&format!("✓ Total: {} bytes in {} chunks", total_bytes, chunk_count).into());
185 }
186
187 #[wasm_bindgen]
189 pub async fn stream_text_content() {
190 let client = Client;
191 let url = "https://raw.githubusercontent.com/yaptown/yap/refs/heads/main/out/deu/frequency_lists/combined/frequencies.jsonl";
192
193 console::log_1(&"Starting line-by-line streaming...".into());
194
195 let response = match client
196 .get(url)
197 .send()
198 .await
199 .and_then(|r| r.error_for_status())
200 {
201 Ok(r) => r,
202 Err(e) => {
203 console::error_1(&format!("Request failed: {}", e).into());
204 return;
205 }
206 };
207
208 let reader = match response.stream_reader() {
209 Ok(r) => r,
210 Err(e) => {
211 console::error_1(&format!("Failed to get stream reader: {}", e).into());
212 return;
213 }
214 };
215
216 let mut buffer = Vec::new();
217 let mut line_count = 0;
218
219 loop {
220 let chunk = match reader.read_chunk().await {
221 Ok(Some(c)) => c,
222 Ok(None) => break,
223 Err(e) => {
224 console::error_1(&format!("Error reading chunk: {}", e).into());
225 return;
226 }
227 };
228
229 buffer.extend_from_slice(&chunk);
230
231 while let Some(newline_pos) = buffer.iter().position(|&b| b == b'\n') {
233 let line_bytes = buffer.drain(..=newline_pos).collect::<Vec<_>>();
234 let line = String::from_utf8_lossy(&line_bytes);
235 line_count += 1;
236
237 if line_count <= 5 {
239 console::log_1(&format!("Line {}: {}", line_count, line.trim()).into());
240 }
241 }
242 }
243
244 if !buffer.is_empty() {
246 let line = String::from_utf8_lossy(&buffer);
247 line_count += 1;
248 console::log_1(&format!("Last line: {}", line.trim()).into());
249 }
250
251 console::log_1(&format!("✓ Processed {} lines total", line_count).into());
252 }
253
254 #[wasm_bindgen]
256 pub async fn download_with_progress() {
257 let client = Client;
258 let url = "https://raw.githubusercontent.com/yaptown/yap/refs/heads/main/out/deu/frequency_lists/combined/frequencies.jsonl";
259
260 console::log_1(&"Starting download with progress tracking...".into());
261
262 let response = match client
263 .get(url)
264 .send()
265 .await
266 .and_then(|r| r.error_for_status())
267 {
268 Ok(r) => r,
269 Err(e) => {
270 console::error_1(&format!("Request failed: {}", e).into());
271 return;
272 }
273 };
274
275 let content_length = response
277 .header("content-length")
278 .ok()
279 .flatten()
280 .and_then(|s| s.parse::<usize>().ok());
281
282 if let Some(total) = content_length {
283 console::log_1(&format!("Content-Length: {} bytes", total).into());
284 } else {
285 console::log_1(&"Content-Length not available".into());
286 }
287
288 let reader = match response.stream_reader() {
289 Ok(r) => r,
290 Err(e) => {
291 console::error_1(&format!("Failed to get stream reader: {}", e).into());
292 return;
293 }
294 };
295
296 let mut downloaded = Vec::new();
297 let mut last_logged_percent = 0;
298
299 loop {
300 let chunk = match reader.read_chunk().await {
301 Ok(Some(c)) => c,
302 Ok(None) => break,
303 Err(e) => {
304 console::error_1(&format!("Error reading chunk: {}", e).into());
305 return;
306 }
307 };
308
309 downloaded.extend_from_slice(&chunk);
310
311 if let Some(total) = content_length {
312 let progress = (downloaded.len() as f64 / total as f64) * 100.0;
313 let progress_int = progress as u32;
314
315 if progress_int >= last_logged_percent + 10 {
317 console::log_1(
318 &format!(
319 "Progress: {:.1}% ({}/{})",
320 progress,
321 downloaded.len(),
322 total
323 )
324 .into(),
325 );
326 last_logged_percent = progress_int;
327 }
328 }
329 }
330
331 console::log_1(&format!("✓ Download complete: {} bytes", downloaded.len()).into());
332 }
333}