use std::ffi::{CStr, CString, c_char, c_double, c_int, c_void};
use std::sync::Mutex;
use asdf_core::yaml::{
self as asdf_yaml, Document, NodeId, Resolved, ScalarStyle, Schema, Tag, resolve,
};
use asdf_core::{PendingBlock, Reader, Writer};
use crate::error_ffi::ErrorState;
use crate::ffi::{CMallocBuf, write_out};
use crate::panic::guard;
use crate::types::{AsdfValueErr, AsdfValueType, asdf_config_t};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum FileMode {
ReadOnly,
Write,
ReadWrite,
}
impl FileMode {
fn writable(self) -> bool {
self != FileMode::ReadOnly
}
fn parse(mode: &str) -> Option<Self> {
match mode.to_ascii_lowercase().as_str() {
"r" => Some(FileMode::ReadOnly),
"w" => Some(FileMode::Write),
"rw" => Some(FileMode::ReadWrite),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub(crate) struct FileConfig {
pub array_storage: crate::types::AsdfArrayStorage,
pub inline_ndarray_warning_thresh: usize,
pub log_stream: *mut c_void,
pub log_level: crate::error_ffi::LogLevel,
}
#[derive(Debug)]
pub struct AsdfFile {
reader: Option<Reader>,
document: Option<Document>,
mode: FileMode,
pub(crate) config: FileConfig,
blocks: Vec<PendingBlock>,
error: ErrorState,
interned: Mutex<Vec<CString>>,
}
#[derive(Debug)]
pub struct AsdfValue {
file: *mut AsdfFile,
node: NodeId,
}
impl AsdfValue {
pub(crate) fn new(file: *mut AsdfFile, node: NodeId) -> Self {
Self { file, node }
}
}
pub(crate) unsafe fn read_config(config: *const asdf_config_t) -> FileConfig {
if config.is_null() {
return FileConfig::default();
}
let view = unsafe { &*config };
FileConfig {
array_storage: view.emitter.array_storage,
inline_ndarray_warning_thresh: view.emitter.inline_ndarray_warning_thresh,
log_stream: view.log.stream,
log_level: view.log.level,
}
}
pub(crate) fn file_config(file: *const AsdfFile) -> Option<FileConfig> {
unsafe { crate::ffi::as_ref(file) }.map(|f| f.config)
}
pub(crate) fn error_state(file: *mut AsdfFile) -> Option<&'static ErrorState> {
if file.is_null() {
return None;
}
Some(&unsafe { &*file }.error)
}
pub(crate) fn value_file(value: *mut AsdfValue) -> Option<*mut AsdfFile> {
if value.is_null() {
return None;
}
let file = unsafe { &*value }.file;
(!file.is_null()).then_some(file)
}
pub(crate) fn value_node(value: *mut AsdfValue) -> Option<NodeId> {
if value.is_null() {
return None;
}
Some(unsafe { &*value }.node)
}
pub(crate) fn file_reader(file: *mut AsdfFile) -> Option<&'static Reader> {
if file.is_null() {
return None;
}
unsafe { &*file }.reader.as_ref()
}
pub(crate) fn file_blocks_mut(file: *mut AsdfFile) -> Option<&'static mut Vec<PendingBlock>> {
if file.is_null() {
return None;
}
let handle = unsafe { &mut *file };
handle.mode.writable().then_some(&mut handle.blocks)
}
pub(crate) fn file_document(file: *mut AsdfFile) -> Option<&'static Document> {
if file.is_null() {
return None;
}
unsafe { &*file }.document()
}
pub(crate) fn file_document_mut(file: *mut AsdfFile) -> Option<&'static mut Document> {
if file.is_null() {
return None;
}
unsafe { &mut *file }.document_for_values()
}
pub(crate) fn value_document(value: *mut AsdfValue) -> Option<&'static Document> {
let file = value_file(value)?;
unsafe { &*file }.document()
}
impl AsdfFile {
fn new(mode: FileMode) -> Self {
Self {
reader: None,
document: None,
mode,
config: FileConfig::default(),
blocks: Vec::new(),
error: ErrorState::default(),
interned: Mutex::new(Vec::new()),
}
}
fn new_for_writing() -> Self {
let mut file = Self::new(FileMode::Write);
file.document_for_write();
file
}
fn document_for_write(&mut self) -> Option<&mut Document> {
if !self.mode.writable() {
return None;
}
if self.document.is_none() {
let mut doc = Document::new_asdf();
let root = doc.add(asdf_core::yaml::Node::mapping());
doc.node_mut(root).tag = Some(Tag::parse(ASDF_ROOT_TAG));
doc.set_root(root);
self.document = Some(doc);
}
self.document.as_mut()
}
pub(crate) fn intern(&self, s: &str) -> *const c_char {
let Ok(c) = CString::new(s) else {
return std::ptr::null();
};
let mut arena = self.interned.lock().unwrap_or_else(|e| e.into_inner());
arena.push(c);
arena.last().map_or(std::ptr::null(), |c| c.as_ptr())
}
fn document(&self) -> Option<&Document> {
self.document.as_ref()
}
pub(crate) fn document_for_values(&mut self) -> Option<&mut Document> {
if self.document.is_none() {
self.document = Some(Document::new_asdf());
}
self.document.as_mut()
}
}
const ASDF_ROOT_TAG: &str = "tag:stsci.edu:asdf/core/asdf-1.1.0";
fn with_config(file: *mut AsdfFile, settings: FileConfig) -> *mut AsdfFile {
if !file.is_null() {
unsafe { (*file).config = settings };
}
file
}
fn open_reader(reader: Reader, mode: FileMode) -> *mut AsdfFile {
let mut file = AsdfFile::new(mode);
match reader.tree() {
Ok(doc) => file.document = doc,
Err(e) => {
file.error.set_error(&e);
}
}
file.reader = Some(reader);
Box::into_raw(Box::new(file))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_open_file_ex(
filename: *const c_char,
mode: *const c_char,
config: *mut asdf_config_t,
) -> *mut AsdfFile {
guard("asdf_open_file_ex", std::ptr::null_mut(), || {
let settings = unsafe { read_config(config) };
if mode.is_null() {
return std::ptr::null_mut();
}
let text = unsafe { CStr::from_ptr(mode) }.to_string_lossy().into_owned();
let Some(mode) = FileMode::parse(&text) else {
return std::ptr::null_mut();
};
if mode == FileMode::Write {
let mut file = AsdfFile::new_for_writing();
file.config = settings;
return Box::into_raw(Box::new(file));
}
if filename.is_null() {
return std::ptr::null_mut();
}
let path = unsafe { CStr::from_ptr(filename) }.to_string_lossy().into_owned();
match Reader::open(&path) {
Ok(reader) => with_config(open_reader(reader, mode), settings),
Err(_) => std::ptr::null_mut(),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_open_mem_ex(
buf: *const c_void,
size: usize,
config: *mut asdf_config_t,
) -> *mut AsdfFile {
guard("asdf_open_mem_ex", std::ptr::null_mut(), || {
let settings = unsafe { read_config(config) };
if buf.is_null() || size == 0 {
let mut file = AsdfFile::new_for_writing();
file.config = settings;
return Box::into_raw(Box::new(file));
}
let bytes = unsafe { std::slice::from_raw_parts(buf.cast::<u8>(), size) }.to_vec();
match Reader::from_bytes(bytes) {
Ok(reader) => with_config(open_reader(reader, FileMode::ReadWrite), settings),
Err(_) => std::ptr::null_mut(),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_open_fp_ex(
fp: *mut c_void,
filename: *const c_char,
config: *mut asdf_config_t,
) -> *mut AsdfFile {
guard("asdf_open_fp_ex", std::ptr::null_mut(), || {
let _ = filename;
let settings = unsafe { read_config(config) };
if fp.is_null() {
return std::ptr::null_mut();
}
let mut bytes = Vec::new();
let mut chunk = [0u8; 8192];
loop {
let read = unsafe {
libc::fread(
chunk.as_mut_ptr().cast::<c_void>(),
1,
chunk.len(),
fp.cast::<libc::FILE>(),
)
};
if read == 0 {
break;
}
bytes.extend_from_slice(&chunk[..read]);
}
if bytes.is_empty() {
return std::ptr::null_mut();
}
match Reader::from_bytes(bytes) {
Ok(reader) => with_config(open_reader(reader, FileMode::ReadOnly), settings),
Err(_) => std::ptr::null_mut(),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_close(file: *mut AsdfFile) {
guard("asdf_close", (), || {
if !file.is_null() {
drop(unsafe { Box::from_raw(file) });
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_error(file: *mut AsdfFile) -> *const c_char {
guard("asdf_error", std::ptr::null(), || {
if file.is_null() {
return std::ptr::null();
}
unsafe { &*file }.error.message_ptr()
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_error_code(file: *mut AsdfFile) -> c_int {
guard("asdf_error_code", 0, || {
if file.is_null() {
return 0;
}
unsafe { &*file }.error.code()
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_error_errno(file: *mut AsdfFile) -> c_int {
guard("asdf_error_errno", 0, || {
if file.is_null() {
return 0;
}
unsafe { &*file }.error.errno()
})
}
fn lookup(file: *mut AsdfFile, path: *const c_char) -> Option<(&'static Document, NodeId)> {
if file.is_null() {
return None;
}
let f: &'static AsdfFile = unsafe { &*file };
let doc = f.document()?;
let path = if path.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(path) }.to_string_lossy().into_owned()
};
let node = doc.lookup_str(&path)?;
Some((doc, node))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_value(
file: *mut AsdfFile,
path: *const c_char,
) -> *mut AsdfValue {
guard("asdf_get_value", std::ptr::null_mut(), || match lookup(file, path) {
Some((_, node)) => Box::into_raw(Box::new(AsdfValue { file, node })),
None => std::ptr::null_mut(),
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_value_destroy(value: *mut AsdfValue) {
guard("asdf_value_destroy", (), || {
if !value.is_null() {
drop(unsafe { Box::from_raw(value) });
}
})
}
fn value_parts(value: *mut AsdfValue) -> Option<(&'static AsdfFile, &'static Document, NodeId)> {
if value.is_null() {
return None;
}
let v = unsafe { &*value };
if v.file.is_null() {
return None;
}
let f: &'static AsdfFile = unsafe { &*v.file };
let doc = f.document()?;
Some((f, doc, v.node))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_value_get_type(value: *mut AsdfValue) -> AsdfValueType {
guard("asdf_value_get_type", AsdfValueType::Unknown, || {
let Some((_, doc, node)) = value_parts(value) else {
return AsdfValueType::Unknown;
};
AsdfValueType::from(node_type(doc, node))
})
}
fn node_type(doc: &Document, node: NodeId) -> asdf_yaml::ValueType {
use asdf_yaml::{NodeData, ValueType};
let resolved = doc.resolved(node);
match &resolved.data {
NodeData::Mapping { .. } => ValueType::Mapping,
NodeData::Sequence { .. } => ValueType::Sequence,
NodeData::Scalar { value, style } => {
if let Some(tag) = doc.tag_of(node)
&& tag.is_yaml_builtin()
&& let Some(r) =
asdf_yaml::scalar::resolve_tagged(value, tag.suffix(), Schema::Libasdf)
{
return r.value_type();
}
resolve(value, *style, Schema::Libasdf).value_type()
}
NodeData::Alias(_) => ValueType::Unknown,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_value_tag(value: *mut AsdfValue) -> *const c_char {
guard("asdf_value_tag", std::ptr::null(), || {
let Some((file, doc, node)) = value_parts(value) else {
return std::ptr::null();
};
match doc.tag_of(node) {
Some(tag) => file.intern(&tag.full()),
None => std::ptr::null(),
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn asdf_value_type_string(value_type: c_int) -> *const c_char {
let Some(value_type) = AsdfValueType::from_i32(value_type) else {
return c"<unknown>".as_ptr();
};
let s: &'static CStr = match value_type {
AsdfValueType::Unknown => c"<unknown>",
AsdfValueType::Sequence => c"sequence",
AsdfValueType::Mapping => c"mapping",
AsdfValueType::Scalar => c"scalar",
AsdfValueType::String => c"string",
AsdfValueType::Bool => c"bool",
AsdfValueType::Null => c"null",
AsdfValueType::Int8 => c"int8",
AsdfValueType::Int16 => c"int16",
AsdfValueType::Int32 => c"int32",
AsdfValueType::Int64 => c"int64",
AsdfValueType::Uint8 => c"uint8",
AsdfValueType::Uint16 => c"uint16",
AsdfValueType::Uint32 => c"uint32",
AsdfValueType::Uint64 => c"uint64",
AsdfValueType::Float => c"float",
AsdfValueType::Double => c"double",
AsdfValueType::Extension => c"<extension>",
};
s.as_ptr()
}
fn resolve_at(file: *mut AsdfFile, path: *const c_char) -> Option<(Resolved, String, ScalarStyle)> {
let (doc, node) = lookup(file, path)?;
let resolved_node = doc.resolved(node);
let (text, style) = match &resolved_node.data {
asdf_yaml::NodeData::Scalar { value, style } => (value.clone(), *style),
_ => return None,
};
if let Some(tag) = doc.tag_of(node)
&& tag.is_yaml_builtin()
&& let Some(r) = asdf_yaml::scalar::resolve_tagged(&text, tag.suffix(), Schema::Libasdf)
{
return Some((r, text, style));
}
Some((resolve(&text, style, Schema::Libasdf), text, style))
}
macro_rules! int_getter {
($name:ident, $ty:ty) => {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(
file: *mut AsdfFile,
path: *const c_char,
out: *mut $ty,
) -> AsdfValueErr {
guard(stringify!($name), AsdfValueErr::Unknown, || {
let Some((resolved, _, _)) = resolve_at(file, path) else {
return AsdfValueErr::NotFound;
};
let (truncated, fits): ($ty, bool) = match resolved {
Resolved::Uint(v, _) => (v as $ty, <$ty>::try_from(v).is_ok()),
Resolved::Int(v, _) => (v as $ty, <$ty>::try_from(v).is_ok()),
Resolved::IntOverflow => return AsdfValueErr::Overflow,
_ => return AsdfValueErr::TypeMismatch,
};
if !out.is_null() {
unsafe { write_out(out, truncated) };
}
if fits { AsdfValueErr::Ok } else { AsdfValueErr::Overflow }
})
}
};
}
int_getter!(asdf_get_int8, i8);
int_getter!(asdf_get_int16, i16);
int_getter!(asdf_get_int32, i32);
int_getter!(asdf_get_int64, i64);
int_getter!(asdf_get_uint8, u8);
int_getter!(asdf_get_uint16, u16);
int_getter!(asdf_get_uint32, u32);
int_getter!(asdf_get_uint64, u64);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_double(
file: *mut AsdfFile,
path: *const c_char,
out: *mut c_double,
) -> AsdfValueErr {
guard("asdf_get_double", AsdfValueErr::Unknown, || {
let Some((resolved, _, _)) = resolve_at(file, path) else {
return AsdfValueErr::NotFound;
};
let value = match resolved {
Resolved::Double(d) => d,
Resolved::Uint(v, _) => v as f64,
Resolved::Int(v, _) => v as f64,
_ => return AsdfValueErr::TypeMismatch,
};
if !out.is_null() {
unsafe { write_out(out, value) };
}
AsdfValueErr::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_float(
file: *mut AsdfFile,
path: *const c_char,
out: *mut f32,
) -> AsdfValueErr {
guard("asdf_get_float", AsdfValueErr::Unknown, || {
let Some((resolved, _, _)) = resolve_at(file, path) else {
return AsdfValueErr::NotFound;
};
let value = match resolved {
Resolved::Double(d) => d,
Resolved::Uint(v, _) => v as f64,
Resolved::Int(v, _) => v as f64,
_ => return AsdfValueErr::TypeMismatch,
};
if !out.is_null() {
unsafe { write_out(out, value as f32) };
}
AsdfValueErr::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_bool(
file: *mut AsdfFile,
path: *const c_char,
out: *mut bool,
) -> AsdfValueErr {
guard("asdf_get_bool", AsdfValueErr::Unknown, || {
let Some((resolved, text, _)) = resolve_at(file, path) else {
return AsdfValueErr::NotFound;
};
let value = match resolved {
Resolved::Bool(b) => b,
Resolved::Uint(0, _) => false,
Resolved::Uint(1, _) => true,
_ => {
let _ = text;
return AsdfValueErr::TypeMismatch;
}
};
if !out.is_null() {
unsafe { write_out(out, value) };
}
AsdfValueErr::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_string0(
file: *mut AsdfFile,
path: *const c_char,
out: *mut *const c_char,
) -> AsdfValueErr {
guard("asdf_get_string0", AsdfValueErr::Unknown, || {
let Some((resolved, text, _)) = resolve_at(file, path) else {
return AsdfValueErr::NotFound;
};
if !matches!(resolved, Resolved::String) {
return AsdfValueErr::TypeMismatch;
}
let ptr = unsafe { &*file }.intern(&text);
if ptr.is_null() {
return AsdfValueErr::Oom;
}
if !out.is_null() {
unsafe { write_out(out, ptr) };
}
AsdfValueErr::Ok
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_is_null(file: *mut AsdfFile, path: *const c_char) -> bool {
guard("asdf_is_null", false, || matches!(resolve_at(file, path), Some((Resolved::Null, _, _))))
}
macro_rules! type_predicate {
($name:ident, $variant:ident) => {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(file: *mut AsdfFile, path: *const c_char) -> bool {
guard(stringify!($name), false, || match lookup(file, path) {
Some((doc, node)) => {
AsdfValueType::from(node_type(doc, node)) == AsdfValueType::$variant
}
None => false,
})
}
};
}
type_predicate!(asdf_is_mapping, Mapping);
type_predicate!(asdf_is_sequence, Sequence);
type_predicate!(asdf_is_string, String);
type_predicate!(asdf_is_bool, Bool);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_block_count(file: *mut AsdfFile) -> usize {
guard("asdf_block_count", 0, || {
if file.is_null() {
return 0;
}
let handle = unsafe { &*file };
match &handle.reader {
Some(reader) => reader.block_count(),
None => handle.blocks.len(),
}
})
}
fn write_target(file: *mut AsdfFile) -> Option<&'static mut AsdfFile> {
if file.is_null() {
return None;
}
Some(unsafe { &mut *file })
}
fn set_node(
file: *mut AsdfFile,
path: *const c_char,
make: impl FnOnce(&mut Document) -> NodeId,
) -> AsdfValueErr {
let Some(handle) = write_target(file) else {
return AsdfValueErr::Unknown;
};
if !handle.mode.writable() {
return AsdfValueErr::ReadOnly;
}
let path = if path.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(path) }.to_string_lossy().into_owned()
};
let Some(doc) = handle.document_for_write() else {
return AsdfValueErr::Unknown;
};
let node = make(doc);
match doc.insert_at_str(&path, node) {
Ok(_) => AsdfValueErr::Ok,
Err(_) => AsdfValueErr::Unknown,
}
}
pub(crate) unsafe fn set_value_at(
file: *mut AsdfFile,
path: *const c_char,
value: *mut AsdfValue,
) -> AsdfValueErr {
let Some(node) = crate::file_ffi::value_node(value) else {
return AsdfValueErr::Unknown;
};
set_node(file, path, |_| node)
}
macro_rules! scalar_setter {
($name:ident, $ty:ty) => {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(
file: *mut AsdfFile,
path: *const c_char,
value: $ty,
) -> AsdfValueErr {
guard(stringify!($name), AsdfValueErr::Unknown, || {
set_node(file, path, |doc| doc.add_scalar(value.to_string()))
})
}
};
}
scalar_setter!(asdf_set_int8, i8);
scalar_setter!(asdf_set_int16, i16);
scalar_setter!(asdf_set_int32, i32);
scalar_setter!(asdf_set_int64, i64);
scalar_setter!(asdf_set_uint8, u8);
scalar_setter!(asdf_set_uint16, u16);
scalar_setter!(asdf_set_uint32, u32);
scalar_setter!(asdf_set_uint64, u64);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_string0(
file: *mut AsdfFile,
path: *const c_char,
value: *const c_char,
) -> AsdfValueErr {
guard("asdf_set_string0", AsdfValueErr::Unknown, || {
if value.is_null() {
return AsdfValueErr::Unknown;
}
let text = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
set_node(file, path, |doc| {
let style = match asdf_yaml::resolve(&text, ScalarStyle::Plain, Schema::Libasdf) {
Resolved::String => ScalarStyle::Plain,
_ => ScalarStyle::SingleQuoted,
};
doc.add_scalar_styled(text, style)
})
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_bool(
file: *mut AsdfFile,
path: *const c_char,
value: bool,
) -> AsdfValueErr {
guard("asdf_set_bool", AsdfValueErr::Unknown, || {
set_node(file, path, |doc| doc.add_scalar(if value { "true" } else { "false" }))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_null(file: *mut AsdfFile, path: *const c_char) -> AsdfValueErr {
guard("asdf_set_null", AsdfValueErr::Unknown, || {
set_node(file, path, |doc| doc.add_scalar("null"))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_double(
file: *mut AsdfFile,
path: *const c_char,
value: c_double,
) -> AsdfValueErr {
guard("asdf_set_double", AsdfValueErr::Unknown, || {
set_node(file, path, |doc| doc.add_scalar(asdf_core::core::elements::format_float(value)))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_float(
file: *mut AsdfFile,
path: *const c_char,
value: f32,
) -> AsdfValueErr {
guard("asdf_set_float", AsdfValueErr::Unknown, || {
set_node(file, path, |doc| {
doc.add_scalar(asdf_core::core::elements::format_float(f64::from(value)))
})
})
}
fn serialize(handle: &AsdfFile) -> Result<Vec<u8>, asdf_core::Error> {
let mut writer = match &handle.document {
Some(doc) => Writer::from_document(doc.clone()),
None => Writer::new(),
};
for block in &handle.blocks {
writer.add_block(block.clone());
}
writer.to_bytes()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_write_to_file(file: *mut AsdfFile, filename: *const c_char) -> c_int {
guard("asdf_write_to_file", -1, || {
if file.is_null() || filename.is_null() {
return -1;
}
let handle = unsafe { &*file };
let path = unsafe { CStr::from_ptr(filename) }.to_string_lossy().into_owned();
match serialize(handle).and_then(|bytes| Ok(std::fs::write(&path, bytes)?)) {
Ok(()) => 0,
Err(e) => {
handle.error.set_error(&e);
-1
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_write_to_fp(file: *mut AsdfFile, fp: *mut c_void) -> c_int {
guard("asdf_write_to_fp", -1, || {
if file.is_null() || fp.is_null() {
return -1;
}
let handle = unsafe { &*file };
let bytes = match serialize(handle) {
Ok(b) => b,
Err(e) => {
handle.error.set_error(&e);
return -1;
}
};
let written = unsafe {
libc::fwrite(bytes.as_ptr().cast::<c_void>(), 1, bytes.len(), fp.cast::<libc::FILE>())
};
if written == bytes.len() { 0 } else { -1 }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_write_to_mem(
file: *mut AsdfFile,
buf: *mut *mut c_void,
size: *mut usize,
) -> c_int {
guard("asdf_write_to_mem", -1, || {
if file.is_null() || buf.is_null() || size.is_null() {
return -1;
}
let handle = unsafe { &*file };
let bytes = match serialize(handle) {
Ok(b) => b,
Err(e) => {
handle.error.set_error(&e);
return -1;
}
};
let Some(allocation) = CMallocBuf::copy_from(&bytes) else {
return -1;
};
unsafe { write_out(size, allocation.len()) };
unsafe { write_out(buf, allocation.into_raw()) };
0
})
}
macro_rules! int_predicate {
($name:ident, $ty:ty) => {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(file: *mut AsdfFile, path: *const c_char) -> bool {
guard(stringify!($name), false, || match resolve_at(file, path) {
Some((Resolved::Uint(v, _), _, _)) => <$ty>::try_from(v).is_ok(),
Some((Resolved::Int(v, _), _, _)) => <$ty>::try_from(v).is_ok(),
_ => false,
})
}
};
}
int_predicate!(asdf_is_int8, i8);
int_predicate!(asdf_is_int16, i16);
int_predicate!(asdf_is_int32, i32);
int_predicate!(asdf_is_int64, i64);
int_predicate!(asdf_is_uint8, u8);
int_predicate!(asdf_is_uint16, u16);
int_predicate!(asdf_is_uint32, u32);
int_predicate!(asdf_is_uint64, u64);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_is_int(file: *mut AsdfFile, path: *const c_char) -> bool {
guard("asdf_is_int", false, || {
matches!(resolve_at(file, path), Some((Resolved::Int(..) | Resolved::Uint(..), _, _)))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_is_float(file: *mut AsdfFile, path: *const c_char) -> bool {
guard("asdf_is_float", false, || {
matches!(resolve_at(file, path), Some((Resolved::Double(_), _, _)))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_is_double(file: *mut AsdfFile, path: *const c_char) -> bool {
guard("asdf_is_double", false, || {
matches!(resolve_at(file, path), Some((Resolved::Double(_), _, _)))
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_is_scalar(file: *mut AsdfFile, path: *const c_char) -> bool {
guard("asdf_is_scalar", false, || match lookup(file, path) {
Some((doc, node)) => doc.resolved(node).is_scalar(),
None => false,
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_string(
file: *mut AsdfFile,
path: *const c_char,
out: *mut *const c_char,
out_len: *mut usize,
) -> AsdfValueErr {
guard("asdf_get_string", AsdfValueErr::Unknown, || {
let Some((resolved, text, _)) = resolve_at(file, path) else {
return AsdfValueErr::NotFound;
};
if !matches!(resolved, Resolved::String) {
return AsdfValueErr::TypeMismatch;
}
intern_at(file, &text, out, out_len)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_scalar(
file: *mut AsdfFile,
path: *const c_char,
out: *mut *const c_char,
out_len: *mut usize,
) -> AsdfValueErr {
guard("asdf_get_scalar", AsdfValueErr::Unknown, || {
let Some((doc, node)) = lookup(file, path) else {
return AsdfValueErr::NotFound;
};
let Some(text) = doc.resolved(node).as_str() else {
return AsdfValueErr::TypeMismatch;
};
let text = text.to_string();
intern_at(file, &text, out, out_len)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_get_scalar0(
file: *mut AsdfFile,
path: *const c_char,
out: *mut *const c_char,
) -> AsdfValueErr {
unsafe { asdf_get_scalar(file, path, out, std::ptr::null_mut()) }
}
fn intern_at(
file: *mut AsdfFile,
text: &str,
out: *mut *const c_char,
out_len: *mut usize,
) -> AsdfValueErr {
let ptr = unsafe { &*file }.intern(text);
if ptr.is_null() {
return AsdfValueErr::Oom;
}
if !out.is_null() {
unsafe { write_out(out, ptr) };
}
if !out_len.is_null() {
unsafe { write_out(out_len, text.len()) };
}
AsdfValueErr::Ok
}
macro_rules! container_getter {
($name:ident, $handle:ty, $variant:ident) => {
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(
file: *mut AsdfFile,
path: *const c_char,
out: *mut *mut $handle,
) -> AsdfValueErr {
guard(stringify!($name), AsdfValueErr::Unknown, || {
let Some((doc, node)) = lookup(file, path) else {
return AsdfValueErr::NotFound;
};
if !doc.resolved(node).$variant() {
return AsdfValueErr::TypeMismatch;
}
if !out.is_null() {
let handle = Box::into_raw(Box::new(AsdfValue::new(file, node)));
if handle.is_null() {
return AsdfValueErr::Oom;
}
unsafe { write_out(out, handle) };
}
AsdfValueErr::Ok
})
}
};
}
container_getter!(asdf_get_mapping, crate::value_ffi::AsdfMapping, is_mapping);
container_getter!(asdf_get_sequence, crate::value_ffi::AsdfSequence, is_sequence);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_string(
file: *mut AsdfFile,
path: *const c_char,
str_: *const c_char,
len: usize,
) -> AsdfValueErr {
guard("asdf_set_string", AsdfValueErr::Unknown, || {
if str_.is_null() {
return AsdfValueErr::Unknown;
}
let bytes = unsafe { std::slice::from_raw_parts(str_.cast::<u8>(), len) };
let text = String::from_utf8_lossy(bytes).into_owned();
set_node(file, path, |doc| {
let style = match asdf_yaml::resolve(&text, ScalarStyle::Plain, Schema::Libasdf) {
Resolved::String => ScalarStyle::Plain,
_ => ScalarStyle::SingleQuoted,
};
doc.add_scalar_styled(text, style)
})
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_value(
file: *mut AsdfFile,
path: *const c_char,
value: *mut AsdfValue,
) -> AsdfValueErr {
guard("asdf_set_value", AsdfValueErr::Unknown, || unsafe { set_value_at(file, path, value) })
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_mapping(
file: *mut AsdfFile,
path: *const c_char,
mapping: *mut crate::value_ffi::AsdfMapping,
) -> AsdfValueErr {
guard("asdf_set_mapping", AsdfValueErr::Unknown, || unsafe {
set_value_at(file, path, mapping)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn asdf_set_sequence(
file: *mut AsdfFile,
path: *const c_char,
sequence: *mut crate::value_ffi::AsdfSequence,
) -> AsdfValueErr {
guard("asdf_set_sequence", AsdfValueErr::Unknown, || unsafe {
set_value_at(file, path, sequence)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
buf.extend_from_slice(
b"name: Dennis Richie\nfoo: 42\nbig: 5000000000\nneg: -7\n\
pi: 3.5\nyes_flag: true\nnothing: null\nquoted: '1'\n\
nested:\n inner: deep\nlist: [a, b, c]\n",
);
buf.extend_from_slice(b"...\n");
buf
}
struct Handle(*mut AsdfFile);
impl Drop for Handle {
fn drop(&mut self) {
unsafe { asdf_close(self.0) };
}
}
fn open() -> Handle {
let bytes = sample();
let f =
unsafe { asdf_open_mem_ex(bytes.as_ptr().cast(), bytes.len(), std::ptr::null_mut()) };
assert!(!f.is_null());
Handle(f)
}
fn cpath(s: &str) -> CString {
CString::new(s).unwrap()
}
fn handle_with(body: &str) -> Handle {
let mut buf = Vec::new();
buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
buf.extend_from_slice(body.as_bytes());
buf.extend_from_slice(b"...\n");
let f = unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), std::ptr::null_mut()) };
assert!(!f.is_null());
Handle(f)
}
#[test]
fn width_predicates_agree_with_the_getters() {
let h = handle_with("small: 200\nbig: 70000\nnegative: -5\ntext: hello\n");
let small = cpath("small");
let big = cpath("big");
let negative = cpath("negative");
let text = cpath("text");
assert!(unsafe { asdf_is_uint8(h.0, small.as_ptr()) });
assert!(!unsafe { asdf_is_int8(h.0, small.as_ptr()) });
assert!(unsafe { asdf_is_int16(h.0, small.as_ptr()) });
assert!(unsafe { asdf_is_int(h.0, small.as_ptr()) });
assert!(!unsafe { asdf_is_uint16(h.0, big.as_ptr()) });
assert!(unsafe { asdf_is_uint32(h.0, big.as_ptr()) });
assert!(unsafe { asdf_is_int32(h.0, negative.as_ptr()) });
assert!(!unsafe { asdf_is_uint32(h.0, negative.as_ptr()) });
assert!(!unsafe { asdf_is_int(h.0, text.as_ptr()) });
assert!(unsafe { asdf_is_scalar(h.0, text.as_ptr()) });
let mut narrow: i8 = 0;
assert_eq!(
unsafe { asdf_get_int8(h.0, small.as_ptr(), &mut narrow) },
AsdfValueErr::Overflow
);
let mut wide: u8 = 0;
assert_eq!(unsafe { asdf_get_uint8(h.0, small.as_ptr(), &mut wide) }, AsdfValueErr::Ok);
}
#[test]
fn float_predicates_and_scalar_getters() {
let h = handle_with("pi: 3.14\nn: 7\ntext: hi\n");
let pi = cpath("pi");
let n = cpath("n");
let text = cpath("text");
assert!(unsafe { asdf_is_float(h.0, pi.as_ptr()) });
assert!(unsafe { asdf_is_double(h.0, pi.as_ptr()) });
assert!(!unsafe { asdf_is_float(h.0, n.as_ptr()) });
let mut out = std::ptr::null();
let mut len = 0usize;
assert_eq!(
unsafe { asdf_get_scalar(h.0, pi.as_ptr(), &mut out, &mut len) },
AsdfValueErr::Ok
);
assert_eq!(len, 4);
assert_eq!(unsafe { CStr::from_ptr(out) }, c"3.14");
assert_eq!(
unsafe { asdf_get_string(h.0, pi.as_ptr(), &mut out, &mut len) },
AsdfValueErr::TypeMismatch
);
assert_eq!(
unsafe { asdf_get_string(h.0, text.as_ptr(), &mut out, &mut len) },
AsdfValueErr::Ok
);
assert_eq!(len, 2);
let mut zero_terminated = std::ptr::null();
assert_eq!(
unsafe { asdf_get_scalar0(h.0, n.as_ptr(), &mut zero_terminated) },
AsdfValueErr::Ok
);
assert_eq!(unsafe { CStr::from_ptr(zero_terminated) }, c"7");
}
#[test]
fn container_getters_check_the_type() {
let h = handle_with("m:\n k: v\nseq: [1, 2]\nscalar: 3\n");
let m = cpath("m");
let seq = cpath("seq");
let scalar = cpath("scalar");
let mut mapping = std::ptr::null_mut();
assert_eq!(unsafe { asdf_get_mapping(h.0, m.as_ptr(), &mut mapping) }, AsdfValueErr::Ok);
assert!(!mapping.is_null());
unsafe { asdf_value_destroy(mapping) };
let mut sequence = std::ptr::null_mut();
assert_eq!(
unsafe { asdf_get_sequence(h.0, seq.as_ptr(), &mut sequence) },
AsdfValueErr::Ok
);
assert!(!sequence.is_null());
unsafe { asdf_value_destroy(sequence) };
assert_eq!(
unsafe { asdf_get_mapping(h.0, seq.as_ptr(), &mut mapping) },
AsdfValueErr::TypeMismatch
);
assert_eq!(
unsafe { asdf_get_sequence(h.0, scalar.as_ptr(), &mut sequence) },
AsdfValueErr::TypeMismatch
);
let missing = cpath("absent");
assert_eq!(
unsafe { asdf_get_mapping(h.0, missing.as_ptr(), &mut mapping) },
AsdfValueErr::NotFound
);
}
#[test]
fn counted_string_setter_round_trips() {
let file = unsafe { asdf_open_mem_ex(std::ptr::null(), 0, std::ptr::null_mut()) };
let h = Handle(file);
let path = cpath("label");
let value = b"embedded";
assert_eq!(
unsafe {
asdf_set_string(h.0, path.as_ptr(), value.as_ptr().cast::<c_char>(), value.len())
},
AsdfValueErr::Ok
);
let mut out = std::ptr::null();
let mut len = 0usize;
assert_eq!(
unsafe { asdf_get_string(h.0, path.as_ptr(), &mut out, &mut len) },
AsdfValueErr::Ok
);
assert_eq!(len, value.len());
assert_eq!(unsafe { CStr::from_ptr(out) }, c"embedded");
}
#[test]
fn set_mapping_attaches_a_built_container() {
let file = unsafe { asdf_open_mem_ex(std::ptr::null(), 0, std::ptr::null_mut()) };
let h = Handle(file);
let mapping = unsafe { crate::value_ffi::asdf_mapping_create(h.0) };
let inner = cpath("inner");
assert_eq!(
unsafe { crate::value_ffi::asdf_mapping_set_int32(mapping, inner.as_ptr(), 5) },
AsdfValueErr::Ok
);
let path = cpath("outer/nested");
assert_eq!(unsafe { asdf_set_mapping(h.0, path.as_ptr(), mapping) }, AsdfValueErr::Ok);
unsafe { asdf_value_destroy(mapping) };
let full = cpath("outer/nested/inner");
let mut got: i32 = 0;
assert_eq!(unsafe { asdf_get_int32(h.0, full.as_ptr(), &mut got) }, AsdfValueErr::Ok);
assert_eq!(got, 5);
}
#[test]
fn opens_and_closes_a_memory_buffer() {
let h = open();
assert_eq!(unsafe { asdf_error_code(h.0) }, 0);
assert!(unsafe { asdf_error(h.0) }.is_null());
}
#[test]
fn rejects_bad_arguments_without_crashing() {
assert!(
unsafe { asdf_open_file_ex(std::ptr::null(), std::ptr::null(), std::ptr::null_mut()) }
.is_null()
);
unsafe { asdf_close(std::ptr::null_mut()) };
assert_eq!(unsafe { asdf_error_code(std::ptr::null_mut()) }, 0);
}
#[test]
fn opening_a_null_buffer_creates_a_writable_file() {
let f = unsafe { asdf_open_mem_ex(std::ptr::null(), 0, std::ptr::null_mut()) };
assert!(!f.is_null());
let h = Handle(f);
let path = cpath("foo");
assert_eq!(unsafe { asdf_set_int64(h.0, path.as_ptr(), 42) }, AsdfValueErr::Ok);
}
fn sample_on_disk(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("asdf-file-ffi-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join(name);
std::fs::write(&path, sample()).unwrap();
path
}
#[test]
fn writing_to_a_read_only_file_is_refused() {
let path = sample_on_disk("read-only.asdf");
let name = CString::new(path.to_str().unwrap()).unwrap();
let f = unsafe { asdf_open_file_ex(name.as_ptr(), c"r".as_ptr(), std::ptr::null_mut()) };
assert!(!f.is_null());
let h = Handle(f);
let key = cpath("foo");
assert_eq!(unsafe { asdf_set_int64(h.0, key.as_ptr(), 1) }, AsdfValueErr::ReadOnly);
}
#[test]
fn a_memory_backed_file_is_writable() {
let h = open();
let key = cpath("foo");
assert_eq!(unsafe { asdf_set_int64(h.0, key.as_ptr(), 1) }, AsdfValueErr::Ok);
let name = cpath("name");
let mut out = std::ptr::null();
assert_eq!(unsafe { asdf_get_string0(h.0, name.as_ptr(), &mut out) }, AsdfValueErr::Ok);
let mut got: i64 = 0;
assert_eq!(unsafe { asdf_get_int64(h.0, key.as_ptr(), &mut got) }, AsdfValueErr::Ok);
assert_eq!(got, 1);
}
#[test]
fn open_modes_are_the_three_libasdf_accepts() {
let path = sample_on_disk("modes.asdf");
let name = CString::new(path.to_str().unwrap()).unwrap();
let f = unsafe { asdf_open_file_ex(name.as_ptr(), c"rw".as_ptr(), std::ptr::null_mut()) };
assert!(!f.is_null());
let h = Handle(f);
let key = cpath("foo");
assert_eq!(unsafe { asdf_set_int64(h.0, key.as_ptr(), 7) }, AsdfValueErr::Ok);
let w = unsafe { asdf_open_file_ex(name.as_ptr(), c"W".as_ptr(), std::ptr::null_mut()) };
assert!(!w.is_null());
let wh = Handle(w);
assert_eq!(unsafe { asdf_block_count(wh.0) }, 0);
assert_eq!(unsafe { asdf_set_int64(wh.0, key.as_ptr(), 1) }, AsdfValueErr::Ok);
for bad in [c"rb", c"a", c"r+", c""] {
let bad_open =
unsafe { asdf_open_file_ex(name.as_ptr(), bad.as_ptr(), std::ptr::null_mut()) };
assert!(bad_open.is_null(), "{bad:?} should not be a valid mode");
}
}
#[test]
fn a_written_file_reads_back() {
let f = unsafe { asdf_open_mem_ex(std::ptr::null(), 0, std::ptr::null_mut()) };
let h = Handle(f);
let name = cpath("name");
let value = CString::new("Dennis Richie").unwrap();
assert_eq!(
unsafe { asdf_set_string0(h.0, name.as_ptr(), value.as_ptr()) },
AsdfValueErr::Ok
);
let foo = cpath("foo");
assert_eq!(unsafe { asdf_set_int64(h.0, foo.as_ptr(), 42) }, AsdfValueErr::Ok);
let nested = cpath("powers/squares");
assert_eq!(unsafe { asdf_set_uint64(h.0, nested.as_ptr(), 1764) }, AsdfValueErr::Ok);
let mut buf: *mut c_void = std::ptr::null_mut();
let mut size: usize = 0;
assert_eq!(unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) }, 0);
assert!(!buf.is_null() && size > 0);
let reopened = unsafe { asdf_open_mem_ex(buf, size, std::ptr::null_mut()) };
assert!(!reopened.is_null());
let r = Handle(reopened);
let mut out: *const c_char = std::ptr::null();
assert_eq!(unsafe { asdf_get_string0(r.0, name.as_ptr(), &mut out) }, AsdfValueErr::Ok);
assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "Dennis Richie");
let mut v: i64 = 0;
assert_eq!(unsafe { asdf_get_int64(r.0, foo.as_ptr(), &mut v) }, AsdfValueErr::Ok);
assert_eq!(v, 42);
let mut u: u64 = 0;
assert_eq!(unsafe { asdf_get_uint64(r.0, nested.as_ptr(), &mut u) }, AsdfValueErr::Ok);
assert_eq!(u, 1764);
unsafe { libc::free(buf) };
}
#[test]
fn string_setters_preserve_stringness() {
let f = unsafe { asdf_open_mem_ex(std::ptr::null(), 0, std::ptr::null_mut()) };
let h = Handle(f);
let key = cpath("version");
let value = CString::new("42").unwrap();
unsafe { asdf_set_string0(h.0, key.as_ptr(), value.as_ptr()) };
let mut buf: *mut c_void = std::ptr::null_mut();
let mut size: usize = 0;
unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) };
let reopened = unsafe { asdf_open_mem_ex(buf, size, std::ptr::null_mut()) };
let r = Handle(reopened);
let mut out: *const c_char = std::ptr::null();
assert_eq!(
unsafe { asdf_get_string0(r.0, key.as_ptr(), &mut out) },
AsdfValueErr::Ok,
"a quoted numeric string must read back as a string"
);
assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "42");
unsafe { libc::free(buf) };
}
#[test]
fn a_missing_file_returns_null() {
let name = cpath("/definitely/not/here.asdf");
let mode = cpath("r");
let f = unsafe { asdf_open_file_ex(name.as_ptr(), mode.as_ptr(), std::ptr::null_mut()) };
assert!(f.is_null());
}
#[test]
fn reads_a_string() {
let h = open();
let mut out: *const c_char = std::ptr::null();
let path = cpath("name");
assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut out) }, AsdfValueErr::Ok);
assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "Dennis Richie");
}
#[test]
fn reads_integers_at_every_width() {
let h = open();
let path = cpath("foo");
let mut v8: i8 = 0;
assert_eq!(unsafe { asdf_get_int8(h.0, path.as_ptr(), &mut v8) }, AsdfValueErr::Ok);
assert_eq!(v8, 42);
let mut v64: i64 = 0;
assert_eq!(unsafe { asdf_get_int64(h.0, path.as_ptr(), &mut v64) }, AsdfValueErr::Ok);
assert_eq!(v64, 42);
let mut u8v: u8 = 0;
assert_eq!(unsafe { asdf_get_uint8(h.0, path.as_ptr(), &mut u8v) }, AsdfValueErr::Ok);
assert_eq!(u8v, 42);
}
#[test]
fn too_small_a_type_overflows_rather_than_truncating() {
let h = open();
let path = cpath("big");
let mut v: u8 = 0;
assert_eq!(unsafe { asdf_get_uint8(h.0, path.as_ptr(), &mut v) }, AsdfValueErr::Overflow);
let mut w: u64 = 0;
assert_eq!(unsafe { asdf_get_uint64(h.0, path.as_ptr(), &mut w) }, AsdfValueErr::Ok);
assert_eq!(w, 5_000_000_000);
}
#[test]
fn a_negative_value_does_not_read_as_unsigned() {
let h = open();
let path = cpath("neg");
let mut v: u32 = 0;
assert_eq!(unsafe { asdf_get_uint32(h.0, path.as_ptr(), &mut v) }, AsdfValueErr::Overflow);
let mut s: i32 = 0;
assert_eq!(unsafe { asdf_get_int32(h.0, path.as_ptr(), &mut s) }, AsdfValueErr::Ok);
assert_eq!(s, -7);
}
#[test]
fn reads_floats_and_accepts_integers_as_doubles() {
let h = open();
let mut d: f64 = 0.0;
let pi = cpath("pi");
assert_eq!(unsafe { asdf_get_double(h.0, pi.as_ptr(), &mut d) }, AsdfValueErr::Ok);
assert_eq!(d, 3.5);
let foo = cpath("foo");
assert_eq!(unsafe { asdf_get_double(h.0, foo.as_ptr(), &mut d) }, AsdfValueErr::Ok);
assert_eq!(d, 42.0);
}
#[test]
fn reads_booleans() {
let h = open();
let mut b = false;
let path = cpath("yes_flag");
assert_eq!(unsafe { asdf_get_bool(h.0, path.as_ptr(), &mut b) }, AsdfValueErr::Ok);
assert!(b);
}
#[test]
fn a_quoted_number_is_a_string_not_an_integer() {
let h = open();
let path = cpath("quoted");
let mut v: i64 = 0;
assert_eq!(
unsafe { asdf_get_int64(h.0, path.as_ptr(), &mut v) },
AsdfValueErr::TypeMismatch
);
let mut s: *const c_char = std::ptr::null();
assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut s) }, AsdfValueErr::Ok);
assert_eq!(unsafe { CStr::from_ptr(s) }.to_str().unwrap(), "1");
}
#[test]
fn a_missing_path_is_not_found() {
let h = open();
let path = cpath("nope");
let mut v: i64 = 0;
assert_eq!(unsafe { asdf_get_int64(h.0, path.as_ptr(), &mut v) }, AsdfValueErr::NotFound);
}
#[test]
fn nested_and_indexed_paths_resolve() {
let h = open();
let mut out: *const c_char = std::ptr::null();
let path = cpath("nested/inner");
assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut out) }, AsdfValueErr::Ok);
assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "deep");
let path = cpath("list/1");
assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut out) }, AsdfValueErr::Ok);
assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "b");
}
#[test]
fn nulls_and_type_predicates() {
let h = open();
let nothing = cpath("nothing");
assert!(unsafe { asdf_is_null(h.0, nothing.as_ptr()) });
let nested = cpath("nested");
assert!(unsafe { asdf_is_mapping(h.0, nested.as_ptr()) });
assert!(!unsafe { asdf_is_sequence(h.0, nested.as_ptr()) });
let list = cpath("list");
assert!(unsafe { asdf_is_sequence(h.0, list.as_ptr()) });
let name = cpath("name");
assert!(unsafe { asdf_is_string(h.0, name.as_ptr()) });
}
#[test]
fn value_handles_report_type_and_tag() {
let h = open();
let root = cpath("");
let v = unsafe { asdf_get_value(h.0, root.as_ptr()) };
assert!(!v.is_null());
assert_eq!(unsafe { asdf_value_get_type(v) }, AsdfValueType::Mapping);
let tag = unsafe { asdf_value_tag(v) };
assert!(!tag.is_null());
assert_eq!(
unsafe { CStr::from_ptr(tag) }.to_str().unwrap(),
"tag:stsci.edu:asdf/core/asdf-1.1.0"
);
unsafe { asdf_value_destroy(v) };
unsafe { asdf_value_destroy(std::ptr::null_mut()) };
}
#[test]
fn type_names_match_libasdf() {
let name = |t| unsafe { CStr::from_ptr(asdf_value_type_string(t)) }.to_str().unwrap();
assert_eq!(name(AsdfValueType::Uint8 as c_int), "uint8");
assert_eq!(name(AsdfValueType::Mapping as c_int), "mapping");
assert_eq!(name(AsdfValueType::Unknown as c_int), "<unknown>");
assert_eq!(name(AsdfValueType::Extension as c_int), "<extension>");
}
#[test]
fn interned_strings_stay_valid_while_the_file_is_open() {
let h = open();
let mut first: *const c_char = std::ptr::null();
let path = cpath("name");
unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut first) };
for _ in 0..100 {
let mut other: *const c_char = std::ptr::null();
unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut other) };
}
assert_eq!(unsafe { CStr::from_ptr(first) }.to_str().unwrap(), "Dennis Richie");
}
#[test]
fn null_out_pointers_are_accepted() {
let h = open();
let path = cpath("foo");
assert_eq!(
unsafe { asdf_get_int64(h.0, path.as_ptr(), std::ptr::null_mut()) },
AsdfValueErr::Ok
);
}
#[test]
fn block_count_is_reported() {
let h = open();
assert_eq!(unsafe { asdf_block_count(h.0) }, 0);
assert_eq!(unsafe { asdf_block_count(std::ptr::null_mut()) }, 0);
}
}