#![allow(clippy::missing_safety_doc)]
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use crate::aam::AAM;
use crate::error::AamlError;
use crate::pipeline::formatter::FormattingOptions as FormatterRules;
fn first_error(errors: Vec<AamlError>) -> AamlError {
errors.into_iter().next().unwrap_or(AamlError::ParseError {
line: 1,
content: String::new(),
details: "unexpected empty parse error list".to_string(),
diagnostics: None,
})
}
pub struct AamHandle {
inner: AAM,
last_error: Option<CString>,
}
impl AamHandle {
fn set_error(&mut self, err: impl ToString) {
let msg = err.to_string().replace('\0', "<NUL>");
self.last_error = CString::new(msg).ok();
}
fn clear_error(&mut self) {
self.last_error = None;
}
}
#[unsafe(no_mangle)]
pub extern "C" fn aam_new() -> *mut AamHandle {
Box::into_raw(Box::new(AamHandle {
inner: AAM::new(),
last_error: None,
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_free(handle: *mut AamHandle) {
if !handle.is_null() {
unsafe { drop(Box::from_raw(handle)) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_parse(handle: *mut AamHandle, content: *const c_char) -> i32 {
if handle.is_null() || content.is_null() {
return -1;
}
let handle = unsafe { &mut *handle };
let content = match unsafe { CStr::from_ptr(content) }.to_str() {
Ok(s) => s,
Err(e) => {
handle.set_error(e);
return -1;
}
};
match AAM::parse(content) {
Ok(aam) => {
handle.inner = aam;
handle.clear_error();
0
}
Err(e) => {
handle.set_error(first_error(e));
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_load(handle: *mut AamHandle, path: *const c_char) -> i32 {
if handle.is_null() || path.is_null() {
return -1;
}
let handle = unsafe { &mut *handle };
let path = match unsafe { CStr::from_ptr(path) }.to_str() {
Ok(s) => s,
Err(e) => {
handle.set_error(e);
return -1;
}
};
match AAM::load(path) {
Ok(aam) => {
handle.inner = aam;
handle.clear_error();
0
}
Err(e) => {
handle.set_error(first_error(e));
-1
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_format(handle: *mut AamHandle, content: *const c_char) -> *mut c_char {
if handle.is_null() || content.is_null() {
return std::ptr::null_mut();
}
let handle_ref = unsafe { &mut *handle };
let content_str = match unsafe { CStr::from_ptr(content) }.to_str() {
Ok(s) => s,
Err(e) => {
handle_ref.set_error(e);
return std::ptr::null_mut();
}
};
let rules = FormatterRules::default();
match handle_ref.inner.format(content_str, &rules) {
Ok(formatted) => to_c_string(&formatted),
Err(e) => {
handle_ref.set_error(e);
std::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_get(handle: *const AamHandle, key: *const c_char) -> *mut c_char {
if handle.is_null() || key.is_null() {
return std::ptr::null_mut();
}
let handle = unsafe { &*handle };
let key = match unsafe { CStr::from_ptr(key) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
match handle.inner.get(key) {
Some(v) => to_c_string(v),
None => std::ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_find(handle: *const AamHandle, query: *const c_char) -> *mut c_char {
if handle.is_null() || query.is_null() {
return std::ptr::null_mut();
}
let handle = unsafe { &*handle };
let query = match unsafe { CStr::from_ptr(query) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
to_c_string_map(handle.inner.find(query))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_deep_search(
handle: *const AamHandle,
pattern: *const c_char,
) -> *mut c_char {
if handle.is_null() || pattern.is_null() {
return std::ptr::null_mut();
}
let handle = unsafe { &*handle };
let pattern = match unsafe { CStr::from_ptr(pattern) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
to_c_string_map(handle.inner.deep_search(pattern))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_reverse_search(
handle: *const AamHandle,
value: *const c_char,
) -> *mut c_char {
if handle.is_null() || value.is_null() {
return std::ptr::null_mut();
}
let handle = unsafe { &*handle };
let value = match unsafe { CStr::from_ptr(value) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
to_c_string_list(handle.inner.reverse_search(value))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_schema_names(handle: *const AamHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
let handle = unsafe { &*handle };
if let Some(schemas) = handle.inner.schemas() {
let keys: Vec<&str> = schemas.keys().map(|k| k.as_str()).collect();
to_c_string_list(keys)
} else {
std::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_type_names(handle: *const AamHandle) -> *mut c_char {
if handle.is_null() {
return std::ptr::null_mut();
}
let handle = unsafe { &*handle };
if let Some(types) = handle.inner.types() {
let keys: Vec<&str> = types.keys().map(|k| k.as_str()).collect();
to_c_string_list(keys)
} else {
std::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_string_free(s: *mut c_char) {
if !s.is_null() {
unsafe { drop(CString::from_raw(s)) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn aam_last_error(handle: *const AamHandle) -> *const c_char {
if handle.is_null() {
return std::ptr::null();
}
let handle = unsafe { &*handle };
match &handle.last_error {
Some(cs) => cs.as_ptr(),
None => std::ptr::null(),
}
}
fn to_c_string(s: &str) -> *mut c_char {
let safe = s.replace('\0', "<NUL>");
match CString::new(safe) {
Ok(cs) => cs.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
fn to_c_string_list(list: Vec<&str>) -> *mut c_char {
if list.is_empty() {
return std::ptr::null_mut();
}
let joined = list.join(",");
to_c_string(&joined)
}
fn to_c_string_map(map: Vec<(&str, &str)>) -> *mut c_char {
if map.is_empty() {
return std::ptr::null_mut();
}
let joined = map
.into_iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("\n");
to_c_string(&joined)
}