use std::ffi::{CStr, CString, c_char, c_void};
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::sync::Arc;
use tokio::runtime::Handle;
use url::Url;
use super::raw::ScandalUriResolver;
use crate::scandal::error::ScandalError;
use crate::scandal::{ResolvedUri, ResourceAccess, UriResolver};
struct HostResolver {
resolver: Arc<dyn UriResolver>,
runtime: Handle,
}
pub(super) fn callbacks(
resolver: Arc<dyn UriResolver>,
) -> Result<ScandalUriResolver, ScandalError> {
let runtime = Handle::try_current().map_err(internal)?;
let context = Box::into_raw(Box::new(HostResolver { resolver, runtime })).cast();
Ok(ScandalUriResolver {
context,
resolve: Some(resolve),
free_result: Some(free_result),
release: Some(release),
})
}
unsafe extern "C" fn resolve(
context: *mut c_void,
uri: *const c_char,
access: *const c_char,
out: *mut *mut c_char,
) -> bool {
let result = catch_unwind(AssertUnwindSafe(|| -> Result<ResolvedUri, ScandalError> {
let host = unsafe { &*context.cast::<HostResolver>() };
let uri = unsafe { CStr::from_ptr(uri) }.to_str().map_err(internal)?;
let uri = Url::parse(uri).map_err(internal)?;
let access: ResourceAccess =
serde_json::from_slice(unsafe { CStr::from_ptr(access) }.to_bytes())
.map_err(internal)?;
host.runtime
.block_on(host.resolver.resolve_for(&uri, access))
}))
.unwrap_or_else(|_| Err(internal("host URI resolver panicked")));
let success = result.is_ok();
let json = match result {
Ok(value) => serde_json::to_string(&value).expect("URI binding serializes"),
Err(error) => serde_json::to_string(&error.to_wire()).expect("error envelope serializes"),
};
unsafe {
*out = CString::new(json).expect("JSON has no NUL").into_raw();
}
success
}
unsafe extern "C" fn free_result(_context: *mut c_void, value: *mut c_char) {
unsafe {
drop(CString::from_raw(value));
}
}
unsafe extern "C" fn release(context: *mut c_void) {
unsafe {
drop(Box::from_raw(context.cast::<HostResolver>()));
}
}
fn internal(error: impl ToString) -> ScandalError {
ScandalError::Internal {
message: error.to_string(),
}
}