1use std::ffi::{CStr, CString};
40use std::os::raw::c_char;
41use std::ptr;
42use std::sync::Mutex;
43
44use crate::{Client, Error};
45
46static ERROR_STORAGE: Mutex<Option<String>> = Mutex::new(None);
48
49pub struct SupabaseClient {
51 client: Client,
52 runtime: tokio::runtime::Runtime,
53}
54
55#[repr(C)]
57#[derive(Debug, Clone, Copy)]
58pub enum SupabaseError {
59 Success = 0,
60 InvalidInput = 1,
61 NetworkError = 2,
62 AuthError = 3,
63 DatabaseError = 4,
64 StorageError = 5,
65 FunctionsError = 6,
66 RealtimeError = 7,
67 RuntimeError = 8,
68 UnknownError = 99,
69}
70
71impl From<Error> for SupabaseError {
72 fn from(err: Error) -> Self {
73 if let Ok(mut storage) = ERROR_STORAGE.lock() {
75 *storage = Some(format!("{}", err));
76 }
77
78 match err {
79 Error::InvalidInput { .. } => SupabaseError::InvalidInput,
80 Error::Network { .. } => SupabaseError::NetworkError,
81 Error::Auth { .. } => SupabaseError::AuthError,
82 Error::Database { .. } => SupabaseError::DatabaseError,
83 Error::Storage { .. } => SupabaseError::StorageError,
84 Error::Functions { .. } => SupabaseError::FunctionsError,
85 Error::Realtime { .. } => SupabaseError::RealtimeError,
86 Error::Platform { .. } | Error::Crypto { .. } => SupabaseError::RuntimeError,
87 _ => SupabaseError::UnknownError,
88 }
89 }
90}
91
92#[no_mangle]
99pub unsafe extern "C" fn supabase_client_new(
100 url: *const c_char,
101 key: *const c_char,
102) -> *mut SupabaseClient {
103 if url.is_null() || key.is_null() {
104 return ptr::null_mut();
105 }
106
107 let url_str = match CStr::from_ptr(url).to_str() {
108 Ok(s) => s,
109 Err(_) => return ptr::null_mut(),
110 };
111
112 let key_str = match CStr::from_ptr(key).to_str() {
113 Ok(s) => s,
114 Err(_) => return ptr::null_mut(),
115 };
116
117 let runtime = match tokio::runtime::Runtime::new() {
119 Ok(rt) => rt,
120 Err(_) => return ptr::null_mut(),
121 };
122
123 match Client::new(url_str, key_str) {
124 Ok(client) => Box::into_raw(Box::new(SupabaseClient { client, runtime })),
125 Err(_) => ptr::null_mut(),
126 }
127}
128
129#[no_mangle]
135pub unsafe extern "C" fn supabase_client_free(client: *mut SupabaseClient) {
136 if !client.is_null() {
137 let _ = Box::from_raw(client);
138 }
139}
140
141#[no_mangle]
148pub unsafe extern "C" fn supabase_auth_sign_in(
149 client: *mut SupabaseClient,
150 email: *const c_char,
151 password: *const c_char,
152 result: *mut c_char,
153 result_len: usize,
154) -> SupabaseError {
155 if client.is_null() || email.is_null() || password.is_null() || result.is_null() {
156 return SupabaseError::InvalidInput;
157 }
158
159 let client_ref = &(*client);
160
161 let email_str = match CStr::from_ptr(email).to_str() {
162 Ok(s) => s,
163 Err(_) => return SupabaseError::InvalidInput,
164 };
165
166 let password_str = match CStr::from_ptr(password).to_str() {
167 Ok(s) => s,
168 Err(_) => return SupabaseError::InvalidInput,
169 };
170
171 let auth_result = client_ref.runtime.block_on(async {
173 client_ref
174 .client
175 .auth()
176 .sign_in_with_email_and_password(email_str, password_str)
177 .await
178 });
179
180 match auth_result {
181 Ok(session) => {
182 let response = match serde_json::to_string(&session) {
183 Ok(json) => json,
184 Err(_) => return SupabaseError::UnknownError,
185 };
186
187 write_string_to_buffer(&response, result, result_len)
188 }
189 Err(err) => err.into(),
190 }
191}
192
193#[no_mangle]
199pub unsafe extern "C" fn supabase_auth_sign_up(
200 client: *mut SupabaseClient,
201 email: *const c_char,
202 password: *const c_char,
203 result: *mut c_char,
204 result_len: usize,
205) -> SupabaseError {
206 if client.is_null() || email.is_null() || password.is_null() || result.is_null() {
207 return SupabaseError::InvalidInput;
208 }
209
210 let client_ref = &(*client);
211
212 let email_str = match CStr::from_ptr(email).to_str() {
213 Ok(s) => s,
214 Err(_) => return SupabaseError::InvalidInput,
215 };
216
217 let password_str = match CStr::from_ptr(password).to_str() {
218 Ok(s) => s,
219 Err(_) => return SupabaseError::InvalidInput,
220 };
221
222 let auth_result = client_ref.runtime.block_on(async {
223 client_ref
224 .client
225 .auth()
226 .sign_up_with_email_and_password(email_str, password_str)
227 .await
228 });
229
230 match auth_result {
231 Ok(session) => {
232 let response = match serde_json::to_string(&session) {
233 Ok(json) => json,
234 Err(_) => return SupabaseError::UnknownError,
235 };
236
237 write_string_to_buffer(&response, result, result_len)
238 }
239 Err(err) => err.into(),
240 }
241}
242
243#[no_mangle]
249pub unsafe extern "C" fn supabase_database_select(
250 client: *mut SupabaseClient,
251 table: *const c_char,
252 columns: *const c_char,
253 result: *mut c_char,
254 result_len: usize,
255) -> SupabaseError {
256 if client.is_null() || table.is_null() || result.is_null() {
257 return SupabaseError::InvalidInput;
258 }
259
260 let client_ref = &(*client);
261
262 let table_str = match CStr::from_ptr(table).to_str() {
263 Ok(s) => s,
264 Err(_) => return SupabaseError::InvalidInput,
265 };
266
267 let columns_str = if columns.is_null() {
268 "*"
269 } else {
270 match CStr::from_ptr(columns).to_str() {
271 Ok(s) => s,
272 Err(_) => return SupabaseError::InvalidInput,
273 }
274 };
275
276 let db_result = client_ref.runtime.block_on(async {
277 let result: Result<Vec<serde_json::Value>, Error> = client_ref
278 .client
279 .database()
280 .from(table_str)
281 .select(columns_str)
282 .execute()
283 .await;
284 result.map(|data| serde_json::to_string(&data).unwrap_or_default())
285 });
286
287 match db_result {
288 Ok(data) => write_string_to_buffer(&data, result, result_len),
289 Err(err) => err.into(),
290 }
291}
292
293#[no_mangle]
300pub unsafe extern "C" fn supabase_database_insert(
301 client: *mut SupabaseClient,
302 table: *const c_char,
303 json_data: *const c_char,
304 result: *mut c_char,
305 result_len: usize,
306) -> SupabaseError {
307 if client.is_null() || table.is_null() || json_data.is_null() || result.is_null() {
308 return SupabaseError::InvalidInput;
309 }
310
311 let client_ref = &(*client);
312
313 let table_str = match CStr::from_ptr(table).to_str() {
314 Ok(s) => s,
315 Err(_) => return SupabaseError::InvalidInput,
316 };
317
318 let json_str = match CStr::from_ptr(json_data).to_str() {
319 Ok(s) => s,
320 Err(_) => return SupabaseError::InvalidInput,
321 };
322
323 let json_value: serde_json::Value = match serde_json::from_str(json_str) {
324 Ok(v) => v,
325 Err(_) => return SupabaseError::InvalidInput,
326 };
327
328 let db_result = client_ref.runtime.block_on(async {
329 let result = client_ref
330 .client
331 .database()
332 .insert(table_str)
333 .values(json_value)?
334 .execute::<serde_json::Value>()
335 .await;
336
337 match result {
338 Ok(data) => Ok(serde_json::to_string(&data).unwrap_or_default()),
339 Err(err) => Err(err),
340 }
341 });
342
343 match db_result {
344 Ok(data) => write_string_to_buffer(&data, result, result_len),
345 Err(err) => err.into(),
346 }
347}
348
349#[no_mangle]
355pub unsafe extern "C" fn supabase_storage_list_buckets(
356 client: *mut SupabaseClient,
357 result: *mut c_char,
358 result_len: usize,
359) -> SupabaseError {
360 if client.is_null() || result.is_null() {
361 return SupabaseError::InvalidInput;
362 }
363
364 let client_ref = &(*client);
365
366 let storage_result = client_ref
367 .runtime
368 .block_on(async { client_ref.client.storage().list_buckets().await });
369
370 match storage_result {
371 Ok(buckets) => {
372 let response = match serde_json::to_string(&buckets) {
373 Ok(json) => json,
374 Err(_) => return SupabaseError::UnknownError,
375 };
376 write_string_to_buffer(&response, result, result_len)
377 }
378 Err(err) => err.into(),
379 }
380}
381
382#[no_mangle]
388pub unsafe extern "C" fn supabase_functions_invoke(
389 client: *mut SupabaseClient,
390 function_name: *const c_char,
391 json_payload: *const c_char,
392 result: *mut c_char,
393 result_len: usize,
394) -> SupabaseError {
395 if client.is_null() || function_name.is_null() || result.is_null() {
396 return SupabaseError::InvalidInput;
397 }
398
399 let client_ref = &(*client);
400
401 let function_str = match CStr::from_ptr(function_name).to_str() {
402 Ok(s) => s,
403 Err(_) => return SupabaseError::InvalidInput,
404 };
405
406 let payload = if json_payload.is_null() {
407 None
408 } else {
409 match CStr::from_ptr(json_payload).to_str() {
410 Ok(s) => match serde_json::from_str::<serde_json::Value>(s) {
411 Ok(v) => Some(v),
412 Err(_) => return SupabaseError::InvalidInput,
413 },
414 Err(_) => return SupabaseError::InvalidInput,
415 }
416 };
417
418 let function_result = client_ref.runtime.block_on(async {
419 client_ref
420 .client
421 .functions()
422 .invoke(function_str, payload)
423 .await
424 });
425
426 match function_result {
427 Ok(response) => {
428 let response_str = match response {
429 serde_json::Value::String(s) => s,
430 other => serde_json::to_string(&other).unwrap_or_default(),
431 };
432 write_string_to_buffer(&response_str, result, result_len)
433 }
434 Err(err) => err.into(),
435 }
436}
437
438#[no_mangle]
444pub unsafe extern "C" fn supabase_get_last_error(
445 buffer: *mut c_char,
446 buffer_len: usize,
447) -> SupabaseError {
448 if buffer.is_null() || buffer_len == 0 {
449 return SupabaseError::InvalidInput;
450 }
451
452 let error_msg = if let Ok(storage) = ERROR_STORAGE.lock() {
453 storage
454 .as_ref()
455 .cloned()
456 .unwrap_or_else(|| "No error".to_string())
457 } else {
458 "Failed to access error storage".to_string()
459 };
460
461 write_string_to_buffer(&error_msg, buffer, buffer_len)
462}
463
464unsafe fn write_string_to_buffer(
466 data: &str,
467 buffer: *mut c_char,
468 buffer_len: usize,
469) -> SupabaseError {
470 let data_cstring = match CString::new(data) {
471 Ok(s) => s,
472 Err(_) => return SupabaseError::UnknownError,
473 };
474
475 let data_bytes = data_cstring.as_bytes_with_nul();
476 if data_bytes.len() > buffer_len {
477 return SupabaseError::InvalidInput;
478 }
479
480 ptr::copy_nonoverlapping(data_bytes.as_ptr(), buffer as *mut u8, data_bytes.len());
481
482 SupabaseError::Success
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488 use std::ffi::CString;
489
490 #[test]
491 fn test_client_creation() {
492 let url = CString::new("http://localhost:54321").unwrap();
493 let key = CString::new("test-key").unwrap();
494
495 unsafe {
496 let client = supabase_client_new(url.as_ptr(), key.as_ptr());
497 assert!(!client.is_null());
498
499 supabase_client_free(client);
500 }
501 }
502
503 #[test]
504 fn test_error_handling() {
505 unsafe {
506 let client = supabase_client_new(ptr::null(), ptr::null());
507 assert!(client.is_null());
508 }
509 }
510
511 #[test]
512 fn test_error_storage() {
513 let mut buffer = [0u8; 256];
514 unsafe {
515 let result = supabase_get_last_error(buffer.as_mut_ptr() as *mut c_char, buffer.len());
516 assert_eq!(result as i32, SupabaseError::Success as i32);
517 }
518 }
519}