use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::ptr;
use std::sync::Mutex;
use crate::{Client, Error};
static ERROR_STORAGE: Mutex<Option<String>> = Mutex::new(None);
pub struct SupabaseClient {
client: Client,
runtime: tokio::runtime::Runtime,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub enum SupabaseError {
Success = 0,
InvalidInput = 1,
NetworkError = 2,
AuthError = 3,
DatabaseError = 4,
StorageError = 5,
FunctionsError = 6,
RealtimeError = 7,
RuntimeError = 8,
UnknownError = 99,
}
impl From<Error> for SupabaseError {
fn from(err: Error) -> Self {
if let Ok(mut storage) = ERROR_STORAGE.lock() {
*storage = Some(format!("{}", err));
}
match err {
Error::InvalidInput { .. } => SupabaseError::InvalidInput,
Error::Network { .. } => SupabaseError::NetworkError,
Error::Auth { .. } => SupabaseError::AuthError,
Error::Database { .. } => SupabaseError::DatabaseError,
Error::Storage { .. } => SupabaseError::StorageError,
Error::Functions { .. } => SupabaseError::FunctionsError,
Error::Realtime { .. } => SupabaseError::RealtimeError,
Error::Platform { .. } | Error::Crypto { .. } => SupabaseError::RuntimeError,
_ => SupabaseError::UnknownError,
}
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_client_new(
url: *const c_char,
key: *const c_char,
) -> *mut SupabaseClient {
if url.is_null() || key.is_null() {
return ptr::null_mut();
}
let url_str = match CStr::from_ptr(url).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
let key_str = match CStr::from_ptr(key).to_str() {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
let runtime = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(_) => return ptr::null_mut(),
};
match Client::new(url_str, key_str) {
Ok(client) => Box::into_raw(Box::new(SupabaseClient { client, runtime })),
Err(_) => ptr::null_mut(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_client_free(client: *mut SupabaseClient) {
if !client.is_null() {
let _ = Box::from_raw(client);
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_auth_sign_in(
client: *mut SupabaseClient,
email: *const c_char,
password: *const c_char,
result: *mut c_char,
result_len: usize,
) -> SupabaseError {
if client.is_null() || email.is_null() || password.is_null() || result.is_null() {
return SupabaseError::InvalidInput;
}
let client_ref = &(*client);
let email_str = match CStr::from_ptr(email).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let password_str = match CStr::from_ptr(password).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let auth_result = client_ref.runtime.block_on(async {
client_ref
.client
.auth()
.sign_in_with_email_and_password(email_str, password_str)
.await
});
match auth_result {
Ok(session) => {
let response = match serde_json::to_string(&session) {
Ok(json) => json,
Err(_) => return SupabaseError::UnknownError,
};
write_string_to_buffer(&response, result, result_len)
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_auth_sign_up(
client: *mut SupabaseClient,
email: *const c_char,
password: *const c_char,
result: *mut c_char,
result_len: usize,
) -> SupabaseError {
if client.is_null() || email.is_null() || password.is_null() || result.is_null() {
return SupabaseError::InvalidInput;
}
let client_ref = &(*client);
let email_str = match CStr::from_ptr(email).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let password_str = match CStr::from_ptr(password).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let auth_result = client_ref.runtime.block_on(async {
client_ref
.client
.auth()
.sign_up_with_email_and_password(email_str, password_str)
.await
});
match auth_result {
Ok(session) => {
let response = match serde_json::to_string(&session) {
Ok(json) => json,
Err(_) => return SupabaseError::UnknownError,
};
write_string_to_buffer(&response, result, result_len)
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_database_select(
client: *mut SupabaseClient,
table: *const c_char,
columns: *const c_char,
result: *mut c_char,
result_len: usize,
) -> SupabaseError {
if client.is_null() || table.is_null() || result.is_null() {
return SupabaseError::InvalidInput;
}
let client_ref = &(*client);
let table_str = match CStr::from_ptr(table).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let columns_str = if columns.is_null() {
"*"
} else {
match CStr::from_ptr(columns).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
}
};
let db_result = client_ref.runtime.block_on(async {
let result: Result<Vec<serde_json::Value>, Error> = client_ref
.client
.database()
.from(table_str)
.select(columns_str)
.execute()
.await;
result.map(|data| serde_json::to_string(&data).unwrap_or_default())
});
match db_result {
Ok(data) => write_string_to_buffer(&data, result, result_len),
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_database_insert(
client: *mut SupabaseClient,
table: *const c_char,
json_data: *const c_char,
result: *mut c_char,
result_len: usize,
) -> SupabaseError {
if client.is_null() || table.is_null() || json_data.is_null() || result.is_null() {
return SupabaseError::InvalidInput;
}
let client_ref = &(*client);
let table_str = match CStr::from_ptr(table).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let json_str = match CStr::from_ptr(json_data).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let json_value: serde_json::Value = match serde_json::from_str(json_str) {
Ok(v) => v,
Err(_) => return SupabaseError::InvalidInput,
};
let db_result = client_ref.runtime.block_on(async {
let result = client_ref
.client
.database()
.insert(table_str)
.values(json_value)?
.execute::<serde_json::Value>()
.await;
match result {
Ok(data) => Ok(serde_json::to_string(&data).unwrap_or_default()),
Err(err) => Err(err),
}
});
match db_result {
Ok(data) => write_string_to_buffer(&data, result, result_len),
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_storage_list_buckets(
client: *mut SupabaseClient,
result: *mut c_char,
result_len: usize,
) -> SupabaseError {
if client.is_null() || result.is_null() {
return SupabaseError::InvalidInput;
}
let client_ref = &(*client);
let storage_result = client_ref
.runtime
.block_on(async { client_ref.client.storage().list_buckets().await });
match storage_result {
Ok(buckets) => {
let response = match serde_json::to_string(&buckets) {
Ok(json) => json,
Err(_) => return SupabaseError::UnknownError,
};
write_string_to_buffer(&response, result, result_len)
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_functions_invoke(
client: *mut SupabaseClient,
function_name: *const c_char,
json_payload: *const c_char,
result: *mut c_char,
result_len: usize,
) -> SupabaseError {
if client.is_null() || function_name.is_null() || result.is_null() {
return SupabaseError::InvalidInput;
}
let client_ref = &(*client);
let function_str = match CStr::from_ptr(function_name).to_str() {
Ok(s) => s,
Err(_) => return SupabaseError::InvalidInput,
};
let payload = if json_payload.is_null() {
None
} else {
match CStr::from_ptr(json_payload).to_str() {
Ok(s) => match serde_json::from_str::<serde_json::Value>(s) {
Ok(v) => Some(v),
Err(_) => return SupabaseError::InvalidInput,
},
Err(_) => return SupabaseError::InvalidInput,
}
};
let function_result = client_ref.runtime.block_on(async {
client_ref
.client
.functions()
.invoke(function_str, payload)
.await
});
match function_result {
Ok(response) => {
let response_str = match response {
serde_json::Value::String(s) => s,
other => serde_json::to_string(&other).unwrap_or_default(),
};
write_string_to_buffer(&response_str, result, result_len)
}
Err(err) => err.into(),
}
}
#[no_mangle]
pub unsafe extern "C" fn supabase_get_last_error(
buffer: *mut c_char,
buffer_len: usize,
) -> SupabaseError {
if buffer.is_null() || buffer_len == 0 {
return SupabaseError::InvalidInput;
}
let error_msg = if let Ok(storage) = ERROR_STORAGE.lock() {
storage
.as_ref()
.cloned()
.unwrap_or_else(|| "No error".to_string())
} else {
"Failed to access error storage".to_string()
};
write_string_to_buffer(&error_msg, buffer, buffer_len)
}
unsafe fn write_string_to_buffer(
data: &str,
buffer: *mut c_char,
buffer_len: usize,
) -> SupabaseError {
let data_cstring = match CString::new(data) {
Ok(s) => s,
Err(_) => return SupabaseError::UnknownError,
};
let data_bytes = data_cstring.as_bytes_with_nul();
if data_bytes.len() > buffer_len {
return SupabaseError::InvalidInput;
}
ptr::copy_nonoverlapping(data_bytes.as_ptr(), buffer as *mut u8, data_bytes.len());
SupabaseError::Success
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
#[test]
fn test_client_creation() {
let url = CString::new("http://localhost:54321").unwrap();
let key = CString::new("test-key").unwrap();
unsafe {
let client = supabase_client_new(url.as_ptr(), key.as_ptr());
assert!(!client.is_null());
supabase_client_free(client);
}
}
#[test]
fn test_error_handling() {
unsafe {
let client = supabase_client_new(ptr::null(), ptr::null());
assert!(client.is_null());
}
}
#[test]
fn test_error_storage() {
let mut buffer = [0u8; 256];
unsafe {
let result = supabase_get_last_error(buffer.as_mut_ptr() as *mut c_char, buffer.len());
assert_eq!(result as i32, SupabaseError::Success as i32);
}
}
}