1use std::cell::RefCell;
7use std::ffi::{c_char, CString};
8use std::ptr;
9
10use crate::api::ApiError;
11
12pub const LAMINAR_OK: i32 = 0;
16pub const LAMINAR_ERR_NULL_POINTER: i32 = -1;
18pub const LAMINAR_ERR_INVALID_UTF8: i32 = -2;
20pub const LAMINAR_ERR_CONNECTION: i32 = 100;
22pub const LAMINAR_ERR_TABLE_NOT_FOUND: i32 = 200;
24pub const LAMINAR_ERR_TABLE_EXISTS: i32 = 201;
26pub const LAMINAR_ERR_SCHEMA_MISMATCH: i32 = 202;
28pub const LAMINAR_ERR_INGESTION: i32 = 300;
30pub const LAMINAR_ERR_QUERY: i32 = 400;
32pub const LAMINAR_ERR_SUBSCRIPTION: i32 = 500;
34pub const LAMINAR_ERR_INTERNAL: i32 = 900;
36pub const LAMINAR_ERR_SHUTDOWN: i32 = 901;
38
39thread_local! {
41 static LAST_ERROR: RefCell<Option<StoredError>> = const { RefCell::new(None) };
42}
43
44struct StoredError {
46 error: ApiError,
47 c_message: CString,
48}
49
50pub(crate) fn set_last_error(err: ApiError) {
52 let c_message = CString::new(err.message())
53 .unwrap_or_else(|_| CString::new("Error message contained null byte").unwrap());
54 LAST_ERROR.with(|e| {
55 *e.borrow_mut() = Some(StoredError {
56 error: err,
57 c_message,
58 });
59 });
60}
61
62pub(crate) fn clear_last_error() {
64 LAST_ERROR.with(|e| *e.borrow_mut() = None);
65}
66
67#[must_use]
69pub(crate) fn last_error_code() -> i32 {
70 LAST_ERROR.with(|e| {
71 e.borrow()
72 .as_ref()
73 .map_or(LAMINAR_OK, |stored| api_error_to_ffi_code(&stored.error))
74 })
75}
76
77fn api_error_to_ffi_code(err: &ApiError) -> i32 {
79 use crate::api::codes;
80 let code = err.code();
81 match code {
82 codes::CONNECTION_FAILED => LAMINAR_ERR_CONNECTION,
83 codes::TABLE_NOT_FOUND => LAMINAR_ERR_TABLE_NOT_FOUND,
84 codes::TABLE_EXISTS => LAMINAR_ERR_TABLE_EXISTS,
85 codes::SCHEMA_MISMATCH => LAMINAR_ERR_SCHEMA_MISMATCH,
86 codes::INGESTION_FAILED => LAMINAR_ERR_INGESTION,
87 codes::QUERY_FAILED => LAMINAR_ERR_QUERY,
88 codes::SUBSCRIPTION_FAILED | codes::SUBSCRIPTION_CLOSED | codes::SUBSCRIPTION_TIMEOUT => {
89 LAMINAR_ERR_SUBSCRIPTION
90 }
91 codes::SHUTDOWN => LAMINAR_ERR_SHUTDOWN,
92 _ => LAMINAR_ERR_INTERNAL,
94 }
95}
96
97#[no_mangle]
106pub extern "C" fn laminar_last_error() -> *const c_char {
107 LAST_ERROR.with(|e| match &*e.borrow() {
108 Some(stored) => stored.c_message.as_ptr(),
109 None => ptr::null(),
110 })
111}
112
113#[no_mangle]
117pub extern "C" fn laminar_last_error_code() -> i32 {
118 last_error_code()
119}
120
121#[no_mangle]
125pub extern "C" fn laminar_clear_error() {
126 clear_last_error();
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_no_error_returns_null() {
135 clear_last_error();
136 assert!(laminar_last_error().is_null());
137 assert_eq!(laminar_last_error_code(), LAMINAR_OK);
138 }
139
140 #[test]
141 fn test_set_and_get_error() {
142 let err = ApiError::table_not_found("test_table");
143 set_last_error(err);
144
145 let code = laminar_last_error_code();
146 assert_eq!(code, LAMINAR_ERR_TABLE_NOT_FOUND);
147
148 let error_ptr = laminar_last_error();
149 assert!(!error_ptr.is_null());
150
151 let error_cstr = unsafe { std::ffi::CStr::from_ptr(error_ptr) };
153 let message = error_cstr.to_str().unwrap();
154 assert!(message.contains("test_table"));
155 }
156
157 #[test]
158 fn test_clear_error() {
159 set_last_error(ApiError::internal("test"));
160 assert!(!laminar_last_error().is_null());
161
162 laminar_clear_error();
163 assert!(laminar_last_error().is_null());
164 assert_eq!(laminar_last_error_code(), LAMINAR_OK);
165 }
166
167 #[test]
168 fn test_error_code_mapping() {
169 set_last_error(ApiError::connection("conn fail"));
171 assert_eq!(laminar_last_error_code(), LAMINAR_ERR_CONNECTION);
172
173 set_last_error(ApiError::Query {
175 code: crate::api::codes::QUERY_FAILED,
176 message: "query fail".into(),
177 });
178 assert_eq!(laminar_last_error_code(), LAMINAR_ERR_QUERY);
179
180 set_last_error(ApiError::shutdown());
182 assert_eq!(laminar_last_error_code(), LAMINAR_ERR_SHUTDOWN);
183 }
184}