Skip to content

Commit abc67a1

Browse files
committed
improved error handling:
provides both machine-readable error classification and human-readable detailed messages 1. Error Kind Constants (in types.rs) Added 11 error type constants that map to LabVIEW enums: - ERROR_KIND_NONE through ERROR_KIND_UNKNOWN 2. Enhanced Response Structure (in types.rs) Added three new fields to track error details: - error_kind: The type of error (timeout, connection, etc.) - error_url: The URL associated with the error - error_source: The root cause from the error chain 3. Error Analysis Function (in async_support.rs) Created analyze_reqwest_error() that: - Classifies errors using reqwest's is_*() methods - Extracts the URL if available - Gets the root cause via source() - Builds a detailed error message with the full error chain 4. New FFI Functions (in ffi/request.rs) - request_get_error_kind() - Returns the error type constant - request_get_error_message() - Returns the detailed error message - request_get_error_url() - Returns the URL if available - request_get_error_source() - Returns the root cause - request_response_has_error() - Checks if there's any error 5. Exports Updated (in lib.rs) All new functions are exported for use by LabVIEW Now when you get a timeout error, you'll be able to: 1. Call request_get_error_kind() and get ERROR_KIND_TIMEOUT (value 1) 2. Call request_get_error_message() to get a detailed message including the root cause 3. Call request_get_error_url() to see which URL failed 4. Call request_get_error_source() to get just the underlying error cause
1 parent 80b8494 commit abc67a1

File tree

4 files changed

+205
-7
lines changed

4 files changed

+205
-7
lines changed

src/async_support.rs

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,49 @@
1-
use crate::types::{RequestProgress, RequestStatus, Response};
1+
use crate::types::{RequestProgress, RequestStatus, Response, ERROR_KIND_NONE, ERROR_KIND_TIMEOUT, ERROR_KIND_CONNECTION, ERROR_KIND_REDIRECT, ERROR_KIND_INVALID_STATUS, ERROR_KIND_BODY, ERROR_KIND_DECODE, ERROR_KIND_BUILDER, ERROR_KIND_REQUEST, ERROR_KIND_FILE_SYSTEM, ERROR_KIND_UNKNOWN};
22
use reqwest::Response as ReqwestResponse;
33
use std::{
4+
error::Error as StdError,
45
fs::File,
56
future::Future,
67
io::Write,
78
sync::{Arc, RwLock},
89
};
910

