#![allow(dead_code)]
use std::ffi::{CStr, c_char};
use blazen_uniffi::errors::BlazenError as InnerError;
use crate::string::alloc_cstring;
pub const BLAZEN_ERROR_KIND_AUTH: u32 = 1;
pub const BLAZEN_ERROR_KIND_RATE_LIMIT: u32 = 2;
pub const BLAZEN_ERROR_KIND_TIMEOUT: u32 = 3;
pub const BLAZEN_ERROR_KIND_VALIDATION: u32 = 4;
pub const BLAZEN_ERROR_KIND_CONTENT_POLICY: u32 = 5;
pub const BLAZEN_ERROR_KIND_UNSUPPORTED: u32 = 6;
pub const BLAZEN_ERROR_KIND_COMPUTE: u32 = 7;
pub const BLAZEN_ERROR_KIND_MEDIA: u32 = 8;
pub const BLAZEN_ERROR_KIND_PROVIDER: u32 = 9;
pub const BLAZEN_ERROR_KIND_WORKFLOW: u32 = 10;
pub const BLAZEN_ERROR_KIND_TOOL: u32 = 11;
pub const BLAZEN_ERROR_KIND_PEER: u32 = 12;
pub const BLAZEN_ERROR_KIND_PERSIST: u32 = 13;
pub const BLAZEN_ERROR_KIND_PROMPT: u32 = 14;
pub const BLAZEN_ERROR_KIND_MEMORY: u32 = 15;
pub const BLAZEN_ERROR_KIND_CACHE: u32 = 16;
pub const BLAZEN_ERROR_KIND_CANCELLED: u32 = 17;
pub const BLAZEN_ERROR_KIND_INTERNAL: u32 = 18;
pub struct BlazenError {
pub(crate) inner: InnerError,
}
impl BlazenError {
pub(crate) fn into_ptr(self) -> *mut BlazenError {
Box::into_raw(Box::new(self))
}
pub(crate) unsafe fn from_ptr_take(ptr: *mut BlazenError) -> Box<BlazenError> {
debug_assert!(
!ptr.is_null(),
"BlazenError::from_ptr_take called with null"
);
unsafe { Box::from_raw(ptr) }
}
}
impl From<InnerError> for BlazenError {
fn from(inner: InnerError) -> Self {
Self { inner }
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_kind(err: *const BlazenError) -> u32 {
if err.is_null() {
return 0;
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Auth { .. } => BLAZEN_ERROR_KIND_AUTH,
InnerError::RateLimit { .. } => BLAZEN_ERROR_KIND_RATE_LIMIT,
InnerError::Timeout { .. } => BLAZEN_ERROR_KIND_TIMEOUT,
InnerError::Validation { .. } => BLAZEN_ERROR_KIND_VALIDATION,
InnerError::ContentPolicy { .. } => BLAZEN_ERROR_KIND_CONTENT_POLICY,
InnerError::Unsupported { .. } => BLAZEN_ERROR_KIND_UNSUPPORTED,
InnerError::Compute { .. } => BLAZEN_ERROR_KIND_COMPUTE,
InnerError::Media { .. } => BLAZEN_ERROR_KIND_MEDIA,
InnerError::Provider { .. } => BLAZEN_ERROR_KIND_PROVIDER,
InnerError::Workflow { .. } => BLAZEN_ERROR_KIND_WORKFLOW,
InnerError::Tool { .. } => BLAZEN_ERROR_KIND_TOOL,
InnerError::Peer { .. } => BLAZEN_ERROR_KIND_PEER,
InnerError::Persist { .. } => BLAZEN_ERROR_KIND_PERSIST,
InnerError::Prompt { .. } => BLAZEN_ERROR_KIND_PROMPT,
InnerError::Memory { .. } => BLAZEN_ERROR_KIND_MEMORY,
InnerError::Cache { .. } => BLAZEN_ERROR_KIND_CACHE,
InnerError::Cancelled => BLAZEN_ERROR_KIND_CANCELLED,
InnerError::Internal { .. } => BLAZEN_ERROR_KIND_INTERNAL,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_message(err: *const BlazenError) -> *mut c_char {
if err.is_null() {
return std::ptr::null_mut();
}
let err = unsafe { &*err };
let msg = match &err.inner {
InnerError::Auth { message }
| InnerError::RateLimit { message, .. }
| InnerError::Timeout { message, .. }
| InnerError::Validation { message }
| InnerError::ContentPolicy { message }
| InnerError::Unsupported { message }
| InnerError::Compute { message }
| InnerError::Media { message }
| InnerError::Provider { message, .. }
| InnerError::Workflow { message }
| InnerError::Tool { message }
| InnerError::Peer { message, .. }
| InnerError::Persist { message }
| InnerError::Prompt { message, .. }
| InnerError::Memory { message, .. }
| InnerError::Cache { message, .. }
| InnerError::Internal { message } => message.as_str(),
InnerError::Cancelled => "cancelled",
};
alloc_cstring(msg)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_retry_after_ms(err: *const BlazenError) -> i64 {
if err.is_null() {
return -1;
}
let err = unsafe { &*err };
let value = match &err.inner {
InnerError::RateLimit { retry_after_ms, .. }
| InnerError::Provider { retry_after_ms, .. } => *retry_after_ms,
_ => None,
};
value.and_then(|v| i64::try_from(v).ok()).unwrap_or(-1)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_elapsed_ms(err: *const BlazenError) -> u64 {
if err.is_null() {
return 0;
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Timeout { elapsed_ms, .. } => *elapsed_ms,
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_status(err: *const BlazenError) -> i32 {
if err.is_null() {
return -1;
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Provider {
status: Some(status),
..
} => i32::try_from(*status).unwrap_or(-1),
_ => -1,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_provider(err: *const BlazenError) -> *mut c_char {
if err.is_null() {
return std::ptr::null_mut();
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Provider {
provider: Some(provider),
..
} => alloc_cstring(provider),
_ => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_endpoint(err: *const BlazenError) -> *mut c_char {
if err.is_null() {
return std::ptr::null_mut();
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Provider {
endpoint: Some(endpoint),
..
} => alloc_cstring(endpoint),
_ => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_request_id(err: *const BlazenError) -> *mut c_char {
if err.is_null() {
return std::ptr::null_mut();
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Provider {
request_id: Some(request_id),
..
} => alloc_cstring(request_id),
_ => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_detail(err: *const BlazenError) -> *mut c_char {
if err.is_null() {
return std::ptr::null_mut();
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Provider {
detail: Some(detail),
..
} => alloc_cstring(detail),
_ => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_subkind(err: *const BlazenError) -> *mut c_char {
if err.is_null() {
return std::ptr::null_mut();
}
let err = unsafe { &*err };
match &err.inner {
InnerError::Provider { kind, .. }
| InnerError::Peer { kind, .. }
| InnerError::Prompt { kind, .. }
| InnerError::Memory { kind, .. }
| InnerError::Cache { kind, .. } => alloc_cstring(kind),
_ => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_free(err: *mut BlazenError) {
if err.is_null() {
return;
}
drop(unsafe { Box::from_raw(err) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn blazen_error_from_json(json: *const c_char) -> *mut BlazenError {
if json.is_null() {
return std::ptr::null_mut();
}
let cstr = unsafe { CStr::from_ptr(json) };
let Ok(s) = cstr.to_str() else {
return BlazenError::from(InnerError::Internal {
message: "blazen_error_from_json: input is not valid UTF-8".to_string(),
})
.into_ptr();
};
let inner = parse_error_json(s);
BlazenError::from(inner).into_ptr()
}
fn parse_error_json(s: &str) -> InnerError {
let value: serde_json::Value = match serde_json::from_str(s) {
Ok(v) => v,
Err(e) => {
return InnerError::Internal {
message: format!("blazen_error_from_json: malformed JSON: {e}"),
};
}
};
let Some(obj) = value.as_object() else {
return InnerError::Internal {
message: format!("blazen_error_from_json: expected JSON object, got {value}"),
};
};
let kind = obj.get("kind").and_then(serde_json::Value::as_str);
let message = obj
.get("message")
.and_then(serde_json::Value::as_str)
.unwrap_or("")
.to_string();
let get_str = |key: &str| -> Option<String> {
obj.get(key)
.and_then(serde_json::Value::as_str)
.map(str::to_string)
};
let get_u32 = |key: &str| -> Option<u32> {
obj.get(key)
.and_then(serde_json::Value::as_u64)
.and_then(|v| u32::try_from(v).ok())
};
let get_u64 = |key: &str| -> Option<u64> { obj.get(key).and_then(serde_json::Value::as_u64) };
match kind {
Some("Auth") => InnerError::Auth { message },
Some("RateLimit") => InnerError::RateLimit {
message,
retry_after_ms: get_u64("retry_after_ms"),
},
Some("Timeout") => InnerError::Timeout {
message,
elapsed_ms: get_u64("elapsed_ms").unwrap_or(0),
},
Some("Validation") => InnerError::Validation { message },
Some("ContentPolicy") => InnerError::ContentPolicy { message },
Some("Unsupported") => InnerError::Unsupported { message },
Some("Compute") => InnerError::Compute { message },
Some("Media") => InnerError::Media { message },
Some("Provider") => InnerError::Provider {
kind: get_str("subkind")
.or_else(|| get_str("provider_kind"))
.unwrap_or_else(|| "Other".to_string()),
message,
provider: get_str("provider"),
status: get_u32("status"),
endpoint: get_str("endpoint"),
request_id: get_str("request_id"),
detail: get_str("detail"),
retry_after_ms: get_u64("retry_after_ms"),
},
Some("Workflow") => InnerError::Workflow { message },
Some("Tool") => InnerError::Tool { message },
Some("Peer") => InnerError::Peer {
kind: get_str("subkind")
.or_else(|| get_str("peer_kind"))
.unwrap_or_else(|| "Transport".to_string()),
message,
},
Some("Persist") => InnerError::Persist { message },
Some("Prompt") => InnerError::Prompt {
kind: get_str("subkind")
.or_else(|| get_str("prompt_kind"))
.unwrap_or_else(|| "Validation".to_string()),
message,
},
Some("Memory") => InnerError::Memory {
kind: get_str("subkind")
.or_else(|| get_str("memory_kind"))
.unwrap_or_else(|| "Backend".to_string()),
message,
},
Some("Cache") => InnerError::Cache {
kind: get_str("subkind")
.or_else(|| get_str("cache_kind"))
.unwrap_or_else(|| "Io".to_string()),
message,
},
Some("Cancelled") => InnerError::Cancelled,
Some("Internal") => InnerError::Internal { message },
Some(other) => InnerError::Internal {
message: format!(
"blazen_error_from_json: unknown error kind {other:?}; original message: {message}"
),
},
None => InnerError::Internal {
message: format!(
"blazen_error_from_json: missing 'kind' field; original message: {message}"
),
},
}
}