1use std::cell::RefCell;
4use std::ffi::{c_char, CString};
5use std::ptr;
6
7use crate::api::ApiError;
8
9pub const LAMINAR_OK: i32 = 0;
11pub const LAMINAR_ERR_NULL_POINTER: i32 = -1;
13pub const LAMINAR_ERR_INVALID_UTF8: i32 = -2;
15pub const LAMINAR_ERR_CONNECTION: i32 = 100;
17pub const LAMINAR_ERR_TABLE_NOT_FOUND: i32 = 200;
19pub const LAMINAR_ERR_TABLE_EXISTS: i32 = 201;
21pub const LAMINAR_ERR_SCHEMA_MISMATCH: i32 = 202;
23pub const LAMINAR_ERR_INGESTION: i32 = 300;
25pub const LAMINAR_ERR_QUERY: i32 = 400;
27pub const LAMINAR_ERR_SUBSCRIPTION: i32 = 500;
29pub const LAMINAR_ERR_INTERNAL: i32 = 900;
31pub const LAMINAR_ERR_SHUTDOWN: i32 = 901;
33
34thread_local! {
35 static LAST_ERROR: RefCell<Option<StoredError>> = const { RefCell::new(None) };
36}
37
38struct StoredError {
39 error: ApiError,
40 c_message: CString,
41}
42
43pub(crate) fn set_last_error(err: ApiError) {
44 let c_message = CString::new(err.message())
45 .unwrap_or_else(|_| CString::new("Error message contained null byte").unwrap());
46 LAST_ERROR.with(|e| {
47 *e.borrow_mut() = Some(StoredError {
48 error: err,
49 c_message,
50 });
51 });
52}
53
54pub(crate) fn clear_last_error() {
55 LAST_ERROR.with(|e| *e.borrow_mut() = None);
56}
57
58#[must_use]
59pub(crate) fn last_error_code() -> i32 {
60 LAST_ERROR.with(|e| {
61 e.borrow()
62 .as_ref()
63 .map_or(LAMINAR_OK, |stored| api_error_to_ffi_code(&stored.error))
64 })
65}
66
67fn api_error_to_ffi_code(err: &ApiError) -> i32 {
68 use crate::api::codes;
69 let code = err.code();
70 match code {
71 codes::CONNECTION_FAILED => LAMINAR_ERR_CONNECTION,
72 codes::TABLE_NOT_FOUND => LAMINAR_ERR_TABLE_NOT_FOUND,
73 codes::TABLE_EXISTS => LAMINAR_ERR_TABLE_EXISTS,
74 codes::SCHEMA_MISMATCH => LAMINAR_ERR_SCHEMA_MISMATCH,
75 codes::INGESTION_FAILED => LAMINAR_ERR_INGESTION,
76 codes::QUERY_FAILED => LAMINAR_ERR_QUERY,
77 codes::SUBSCRIPTION_FAILED | codes::SUBSCRIPTION_CLOSED | codes::SUBSCRIPTION_TIMEOUT => {
78 LAMINAR_ERR_SUBSCRIPTION
79 }
80 codes::SHUTDOWN => LAMINAR_ERR_SHUTDOWN,
81 _ => LAMINAR_ERR_INTERNAL,
82 }
83}
84
85#[no_mangle]
91pub extern "C" fn laminar_last_error() -> *const c_char {
92 LAST_ERROR.with(|e| match &*e.borrow() {
93 Some(stored) => stored.c_message.as_ptr(),
94 None => ptr::null(),
95 })
96}
97
98#[no_mangle]
100pub extern "C" fn laminar_last_error_code() -> i32 {
101 last_error_code()
102}
103
104#[no_mangle]
106pub extern "C" fn laminar_clear_error() {
107 clear_last_error();
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_no_error_returns_null() {
116 clear_last_error();
117 assert!(laminar_last_error().is_null());
118 assert_eq!(laminar_last_error_code(), LAMINAR_OK);
119 }
120
121 #[test]
122 fn test_set_and_get_error() {
123 let err = ApiError::table_not_found("test_table");
124 set_last_error(err);
125
126 let code = laminar_last_error_code();
127 assert_eq!(code, LAMINAR_ERR_TABLE_NOT_FOUND);
128
129 let error_ptr = laminar_last_error();
130 assert!(!error_ptr.is_null());
131
132 let error_cstr = unsafe { std::ffi::CStr::from_ptr(error_ptr) };
134 let message = error_cstr.to_str().unwrap();
135 assert!(message.contains("test_table"));
136 }
137
138 #[test]
139 fn test_clear_error() {
140 set_last_error(ApiError::internal("test"));
141 assert!(!laminar_last_error().is_null());
142
143 laminar_clear_error();
144 assert!(laminar_last_error().is_null());
145 assert_eq!(laminar_last_error_code(), LAMINAR_OK);
146 }
147
148 #[test]
149 fn test_error_code_mapping() {
150 set_last_error(ApiError::connection("conn fail"));
152 assert_eq!(laminar_last_error_code(), LAMINAR_ERR_CONNECTION);
153
154 set_last_error(ApiError::Query {
156 code: crate::api::codes::QUERY_FAILED,
157 message: "query fail".into(),
158 });
159 assert_eq!(laminar_last_error_code(), LAMINAR_ERR_QUERY);
160
161 set_last_error(ApiError::shutdown());
163 assert_eq!(laminar_last_error_code(), LAMINAR_ERR_SHUTDOWN);
164 }
165}