11+
fn analyze_reqwest_error(e: &reqwest::Error) -> (u8, String, Option<String>, Option<String>) {
12+
let error_kind = if e.is_timeout() {
13+
ERROR_KIND_TIMEOUT
14+
} else if e.is_connect() {
15+
ERROR_KIND_CONNECTION
16+
} else if e.is_redirect() {
17+
ERROR_KIND_REDIRECT
18+
} else if e.is_status() {
19+
ERROR_KIND_INVALID_STATUS
20+
} else if e.is_body() {
21+
ERROR_KIND_BODY
22+
} else if e.is_decode() {
23+
ERROR_KIND_DECODE
24+
} else if e.is_builder() {
25+
ERROR_KIND_BUILDER
26+
} else if e.is_request() {
27+
ERROR_KIND_REQUEST
28+
} else {
29+
ERROR_KIND_UNKNOWN
30+
};
31+
32+
let main_message = e.to_string();
33+
34+
let url = e.url().map(|u| u.to_string());
35+
36+
let source = e.source().map(|s| s.to_string());
37+
38+
let detailed_message = if let Some(ref src) = source {
39+
format!("{}: {}", main_message, src)
40+
} else {
41+
main_message
42+
};
43+
44+
(error_kind, detailed_message, url, source)
45+
}
46+
1047
// Process a request and handle the response stream within an async context
1148
pub async fn process_request(
1249
request_future: impl Future<Output = reqwest::Result<ReqwestResponse>>,
@@ -56,6 +93,9 @@ pub async fn process_request(
5693
version,
5794
headers,
5895
body: Err(format!("File write error: {e}")),
96+
error_kind: ERROR_KIND_FILE_SYSTEM,
97+
error_url: None,
98+
error_source: Some(e.to_string()),
5999
});
60100
return;
61101
}
@@ -74,6 +114,9 @@ pub async fn process_request(
74114
version,
75115
headers,
76116
body: Err(format!("Network error: {e}")),
117+
error_kind: ERROR_KIND_CONNECTION,
118+
error_url: None,
119+
error_source: Some(e.to_string()),
77120
});
78121
return;
79122
}
@@ -88,6 +131,9 @@ pub async fn process_request(
88131
version,
89132
headers,
90133
body: Ok(Vec::new()), // Empty body since it was streamed to file
134+
error_kind: ERROR_KIND_NONE,
135+
error_url: None,
136+
error_source: None,
91137
});
92138
}
93139
Err(e) => {
@@ -99,6 +145,9 @@ pub async fn process_request(
99145
version,
100146
headers,
101147
body: Err(format!("File open error: {e}")),
148+
error_kind: ERROR_KIND_FILE_SYSTEM,
149+
error_url: None,
150+
error_source: Some(e.to_string()),
102151
});
103152
}
104153
}
@@ -119,6 +168,9 @@ pub async fn process_request(
119168
version,
120169
headers,
121170
body: Ok(bytes_vec),
171+
error_kind: ERROR_KIND_NONE,
172+
error_url: None,
173+
error_source: None,
122174
});
123175
}
124176
Err(e) => {
@@ -130,20 +182,28 @@ pub async fn process_request(
130182
version,
131183
headers,
132184
body: Err(format!("Body read error: {e}")),
185+
error_kind: ERROR_KIND_BODY,
186+
error_url: None,
187+
error_source: Some(e.to_string()),
133188
});
134189
}
135190
}
136191
}
137192
}
138193
Err(e) => {
139194
// Request error (connection failed, etc.)
195+
let (error_kind, error_message, error_url, error_source) = analyze_reqwest_error(&e);
196+
140197
let mut progress = progress_info.write().unwrap();
141198
progress.status = RequestStatus::Error;
142199
progress.final_response = Some(Response {
143-
status: reqwest::StatusCode::BAD_REQUEST, // Default status code for errors
200+
status: e.status().unwrap_or(reqwest::StatusCode::BAD_REQUEST),
144201
version: reqwest::Version::HTTP_11, // Default to HTTP/1.1 for errors
145202
headers: reqwest::header::HeaderMap::new(),
146-
body: Err(format!("Request error: {e}")),
203+
body: Err(error_message),
204+
error_kind,
205+
error_url,
206+
error_source,
147207
});
148208
}
149209
}

src/ffi/request.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,3 +296,123 @@ pub extern "C" fn request_read_response_version(
296296
unsafe { *num_bytes = 0 };
297297
ptr::null_mut() // Return null if request not found or no response yet
298298
}
299+
300+
/// Get the error kind from a response
301+
#[unsafe(no_mangle)]
302+
pub extern "C" fn request_get_error_kind(request_id: RequestId) -> u8 {
303+
let tracker = REQUEST_TRACKER.lock().unwrap();
304+
305+
if let Some(progress_info) = tracker.get(&request_id) {
306+
let progress = progress_info.read().unwrap();
307+
if let Some(ref response) = progress.final_response {
308+
return response.error_kind;
309+
}
310+
}
311+
312+
crate::types::ERROR_KIND_NONE
313+
}
314+
315+
/// Get the error message with detailed information
316+
#[unsafe(no_mangle)]
317+
pub extern "C" fn request_get_error_message(
318+
request_id: RequestId,
319+
buffer: *mut c_char,
320+
buffer_len: usize,
321+
) -> usize {
322+
if buffer.is_null() || buffer_len == 0 {
323+
return 0;
324+
}
325+
326+
let tracker = REQUEST_TRACKER.lock().unwrap();
327+
328+
if let Some(progress_info) = tracker.get(&request_id) {
329+
let progress = progress_info.read().unwrap();
330+
if let Some(ref response) = progress.final_response {
331+
if let Err(ref error_msg) = response.body {
332+
let msg_len = error_msg.len().min(buffer_len - 1);
333+
unsafe {
334+
std::ptr::copy_nonoverlapping(error_msg.as_ptr(), buffer as *mut u8, msg_len);
335+
*buffer.add(msg_len) = 0;
336+
}
337+
return msg_len;
338+
}
339+
}
340+
}
341+
342+
0
343+
}
344+
345+
/// Get the error URL if available
346+
#[unsafe(no_mangle)]
347+
pub extern "C" fn request_get_error_url(
348+
request_id: RequestId,
349+
buffer: *mut c_char,
350+
buffer_len: usize,
351+
) -> usize {
352+
if buffer.is_null() || buffer_len == 0 {
353+
return 0;
354+
}
355+
356+
let tracker = REQUEST_TRACKER.lock().unwrap();
357+
358+
if let Some(progress_info) = tracker.get(&request_id) {
359+
let progress = progress_info.read().unwrap();
360+
if let Some(ref response) = progress.final_response {
361+
if let Some(ref url) = response.error_url {
362+
let url_len = url.len().min(buffer_len - 1);
363+
unsafe {
364+
std::ptr::copy_nonoverlapping(url.as_ptr(), buffer as *mut u8, url_len);
365+
*buffer.add(url_len) = 0;
366+
}
367+
return url_len;
368+
}
369+
}
370+
}
371+
372+
0
373+
}
374+
375+
/// Get the root cause error message
376+
#[unsafe(no_mangle)]
377+
pub extern "C" fn request_get_error_source(
378+
request_id: RequestId,
379+
buffer: *mut c_char,
380+
buffer_len: usize,
381+
) -> usize {
382+
if buffer.is_null() || buffer_len == 0 {
383+
return 0;
384+
}
385+
386+
let tracker = REQUEST_TRACKER.lock().unwrap();
387+
388+
if let Some(progress_info) = tracker.get(&request_id) {
389+
let progress = progress_info.read().unwrap();
390+
if let Some(ref response) = progress.final_response {
391+
if let Some(ref source) = response.error_source {
392+
let src_len = source.len().min(buffer_len - 1);
393+
unsafe {
394+
std::ptr::copy_nonoverlapping(source.as_ptr(), buffer as *mut u8, src_len);
395+
*buffer.add(src_len) = 0;
396+
}
397+
return src_len;
398+
}
399+
}
400+
}
401+
402+
0
403+
}
404+
405+
/// Check if response has an error
406+
#[unsafe(no_mangle)]
407+
pub extern "C" fn request_response_has_error(request_id: RequestId) -> bool {
408+
let tracker = REQUEST_TRACKER.lock().unwrap();
409+
410+
if let Some(progress_info) = tracker.get(&request_id) {
411+
let progress = progress_info.read().unwrap();
412+
if let Some(ref response) = progress.final_response {
413+
return response.body.is_err() || response.error_kind != crate::types::ERROR_KIND_NONE;
414+
}
415+
}
416+
417+
false
418+
}

src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ pub use ffi::multipart::{
2828
multipart_form_create, multipart_form_destroy, multipart_form_read_error_message,
2929
};
3030
pub use ffi::request::{
31-
request_cancel, request_destroy, request_has_transport_error, request_is_complete,
32-
request_read_progress, request_read_received_bytes, request_read_response_body,
33-
request_read_response_headers, request_read_response_status, request_read_response_version,
34-
request_read_total_bytes, request_read_transport_error,
31+
request_cancel, request_destroy, request_get_error_kind, request_get_error_message,
32+
request_get_error_source, request_get_error_url, request_has_transport_error,
33+
request_is_complete, request_read_progress, request_read_received_bytes,
34+
request_read_response_body, request_read_response_headers, request_read_response_status,
35+
request_read_response_version, request_read_total_bytes, request_read_transport_error,
36+
request_response_has_error,
3537
};
3638
pub use ffi::request_builder::{
3739
request_builder_basic_auth, request_builder_bearer_auth, request_builder_body,

src/types.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ pub struct Response {
7171
pub version: Version,
7272
pub headers: HeaderMap,
7373
pub body: Result<Vec<u8>, String>,
74+
pub error_kind: u8,
75+
pub error_url: Option<String>,
76+
pub error_source: Option<String>,
7477
}
7578

7679
/// HTTP method constants
@@ -93,3 +96,16 @@ pub struct MultipartFormWrapper {
9396
pub form: Option<reqwest::multipart::Form>,
9497
pub error_message: Option<String>,
9598
}
99+
100+
/// Error kind constants for FFI
101+
pub const ERROR_KIND_NONE: u8 = 0;
102+
pub const ERROR_KIND_TIMEOUT: u8 = 1;
103+
pub const ERROR_KIND_CONNECTION: u8 = 2;
104+
pub const ERROR_KIND_REDIRECT: u8 = 3;
105+
pub const ERROR_KIND_INVALID_STATUS: u8 = 4;
106+
pub const ERROR_KIND_BODY: u8 = 5;
107+
pub const ERROR_KIND_DECODE: u8 = 6;
108+
pub const ERROR_KIND_BUILDER: u8 = 7;
109+
pub const ERROR_KIND_REQUEST: u8 = 8;
110+
pub const ERROR_KIND_FILE_SYSTEM: u8 = 9;
111+
pub const ERROR_KIND_UNKNOWN: u8 = 10;

0 commit comments

Comments
 (0)