1use alloc::ffi::CString;
15use core::ffi::{CStr, c_char, c_double, c_int, c_void};
16use std::sync::Mutex;
17
18use asdf_core::yaml::{
19 self as asdf_yaml, Document, NodeId, Resolved, ScalarStyle, Schema, Tag, resolve,
20};
21use asdf_core::{PendingBlock, Reader, Writer};
22
23use crate::error_ffi::ErrorState;
24use crate::ffi::{CMallocBuf, write_out};
25use crate::panic::guard;
26use crate::types::{AsdfValueErr, AsdfValueType, asdf_config_t};
27
28#[derive(Clone, Copy, PartialEq, Eq, Debug)]
30enum FileMode {
31 ReadOnly,
33 Write,
35 ReadWrite,
38}
39
40impl FileMode {
41 fn writable(self) -> bool {
43 self != FileMode::ReadOnly
44 }
45
46 fn parse(mode: &str) -> Option<Self> {
51 match mode.to_ascii_lowercase().as_str() {
52 "r" => Some(FileMode::ReadOnly),
53 "w" => Some(FileMode::Write),
54 "rw" => Some(FileMode::ReadWrite),
55 _ => None,
56 }
57 }
58}
59
60#[derive(Clone, Copy, Debug, Default)]
66pub(crate) struct FileConfig {
67 pub array_storage: crate::types::AsdfArrayStorage,
69 pub inline_ndarray_warning_thresh: usize,
72 pub log_stream: *mut c_void,
74 pub log_level: crate::error_ffi::LogLevel,
76}
77
78#[derive(Debug)]
80pub struct AsdfFile {
81 reader: Option<Reader>,
82 document: Option<Document>,
83 mode: FileMode,
84 pub(crate) config: FileConfig,
86 blocks: Vec<PendingBlock>,
88 error: ErrorState,
89 interned: Mutex<Vec<CString>>,
95}
96
97#[derive(Debug)]
99pub struct AsdfValue {
100 file: *mut AsdfFile,
101 node: NodeId,
102}
103
104impl AsdfValue {
105 pub(crate) fn new(file: *mut AsdfFile, node: NodeId) -> Self {
107 Self { file, node }
108 }
109}
110
111pub(crate) unsafe fn read_config(config: *const asdf_config_t) -> FileConfig {
116 if config.is_null() {
117 return FileConfig::default();
118 }
119 let view = unsafe { &*config };
120 FileConfig {
121 array_storage: view.emitter.array_storage,
122 inline_ndarray_warning_thresh: view.emitter.inline_ndarray_warning_thresh,
123 log_stream: view.log.stream,
124 log_level: view.log.level,
125 }
126}
127
128pub(crate) fn file_config(file: *const AsdfFile) -> Option<FileConfig> {
130 unsafe { crate::ffi::as_ref(file) }.map(|f| f.config)
131}
132
133pub(crate) fn error_state(file: *mut AsdfFile) -> Option<&'static ErrorState> {
138 if file.is_null() {
139 return None;
140 }
141 Some(&unsafe { &*file }.error)
143}
144
145pub(crate) fn value_file(value: *mut AsdfValue) -> Option<*mut AsdfFile> {
147 if value.is_null() {
148 return None;
149 }
150 let file = unsafe { &*value }.file;
151 (!file.is_null()).then_some(file)
152}
153
154pub(crate) fn value_node(value: *mut AsdfValue) -> Option<NodeId> {
156 if value.is_null() {
157 return None;
158 }
159 Some(unsafe { &*value }.node)
160}
161
162pub(crate) fn file_reader(file: *mut AsdfFile) -> Option<&'static Reader> {
164 if file.is_null() {
165 return None;
166 }
167 unsafe { &*file }.reader.as_ref()
168}
169
170pub(crate) fn file_blocks_mut(file: *mut AsdfFile) -> Option<&'static mut Vec<PendingBlock>> {
172 if file.is_null() {
173 return None;
174 }
175 let handle = unsafe { &mut *file };
176 handle.mode.writable().then_some(&mut handle.blocks)
177}
178
179pub(crate) fn file_document(file: *mut AsdfFile) -> Option<&'static Document> {
181 if file.is_null() {
182 return None;
183 }
184 unsafe { &*file }.document()
186}
187
188pub(crate) fn file_document_mut(file: *mut AsdfFile) -> Option<&'static mut Document> {
196 if file.is_null() {
197 return None;
198 }
199 unsafe { &mut *file }.document_for_values()
200}
201
202pub(crate) fn value_document(value: *mut AsdfValue) -> Option<&'static Document> {
204 let file = value_file(value)?;
205 unsafe { &*file }.document()
207}
208
209impl AsdfFile {
210 fn new(mode: FileMode) -> Self {
211 Self {
212 reader: None,
213 document: None,
214 mode,
215 config: FileConfig::default(),
216 blocks: Vec::new(),
217 error: ErrorState::default(),
218 interned: Mutex::new(Vec::new()),
219 }
220 }
221
222 fn new_for_writing() -> Self {
229 let mut file = Self::new(FileMode::Write);
230 file.document_for_write();
231 file
232 }
233
234 fn document_for_write(&mut self) -> Option<&mut Document> {
239 if !self.mode.writable() {
240 return None;
241 }
242 if self.document.is_none() {
243 let mut doc = Document::new_asdf();
244 let root = doc.add(asdf_core::yaml::Node::mapping());
245 doc.node_mut(root).tag = Some(Tag::parse(ASDF_ROOT_TAG));
247 doc.set_root(root);
248 self.document = Some(doc);
249 }
250 self.document.as_mut()
251 }
252
253 pub(crate) fn intern(&self, s: &str) -> *const c_char {
255 let Ok(c) = CString::new(s) else {
256 return core::ptr::null();
257 };
258 let mut arena = self.interned.lock().unwrap_or_else(|e| e.into_inner());
259 arena.push(c);
260 arena.last().map_or(core::ptr::null(), |c| c.as_ptr())
261 }
262
263 fn document(&self) -> Option<&Document> {
265 self.document.as_ref()
266 }
267
268 pub(crate) fn document_for_values(&mut self) -> Option<&mut Document> {
276 if self.document.is_none() {
277 self.document = Some(Document::new_asdf());
278 }
279 self.document.as_mut()
280 }
281}
282
283const ASDF_ROOT_TAG: &str = "tag:stsci.edu:asdf/core/asdf-1.1.0";
285
286fn with_config(file: *mut AsdfFile, settings: FileConfig) -> *mut AsdfFile {
288 if !file.is_null() {
289 unsafe { (*file).config = settings };
290 }
291 file
292}
293
294fn open_reader(reader: Reader, mode: FileMode) -> *mut AsdfFile {
296 let mut file = AsdfFile::new(mode);
297 match reader.tree() {
298 Ok(doc) => file.document = doc,
299 Err(e) => {
300 file.error.set_error(&e);
303 }
304 }
305 file.reader = Some(reader);
306 Box::into_raw(Box::new(file))
307}
308
309#[unsafe(no_mangle)]
316pub unsafe extern "C" fn asdf_open_file_ex(
317 filename: *const c_char,
318 mode: *const c_char,
319 config: *mut asdf_config_t,
320) -> *mut AsdfFile {
321 guard("asdf_open_file_ex", core::ptr::null_mut(), || {
322 let settings = unsafe { read_config(config) };
323 if mode.is_null() {
324 return core::ptr::null_mut();
325 }
326 let text = unsafe { CStr::from_ptr(mode) }.to_string_lossy().into_owned();
327 let Some(mode) = FileMode::parse(&text) else {
328 return core::ptr::null_mut();
329 };
330 if mode == FileMode::Write {
334 let mut file = AsdfFile::new_for_writing();
335 file.config = settings;
336 return Box::into_raw(Box::new(file));
337 }
338 if filename.is_null() {
339 return core::ptr::null_mut();
340 }
341 let path = unsafe { CStr::from_ptr(filename) }.to_string_lossy().into_owned();
342 match Reader::open(&path) {
343 Ok(reader) => with_config(open_reader(reader, mode), settings),
344 Err(_) => core::ptr::null_mut(),
345 }
346 })
347}
348
349#[unsafe(no_mangle)]
355pub unsafe extern "C" fn asdf_open_mem_ex(
356 buf: *const c_void,
357 size: usize,
358 config: *mut asdf_config_t,
359) -> *mut AsdfFile {
360 guard("asdf_open_mem_ex", core::ptr::null_mut(), || {
361 let settings = unsafe { read_config(config) };
362 if buf.is_null() || size == 0 {
365 let mut file = AsdfFile::new_for_writing();
366 file.config = settings;
367 return Box::into_raw(Box::new(file));
368 }
369 let bytes = unsafe { core::slice::from_raw_parts(buf.cast::<u8>(), size) }.to_vec();
372 match Reader::from_bytes(bytes) {
373 Ok(reader) => with_config(open_reader(reader, FileMode::ReadWrite), settings),
374 Err(_) => core::ptr::null_mut(),
375 }
376 })
377}
378
379#[unsafe(no_mangle)]
388pub unsafe extern "C" fn asdf_open_fp_ex(
389 fp: *mut c_void,
390 filename: *const c_char,
391 config: *mut asdf_config_t,
392) -> *mut AsdfFile {
393 guard("asdf_open_fp_ex", core::ptr::null_mut(), || {
394 let _ = filename;
395 let settings = unsafe { read_config(config) };
396 if fp.is_null() {
397 return core::ptr::null_mut();
398 }
399
400 let mut bytes = Vec::new();
403 let mut chunk = [0u8; 8192];
404 loop {
405 let read = unsafe {
406 libc::fread(
407 chunk.as_mut_ptr().cast::<c_void>(),
408 1,
409 chunk.len(),
410 fp.cast::<libc::FILE>(),
411 )
412 };
413 if read == 0 {
414 break;
415 }
416 bytes.extend_from_slice(&chunk[..read]);
417 }
418 if bytes.is_empty() {
419 return core::ptr::null_mut();
420 }
421 match Reader::from_bytes(bytes) {
422 Ok(reader) => with_config(open_reader(reader, FileMode::ReadOnly), settings),
423 Err(_) => core::ptr::null_mut(),
424 }
425 })
426}
427
428#[unsafe(no_mangle)]
436pub unsafe extern "C" fn asdf_close(file: *mut AsdfFile) {
437 guard("asdf_close", (), || {
438 if !file.is_null() {
439 drop(unsafe { Box::from_raw(file) });
440 }
441 })
442}
443
444#[unsafe(no_mangle)]
450pub unsafe extern "C" fn asdf_error(file: *mut AsdfFile) -> *const c_char {
451 guard("asdf_error", core::ptr::null(), || {
452 if file.is_null() {
453 return core::ptr::null();
454 }
455 unsafe { &*file }.error.message_ptr()
456 })
457}
458
459#[unsafe(no_mangle)]
464pub unsafe extern "C" fn asdf_error_code(file: *mut AsdfFile) -> c_int {
465 guard("asdf_error_code", 0, || {
466 if file.is_null() {
467 return 0;
468 }
469 unsafe { &*file }.error.code()
470 })
471}
472
473#[unsafe(no_mangle)]
478pub unsafe extern "C" fn asdf_error_errno(file: *mut AsdfFile) -> c_int {
479 guard("asdf_error_errno", 0, || {
480 if file.is_null() {
481 return 0;
482 }
483 unsafe { &*file }.error.errno()
484 })
485}
486
487fn lookup(file: *mut AsdfFile, path: *const c_char) -> Option<(&'static Document, NodeId)> {
489 if file.is_null() {
490 return None;
491 }
492 let f: &'static AsdfFile = unsafe { &*file };
494 let doc = f.document()?;
495 let path = if path.is_null() {
496 String::new()
497 } else {
498 unsafe { CStr::from_ptr(path) }.to_string_lossy().into_owned()
499 };
500 let node = doc.lookup_str(&path)?;
501 Some((doc, node))
502}
503
504#[unsafe(no_mangle)]
510pub unsafe extern "C" fn asdf_get_value(
511 file: *mut AsdfFile,
512 path: *const c_char,
513) -> *mut AsdfValue {
514 guard("asdf_get_value", core::ptr::null_mut(), || match lookup(file, path) {
515 Some((_, node)) => Box::into_raw(Box::new(AsdfValue { file, node })),
516 None => core::ptr::null_mut(),
517 })
518}
519
520#[unsafe(no_mangle)]
528pub unsafe extern "C" fn asdf_value_destroy(value: *mut AsdfValue) {
529 guard("asdf_value_destroy", (), || {
530 if !value.is_null() {
531 drop(unsafe { Box::from_raw(value) });
532 }
533 })
534}
535
536fn value_parts(value: *mut AsdfValue) -> Option<(&'static AsdfFile, &'static Document, NodeId)> {
538 if value.is_null() {
539 return None;
540 }
541 let v = unsafe { &*value };
542 if v.file.is_null() {
543 return None;
544 }
545 let f: &'static AsdfFile = unsafe { &*v.file };
546 let doc = f.document()?;
547 Some((f, doc, v.node))
548}
549
550#[unsafe(no_mangle)]
555pub unsafe extern "C" fn asdf_value_get_type(value: *mut AsdfValue) -> AsdfValueType {
556 guard("asdf_value_get_type", AsdfValueType::Unknown, || {
557 let Some((_, doc, node)) = value_parts(value) else {
558 return AsdfValueType::Unknown;
559 };
560 AsdfValueType::from(node_type(doc, node))
561 })
562}
563
564fn node_type(doc: &Document, node: NodeId) -> asdf_yaml::ValueType {
566 use asdf_yaml::{NodeData, ValueType};
567 let resolved = doc.resolved(node);
568 match &resolved.data {
569 NodeData::Mapping { .. } => ValueType::Mapping,
570 NodeData::Sequence { .. } => ValueType::Sequence,
571 NodeData::Scalar { value, style } => {
572 if let Some(tag) = doc.tag_of(node)
574 && tag.is_yaml_builtin()
575 && let Some(r) =
576 asdf_yaml::scalar::resolve_tagged(value, tag.suffix(), Schema::Libasdf)
577 {
578 return r.value_type();
579 }
580 resolve(value, *style, Schema::Libasdf).value_type()
581 }
582 NodeData::Alias(_) => ValueType::Unknown,
583 }
584}
585
586#[unsafe(no_mangle)]
592pub unsafe extern "C" fn asdf_value_tag(value: *mut AsdfValue) -> *const c_char {
593 guard("asdf_value_tag", core::ptr::null(), || {
594 let Some((file, doc, node)) = value_parts(value) else {
595 return core::ptr::null();
596 };
597 match doc.tag_of(node) {
598 Some(tag) => file.intern(&tag.full()),
599 None => core::ptr::null(),
600 }
601 })
602}
603
604#[unsafe(no_mangle)]
613pub extern "C" fn asdf_value_type_string(value_type: c_int) -> *const c_char {
614 let Some(value_type) = AsdfValueType::from_i32(value_type) else {
615 return c"<unknown>".as_ptr();
616 };
617 let s: &'static CStr = match value_type {
619 AsdfValueType::Unknown => c"<unknown>",
620 AsdfValueType::Sequence => c"sequence",
621 AsdfValueType::Mapping => c"mapping",
622 AsdfValueType::Scalar => c"scalar",
623 AsdfValueType::String => c"string",
624 AsdfValueType::Bool => c"bool",
625 AsdfValueType::Null => c"null",
626 AsdfValueType::Int8 => c"int8",
627 AsdfValueType::Int16 => c"int16",
628 AsdfValueType::Int32 => c"int32",
629 AsdfValueType::Int64 => c"int64",
630 AsdfValueType::Uint8 => c"uint8",
631 AsdfValueType::Uint16 => c"uint16",
632 AsdfValueType::Uint32 => c"uint32",
633 AsdfValueType::Uint64 => c"uint64",
634 AsdfValueType::Float => c"float",
635 AsdfValueType::Double => c"double",
636 AsdfValueType::Extension => c"<extension>",
637 };
638 s.as_ptr()
639}
640
641fn resolve_at(file: *mut AsdfFile, path: *const c_char) -> Option<(Resolved, String, ScalarStyle)> {
643 let (doc, node) = lookup(file, path)?;
644 let resolved_node = doc.resolved(node);
645 let (text, style) = match &resolved_node.data {
646 asdf_yaml::NodeData::Scalar { value, style } => (value.clone(), *style),
647 _ => return None,
648 };
649 if let Some(tag) = doc.tag_of(node)
651 && tag.is_yaml_builtin()
652 && let Some(r) = asdf_yaml::scalar::resolve_tagged(&text, tag.suffix(), Schema::Libasdf)
653 {
654 return Some((r, text, style));
655 }
656 Some((resolve(&text, style, Schema::Libasdf), text, style))
657}
658
659macro_rules! int_getter {
661 ($name:ident, $ty:ty) => {
662 #[unsafe(no_mangle)]
671 pub unsafe extern "C" fn $name(
672 file: *mut AsdfFile,
673 path: *const c_char,
674 out: *mut $ty,
675 ) -> AsdfValueErr {
676 guard(stringify!($name), AsdfValueErr::Unknown, || {
677 let Some((resolved, _, _)) = resolve_at(file, path) else {
678 return AsdfValueErr::NotFound;
679 };
680 let (truncated, fits): ($ty, bool) = match resolved {
683 Resolved::Uint(v, _) => (v as $ty, <$ty>::try_from(v).is_ok()),
684 Resolved::Int(v, _) => (v as $ty, <$ty>::try_from(v).is_ok()),
685 Resolved::IntOverflow => return AsdfValueErr::Overflow,
687 _ => return AsdfValueErr::TypeMismatch,
688 };
689 if !out.is_null() {
690 unsafe { write_out(out, truncated) };
691 }
692 if fits { AsdfValueErr::Ok } else { AsdfValueErr::Overflow }
693 })
694 }
695 };
696}
697
698int_getter!(asdf_get_int8, i8);
699int_getter!(asdf_get_int16, i16);
700int_getter!(asdf_get_int32, i32);
701int_getter!(asdf_get_int64, i64);
702int_getter!(asdf_get_uint8, u8);
703int_getter!(asdf_get_uint16, u16);
704int_getter!(asdf_get_uint32, u32);
705int_getter!(asdf_get_uint64, u64);
706
707#[unsafe(no_mangle)]
714pub unsafe extern "C" fn asdf_get_double(
715 file: *mut AsdfFile,
716 path: *const c_char,
717 out: *mut c_double,
718) -> AsdfValueErr {
719 guard("asdf_get_double", AsdfValueErr::Unknown, || {
720 let Some((resolved, _, _)) = resolve_at(file, path) else {
721 return AsdfValueErr::NotFound;
722 };
723 let value = match resolved {
724 Resolved::Double(d) => d,
725 Resolved::Uint(v, _) => v as f64,
726 Resolved::Int(v, _) => v as f64,
727 _ => return AsdfValueErr::TypeMismatch,
728 };
729 if !out.is_null() {
730 unsafe { write_out(out, value) };
731 }
732 AsdfValueErr::Ok
733 })
734}
735
736#[unsafe(no_mangle)]
741pub unsafe extern "C" fn asdf_get_float(
742 file: *mut AsdfFile,
743 path: *const c_char,
744 out: *mut f32,
745) -> AsdfValueErr {
746 guard("asdf_get_float", AsdfValueErr::Unknown, || {
747 let Some((resolved, _, _)) = resolve_at(file, path) else {
748 return AsdfValueErr::NotFound;
749 };
750 let value = match resolved {
751 Resolved::Double(d) => d,
752 Resolved::Uint(v, _) => v as f64,
753 Resolved::Int(v, _) => v as f64,
754 _ => return AsdfValueErr::TypeMismatch,
755 };
756 if !out.is_null() {
757 unsafe { write_out(out, value as f32) };
758 }
759 AsdfValueErr::Ok
760 })
761}
762
763#[unsafe(no_mangle)]
768pub unsafe extern "C" fn asdf_get_bool(
769 file: *mut AsdfFile,
770 path: *const c_char,
771 out: *mut bool,
772) -> AsdfValueErr {
773 guard("asdf_get_bool", AsdfValueErr::Unknown, || {
774 let Some((resolved, text, _)) = resolve_at(file, path) else {
775 return AsdfValueErr::NotFound;
776 };
777 let value = match resolved {
781 Resolved::Bool(b) => b,
782 Resolved::Uint(0, _) => false,
783 Resolved::Uint(1, _) => true,
784 _ => {
785 let _ = text;
786 return AsdfValueErr::TypeMismatch;
787 }
788 };
789 if !out.is_null() {
790 unsafe { write_out(out, value) };
791 }
792 AsdfValueErr::Ok
793 })
794}
795
796#[unsafe(no_mangle)]
802pub unsafe extern "C" fn asdf_get_string0(
803 file: *mut AsdfFile,
804 path: *const c_char,
805 out: *mut *const c_char,
806) -> AsdfValueErr {
807 guard("asdf_get_string0", AsdfValueErr::Unknown, || {
808 let Some((resolved, text, _)) = resolve_at(file, path) else {
809 return AsdfValueErr::NotFound;
810 };
811 if !matches!(resolved, Resolved::String) {
812 return AsdfValueErr::TypeMismatch;
813 }
814 let ptr = unsafe { &*file }.intern(&text);
815 if ptr.is_null() {
816 return AsdfValueErr::Oom;
817 }
818 if !out.is_null() {
819 unsafe { write_out(out, ptr) };
820 }
821 AsdfValueErr::Ok
822 })
823}
824
825#[unsafe(no_mangle)]
830pub unsafe extern "C" fn asdf_is_null(file: *mut AsdfFile, path: *const c_char) -> bool {
831 guard("asdf_is_null", false, || matches!(resolve_at(file, path), Some((Resolved::Null, _, _))))
832}
833
834macro_rules! type_predicate {
836 ($name:ident, $variant:ident) => {
837 #[unsafe(no_mangle)]
842 pub unsafe extern "C" fn $name(file: *mut AsdfFile, path: *const c_char) -> bool {
843 guard(stringify!($name), false, || match lookup(file, path) {
844 Some((doc, node)) => {
845 AsdfValueType::from(node_type(doc, node)) == AsdfValueType::$variant
846 }
847 None => false,
848 })
849 }
850 };
851}
852
853type_predicate!(asdf_is_mapping, Mapping);
854type_predicate!(asdf_is_sequence, Sequence);
855type_predicate!(asdf_is_string, String);
856type_predicate!(asdf_is_bool, Bool);
857
858#[unsafe(no_mangle)]
863pub unsafe extern "C" fn asdf_block_count(file: *mut AsdfFile) -> usize {
864 guard("asdf_block_count", 0, || {
865 if file.is_null() {
866 return 0;
867 }
868 let handle = unsafe { &*file };
869 match &handle.reader {
872 Some(reader) => reader.block_count(),
873 None => handle.blocks.len(),
874 }
875 })
876}
877
878fn write_target(file: *mut AsdfFile) -> Option<&'static mut AsdfFile> {
882 if file.is_null() {
883 return None;
884 }
885 Some(unsafe { &mut *file })
886}
887
888fn set_node(
890 file: *mut AsdfFile,
891 path: *const c_char,
892 make: impl FnOnce(&mut Document) -> NodeId,
893) -> AsdfValueErr {
894 let Some(handle) = write_target(file) else {
895 return AsdfValueErr::Unknown;
896 };
897 if !handle.mode.writable() {
898 return AsdfValueErr::ReadOnly;
901 }
902 let path = if path.is_null() {
903 String::new()
904 } else {
905 unsafe { CStr::from_ptr(path) }.to_string_lossy().into_owned()
906 };
907 let Some(doc) = handle.document_for_write() else {
908 return AsdfValueErr::Unknown;
909 };
910 let node = make(doc);
911 match doc.insert_at_str(&path, node) {
912 Ok(_) => AsdfValueErr::Ok,
913 Err(_) => AsdfValueErr::Unknown,
914 }
915}
916
917pub(crate) unsafe fn set_value_at(
926 file: *mut AsdfFile,
927 path: *const c_char,
928 value: *mut AsdfValue,
929) -> AsdfValueErr {
930 let Some(node) = crate::file_ffi::value_node(value) else {
931 return AsdfValueErr::Unknown;
932 };
933 set_node(file, path, |_| node)
934}
935
936macro_rules! scalar_setter {
938 ($name:ident, $ty:ty) => {
939 #[unsafe(no_mangle)]
948 pub unsafe extern "C" fn $name(
949 file: *mut AsdfFile,
950 path: *const c_char,
951 value: $ty,
952 ) -> AsdfValueErr {
953 guard(stringify!($name), AsdfValueErr::Unknown, || {
954 set_node(file, path, |doc| doc.add_scalar(value.to_string()))
955 })
956 }
957 };
958}
959
960scalar_setter!(asdf_set_int8, i8);
961scalar_setter!(asdf_set_int16, i16);
962scalar_setter!(asdf_set_int32, i32);
963scalar_setter!(asdf_set_int64, i64);
964scalar_setter!(asdf_set_uint8, u8);
965scalar_setter!(asdf_set_uint16, u16);
966scalar_setter!(asdf_set_uint32, u32);
967scalar_setter!(asdf_set_uint64, u64);
968
969#[unsafe(no_mangle)]
978pub unsafe extern "C" fn asdf_set_string0(
979 file: *mut AsdfFile,
980 path: *const c_char,
981 value: *const c_char,
982) -> AsdfValueErr {
983 guard("asdf_set_string0", AsdfValueErr::Unknown, || {
984 if value.is_null() {
985 return AsdfValueErr::Unknown;
986 }
987 let text = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
988 set_node(file, path, |doc| {
989 let style = match asdf_yaml::resolve(&text, ScalarStyle::Plain, Schema::Libasdf) {
992 Resolved::String => ScalarStyle::Plain,
993 _ => ScalarStyle::SingleQuoted,
994 };
995 doc.add_scalar_styled(text, style)
996 })
997 })
998}
999
1000#[unsafe(no_mangle)]
1005pub unsafe extern "C" fn asdf_set_bool(
1006 file: *mut AsdfFile,
1007 path: *const c_char,
1008 value: bool,
1009) -> AsdfValueErr {
1010 guard("asdf_set_bool", AsdfValueErr::Unknown, || {
1011 set_node(file, path, |doc| doc.add_scalar(if value { "true" } else { "false" }))
1012 })
1013}
1014
1015#[unsafe(no_mangle)]
1020pub unsafe extern "C" fn asdf_set_null(file: *mut AsdfFile, path: *const c_char) -> AsdfValueErr {
1021 guard("asdf_set_null", AsdfValueErr::Unknown, || {
1022 set_node(file, path, |doc| doc.add_scalar("null"))
1023 })
1024}
1025
1026#[unsafe(no_mangle)]
1031pub unsafe extern "C" fn asdf_set_double(
1032 file: *mut AsdfFile,
1033 path: *const c_char,
1034 value: c_double,
1035) -> AsdfValueErr {
1036 guard("asdf_set_double", AsdfValueErr::Unknown, || {
1037 set_node(file, path, |doc| doc.add_scalar(asdf_core::core::elements::format_float(value)))
1038 })
1039}
1040
1041#[unsafe(no_mangle)]
1046pub unsafe extern "C" fn asdf_set_float(
1047 file: *mut AsdfFile,
1048 path: *const c_char,
1049 value: f32,
1050) -> AsdfValueErr {
1051 guard("asdf_set_float", AsdfValueErr::Unknown, || {
1052 set_node(file, path, |doc| {
1053 doc.add_scalar(asdf_core::core::elements::format_float(f64::from(value)))
1054 })
1055 })
1056}
1057
1058fn serialize(handle: &AsdfFile) -> Result<Vec<u8>, asdf_core::Error> {
1060 let mut writer = match &handle.document {
1061 Some(doc) => Writer::from_document(doc.clone()),
1062 None => Writer::new(),
1063 };
1064 for block in &handle.blocks {
1065 writer.add_block(block.clone());
1066 }
1067 writer.to_bytes()
1068}
1069
1070#[unsafe(no_mangle)]
1076pub unsafe extern "C" fn asdf_write_to_file(file: *mut AsdfFile, filename: *const c_char) -> c_int {
1077 guard("asdf_write_to_file", -1, || {
1078 if file.is_null() || filename.is_null() {
1079 return -1;
1080 }
1081 let handle = unsafe { &*file };
1082 let path = unsafe { CStr::from_ptr(filename) }.to_string_lossy().into_owned();
1083
1084 match serialize(handle).and_then(|bytes| Ok(std::fs::write(&path, bytes)?)) {
1085 Ok(()) => 0,
1086 Err(e) => {
1087 handle.error.set_error(&e);
1088 -1
1089 }
1090 }
1091 })
1092}
1093
1094#[unsafe(no_mangle)]
1099pub unsafe extern "C" fn asdf_write_to_fp(file: *mut AsdfFile, fp: *mut c_void) -> c_int {
1100 guard("asdf_write_to_fp", -1, || {
1101 if file.is_null() || fp.is_null() {
1102 return -1;
1103 }
1104 let handle = unsafe { &*file };
1105 let bytes = match serialize(handle) {
1106 Ok(b) => b,
1107 Err(e) => {
1108 handle.error.set_error(&e);
1109 return -1;
1110 }
1111 };
1112 let written = unsafe {
1113 libc::fwrite(bytes.as_ptr().cast::<c_void>(), 1, bytes.len(), fp.cast::<libc::FILE>())
1114 };
1115 if written == bytes.len() { 0 } else { -1 }
1116 })
1117}
1118
1119#[unsafe(no_mangle)]
1127pub unsafe extern "C" fn asdf_write_to_mem(
1128 file: *mut AsdfFile,
1129 buf: *mut *mut c_void,
1130 size: *mut usize,
1131) -> c_int {
1132 guard("asdf_write_to_mem", -1, || {
1133 if file.is_null() || buf.is_null() || size.is_null() {
1134 return -1;
1135 }
1136 let handle = unsafe { &*file };
1137 let bytes = match serialize(handle) {
1138 Ok(b) => b,
1139 Err(e) => {
1140 handle.error.set_error(&e);
1141 return -1;
1142 }
1143 };
1144
1145 let Some(allocation) = CMallocBuf::copy_from(&bytes) else {
1150 return -1;
1151 };
1152 unsafe { write_out(size, allocation.len()) };
1153 unsafe { write_out(buf, allocation.into_raw()) };
1154 0
1155 })
1156}
1157
1158macro_rules! int_predicate {
1165 ($name:ident, $ty:ty) => {
1166 #[unsafe(no_mangle)]
1171 pub unsafe extern "C" fn $name(file: *mut AsdfFile, path: *const c_char) -> bool {
1172 guard(stringify!($name), false, || match resolve_at(file, path) {
1173 Some((Resolved::Uint(v, _), _, _)) => <$ty>::try_from(v).is_ok(),
1174 Some((Resolved::Int(v, _), _, _)) => <$ty>::try_from(v).is_ok(),
1175 _ => false,
1176 })
1177 }
1178 };
1179}
1180
1181int_predicate!(asdf_is_int8, i8);
1182int_predicate!(asdf_is_int16, i16);
1183int_predicate!(asdf_is_int32, i32);
1184int_predicate!(asdf_is_int64, i64);
1185int_predicate!(asdf_is_uint8, u8);
1186int_predicate!(asdf_is_uint16, u16);
1187int_predicate!(asdf_is_uint32, u32);
1188int_predicate!(asdf_is_uint64, u64);
1189
1190#[unsafe(no_mangle)]
1195pub unsafe extern "C" fn asdf_is_int(file: *mut AsdfFile, path: *const c_char) -> bool {
1196 guard("asdf_is_int", false, || {
1197 matches!(resolve_at(file, path), Some((Resolved::Int(..) | Resolved::Uint(..), _, _)))
1198 })
1199}
1200
1201#[unsafe(no_mangle)]
1209pub unsafe extern "C" fn asdf_is_float(file: *mut AsdfFile, path: *const c_char) -> bool {
1210 guard("asdf_is_float", false, || {
1211 matches!(resolve_at(file, path), Some((Resolved::Double(_), _, _)))
1212 })
1213}
1214
1215#[unsafe(no_mangle)]
1220pub unsafe extern "C" fn asdf_is_double(file: *mut AsdfFile, path: *const c_char) -> bool {
1221 guard("asdf_is_double", false, || {
1222 matches!(resolve_at(file, path), Some((Resolved::Double(_), _, _)))
1223 })
1224}
1225
1226#[unsafe(no_mangle)]
1231pub unsafe extern "C" fn asdf_is_scalar(file: *mut AsdfFile, path: *const c_char) -> bool {
1232 guard("asdf_is_scalar", false, || match lookup(file, path) {
1233 Some((doc, node)) => doc.resolved(node).is_scalar(),
1234 None => false,
1235 })
1236}
1237
1238#[unsafe(no_mangle)]
1244pub unsafe extern "C" fn asdf_get_string(
1245 file: *mut AsdfFile,
1246 path: *const c_char,
1247 out: *mut *const c_char,
1248 out_len: *mut usize,
1249) -> AsdfValueErr {
1250 guard("asdf_get_string", AsdfValueErr::Unknown, || {
1251 let Some((resolved, text, _)) = resolve_at(file, path) else {
1252 return AsdfValueErr::NotFound;
1253 };
1254 if !matches!(resolved, Resolved::String) {
1255 return AsdfValueErr::TypeMismatch;
1256 }
1257 intern_at(file, &text, out, out_len)
1258 })
1259}
1260
1261#[unsafe(no_mangle)]
1266pub unsafe extern "C" fn asdf_get_scalar(
1267 file: *mut AsdfFile,
1268 path: *const c_char,
1269 out: *mut *const c_char,
1270 out_len: *mut usize,
1271) -> AsdfValueErr {
1272 guard("asdf_get_scalar", AsdfValueErr::Unknown, || {
1273 let Some((doc, node)) = lookup(file, path) else {
1274 return AsdfValueErr::NotFound;
1275 };
1276 let Some(text) = doc.resolved(node).as_str() else {
1277 return AsdfValueErr::TypeMismatch;
1278 };
1279 let text = text.to_string();
1280 intern_at(file, &text, out, out_len)
1281 })
1282}
1283
1284#[unsafe(no_mangle)]
1289pub unsafe extern "C" fn asdf_get_scalar0(
1290 file: *mut AsdfFile,
1291 path: *const c_char,
1292 out: *mut *const c_char,
1293) -> AsdfValueErr {
1294 unsafe { asdf_get_scalar(file, path, out, core::ptr::null_mut()) }
1295}
1296
1297fn intern_at(
1299 file: *mut AsdfFile,
1300 text: &str,
1301 out: *mut *const c_char,
1302 out_len: *mut usize,
1303) -> AsdfValueErr {
1304 let ptr = unsafe { &*file }.intern(text);
1305 if ptr.is_null() {
1306 return AsdfValueErr::Oom;
1307 }
1308 if !out.is_null() {
1309 unsafe { write_out(out, ptr) };
1310 }
1311 if !out_len.is_null() {
1312 unsafe { write_out(out_len, text.len()) };
1313 }
1314 AsdfValueErr::Ok
1315}
1316
1317macro_rules! container_getter {
1319 ($name:ident, $handle:ty, $variant:ident) => {
1320 #[unsafe(no_mangle)]
1327 pub unsafe extern "C" fn $name(
1328 file: *mut AsdfFile,
1329 path: *const c_char,
1330 out: *mut *mut $handle,
1331 ) -> AsdfValueErr {
1332 guard(stringify!($name), AsdfValueErr::Unknown, || {
1333 let Some((doc, node)) = lookup(file, path) else {
1334 return AsdfValueErr::NotFound;
1335 };
1336 if !doc.resolved(node).$variant() {
1337 return AsdfValueErr::TypeMismatch;
1338 }
1339 if !out.is_null() {
1340 let handle = Box::into_raw(Box::new(AsdfValue::new(file, node)));
1341 if handle.is_null() {
1342 return AsdfValueErr::Oom;
1343 }
1344 unsafe { write_out(out, handle) };
1345 }
1346 AsdfValueErr::Ok
1347 })
1348 }
1349 };
1350}
1351
1352container_getter!(asdf_get_mapping, crate::value_ffi::AsdfMapping, is_mapping);
1353container_getter!(asdf_get_sequence, crate::value_ffi::AsdfSequence, is_sequence);
1354
1355#[unsafe(no_mangle)]
1361pub unsafe extern "C" fn asdf_set_string(
1362 file: *mut AsdfFile,
1363 path: *const c_char,
1364 str_: *const c_char,
1365 len: usize,
1366) -> AsdfValueErr {
1367 guard("asdf_set_string", AsdfValueErr::Unknown, || {
1368 if str_.is_null() {
1369 return AsdfValueErr::Unknown;
1370 }
1371 let bytes = unsafe { core::slice::from_raw_parts(str_.cast::<u8>(), len) };
1372 let text = String::from_utf8_lossy(bytes).into_owned();
1373 set_node(file, path, |doc| {
1374 let style = match asdf_yaml::resolve(&text, ScalarStyle::Plain, Schema::Libasdf) {
1375 Resolved::String => ScalarStyle::Plain,
1376 _ => ScalarStyle::SingleQuoted,
1377 };
1378 doc.add_scalar_styled(text, style)
1379 })
1380 })
1381}
1382
1383#[unsafe(no_mangle)]
1389pub unsafe extern "C" fn asdf_set_value(
1390 file: *mut AsdfFile,
1391 path: *const c_char,
1392 value: *mut AsdfValue,
1393) -> AsdfValueErr {
1394 guard("asdf_set_value", AsdfValueErr::Unknown, || unsafe { set_value_at(file, path, value) })
1395}
1396
1397#[unsafe(no_mangle)]
1402pub unsafe extern "C" fn asdf_set_mapping(
1403 file: *mut AsdfFile,
1404 path: *const c_char,
1405 mapping: *mut crate::value_ffi::AsdfMapping,
1406) -> AsdfValueErr {
1407 guard("asdf_set_mapping", AsdfValueErr::Unknown, || unsafe {
1408 set_value_at(file, path, mapping)
1409 })
1410}
1411
1412#[unsafe(no_mangle)]
1417pub unsafe extern "C" fn asdf_set_sequence(
1418 file: *mut AsdfFile,
1419 path: *const c_char,
1420 sequence: *mut crate::value_ffi::AsdfSequence,
1421) -> AsdfValueErr {
1422 guard("asdf_set_sequence", AsdfValueErr::Unknown, || unsafe {
1423 set_value_at(file, path, sequence)
1424 })
1425}
1426
1427#[unsafe(no_mangle)]
1436pub unsafe extern "C" fn asdf_file_find(
1437 file: *mut AsdfFile,
1438 pred: crate::value_ffi::AsdfValuePred,
1439) -> *mut AsdfValue {
1440 guard("asdf_file_find", core::ptr::null_mut(), || file_find_ex(file, pred, false, None, -1))
1441}
1442
1443#[unsafe(no_mangle)]
1451pub unsafe extern "C" fn asdf_file_find_ex(
1452 file: *mut AsdfFile,
1453 pred: crate::value_ffi::AsdfValuePred,
1454 depth_first: bool,
1455 descend_pred: crate::value_ffi::AsdfValuePred,
1456 max_depth: i64,
1457) -> *mut AsdfValue {
1458 guard("asdf_file_find_ex", core::ptr::null_mut(), || {
1459 file_find_ex(file, pred, depth_first, descend_pred, max_depth)
1460 })
1461}
1462
1463fn file_find_ex(
1469 file: *mut AsdfFile,
1470 pred: crate::value_ffi::AsdfValuePred,
1471 depth_first: bool,
1472 descend_pred: crate::value_ffi::AsdfValuePred,
1473 max_depth: i64,
1474) -> *mut AsdfValue {
1475 let Some((_, node)) = lookup(file, core::ptr::null()) else {
1476 return core::ptr::null_mut();
1477 };
1478 let root = Box::into_raw(Box::new(AsdfValue { file, node }));
1479 let found = crate::value_ffi::value_find_ex(root, pred, depth_first, descend_pred, max_depth);
1480 drop(unsafe { Box::from_raw(root) });
1483 found
1484}
1485
1486#[cfg(test)]
1487mod tests {
1488 use super::*;
1489
1490 fn sample() -> Vec<u8> {
1491 let mut buf = Vec::new();
1492 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
1493 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
1494 buf.extend_from_slice(
1495 b"name: Dennis Richie\nfoo: 42\nbig: 5000000000\nneg: -7\n\
1496 pi: 3.5\nyes_flag: true\nnothing: null\nquoted: '1'\n\
1497 nested:\n inner: deep\nlist: [a, b, c]\n",
1498 );
1499 buf.extend_from_slice(b"...\n");
1500 buf
1501 }
1502
1503 struct Handle(*mut AsdfFile);
1504 impl Drop for Handle {
1505 fn drop(&mut self) {
1506 unsafe { asdf_close(self.0) };
1507 }
1508 }
1509
1510 fn open() -> Handle {
1511 let bytes = sample();
1512 let f =
1513 unsafe { asdf_open_mem_ex(bytes.as_ptr().cast(), bytes.len(), core::ptr::null_mut()) };
1514 assert!(!f.is_null());
1515 Handle(f)
1516 }
1517
1518 fn cpath(s: &str) -> CString {
1519 CString::new(s).unwrap()
1520 }
1521
1522 fn handle_with(body: &str) -> Handle {
1524 let mut buf = Vec::new();
1525 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
1526 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
1527 buf.extend_from_slice(body.as_bytes());
1528 buf.extend_from_slice(b"...\n");
1529 let f = unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), core::ptr::null_mut()) };
1530 assert!(!f.is_null());
1531 Handle(f)
1532 }
1533
1534 unsafe extern "C" fn is_hit(value: *mut AsdfValue) -> bool {
1536 let mut out: *const c_char = core::ptr::null();
1537 let err = unsafe { crate::value_ffi::asdf_value_as_string0(value, &mut out) };
1538 err == AsdfValueErr::Ok
1539 && !out.is_null()
1540 && unsafe { CStr::from_ptr(out) }.to_bytes() == b"hit"
1541 }
1542
1543 #[test]
1544 fn a_file_find_starts_at_the_root_and_owns_nothing_else() {
1545 let h = handle_with("a_nested:\n deep: hit\nz_top: hit\n");
1546
1547 let found = unsafe { asdf_file_find(h.0, Some(is_hit)) };
1550 assert!(!found.is_null());
1551 let path = unsafe { crate::value_ffi::asdf_value_path(found) };
1552 assert_eq!(unsafe { CStr::from_ptr(path) }.to_str().unwrap(), "/z_top");
1553 unsafe { asdf_value_destroy(found) };
1554
1555 let deep = unsafe { asdf_file_find_ex(h.0, Some(is_hit), true, None, -1) };
1557 assert!(!deep.is_null());
1558 let path = unsafe { crate::value_ffi::asdf_value_path(deep) };
1559 assert_eq!(unsafe { CStr::from_ptr(path) }.to_str().unwrap(), "/a_nested/deep");
1560 unsafe { asdf_value_destroy(deep) };
1561 }
1562
1563 #[test]
1564 fn a_file_find_that_matches_nothing_returns_null() {
1565 let h = handle_with("a: 1\nb: 2\n");
1566 assert!(unsafe { asdf_file_find(h.0, Some(is_hit)) }.is_null());
1567 assert!(unsafe { asdf_file_find(core::ptr::null_mut(), Some(is_hit)) }.is_null());
1569 assert!(
1570 unsafe { asdf_file_find_ex(core::ptr::null_mut(), Some(is_hit), false, None, -1) }
1571 .is_null()
1572 );
1573 }
1574
1575 #[test]
1576 fn width_predicates_agree_with_the_getters() {
1577 let h = handle_with("small: 200\nbig: 70000\nnegative: -5\ntext: hello\n");
1578 let small = cpath("small");
1579 let big = cpath("big");
1580 let negative = cpath("negative");
1581 let text = cpath("text");
1582
1583 assert!(unsafe { asdf_is_uint8(h.0, small.as_ptr()) });
1585 assert!(!unsafe { asdf_is_int8(h.0, small.as_ptr()) });
1586 assert!(unsafe { asdf_is_int16(h.0, small.as_ptr()) });
1587 assert!(unsafe { asdf_is_int(h.0, small.as_ptr()) });
1588
1589 assert!(!unsafe { asdf_is_uint16(h.0, big.as_ptr()) });
1591 assert!(unsafe { asdf_is_uint32(h.0, big.as_ptr()) });
1592
1593 assert!(unsafe { asdf_is_int32(h.0, negative.as_ptr()) });
1595 assert!(!unsafe { asdf_is_uint32(h.0, negative.as_ptr()) });
1596
1597 assert!(!unsafe { asdf_is_int(h.0, text.as_ptr()) });
1598 assert!(unsafe { asdf_is_scalar(h.0, text.as_ptr()) });
1599
1600 let mut narrow: i8 = 0;
1602 assert_eq!(
1603 unsafe { asdf_get_int8(h.0, small.as_ptr(), &mut narrow) },
1604 AsdfValueErr::Overflow
1605 );
1606 let mut wide: u8 = 0;
1607 assert_eq!(unsafe { asdf_get_uint8(h.0, small.as_ptr(), &mut wide) }, AsdfValueErr::Ok);
1608 }
1609
1610 #[test]
1611 fn float_predicates_and_scalar_getters() {
1612 let h = handle_with("pi: 3.14\nn: 7\ntext: hi\n");
1613 let pi = cpath("pi");
1614 let n = cpath("n");
1615 let text = cpath("text");
1616
1617 assert!(unsafe { asdf_is_float(h.0, pi.as_ptr()) });
1618 assert!(unsafe { asdf_is_double(h.0, pi.as_ptr()) });
1619 assert!(!unsafe { asdf_is_float(h.0, n.as_ptr()) });
1620
1621 let mut out = core::ptr::null();
1624 let mut len = 0usize;
1625 assert_eq!(
1626 unsafe { asdf_get_scalar(h.0, pi.as_ptr(), &mut out, &mut len) },
1627 AsdfValueErr::Ok
1628 );
1629 assert_eq!(len, 4);
1630 assert_eq!(unsafe { CStr::from_ptr(out) }, c"3.14");
1631 assert_eq!(
1632 unsafe { asdf_get_string(h.0, pi.as_ptr(), &mut out, &mut len) },
1633 AsdfValueErr::TypeMismatch
1634 );
1635
1636 assert_eq!(
1637 unsafe { asdf_get_string(h.0, text.as_ptr(), &mut out, &mut len) },
1638 AsdfValueErr::Ok
1639 );
1640 assert_eq!(len, 2);
1641
1642 let mut zero_terminated = core::ptr::null();
1643 assert_eq!(
1644 unsafe { asdf_get_scalar0(h.0, n.as_ptr(), &mut zero_terminated) },
1645 AsdfValueErr::Ok
1646 );
1647 assert_eq!(unsafe { CStr::from_ptr(zero_terminated) }, c"7");
1648 }
1649
1650 #[test]
1651 fn container_getters_check_the_type() {
1652 let h = handle_with("m:\n k: v\nseq: [1, 2]\nscalar: 3\n");
1653 let m = cpath("m");
1654 let seq = cpath("seq");
1655 let scalar = cpath("scalar");
1656
1657 let mut mapping = core::ptr::null_mut();
1658 assert_eq!(unsafe { asdf_get_mapping(h.0, m.as_ptr(), &mut mapping) }, AsdfValueErr::Ok);
1659 assert!(!mapping.is_null());
1660 unsafe { asdf_value_destroy(mapping) };
1661
1662 let mut sequence = core::ptr::null_mut();
1663 assert_eq!(
1664 unsafe { asdf_get_sequence(h.0, seq.as_ptr(), &mut sequence) },
1665 AsdfValueErr::Ok
1666 );
1667 assert!(!sequence.is_null());
1668 unsafe { asdf_value_destroy(sequence) };
1669
1670 assert_eq!(
1672 unsafe { asdf_get_mapping(h.0, seq.as_ptr(), &mut mapping) },
1673 AsdfValueErr::TypeMismatch
1674 );
1675 assert_eq!(
1676 unsafe { asdf_get_sequence(h.0, scalar.as_ptr(), &mut sequence) },
1677 AsdfValueErr::TypeMismatch
1678 );
1679 let missing = cpath("absent");
1680 assert_eq!(
1681 unsafe { asdf_get_mapping(h.0, missing.as_ptr(), &mut mapping) },
1682 AsdfValueErr::NotFound
1683 );
1684 }
1685
1686 #[test]
1687 fn counted_string_setter_round_trips() {
1688 let file = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1689 let h = Handle(file);
1690 let path = cpath("label");
1691 let value = b"embedded";
1692 assert_eq!(
1693 unsafe {
1694 asdf_set_string(h.0, path.as_ptr(), value.as_ptr().cast::<c_char>(), value.len())
1695 },
1696 AsdfValueErr::Ok
1697 );
1698 let mut out = core::ptr::null();
1699 let mut len = 0usize;
1700 assert_eq!(
1701 unsafe { asdf_get_string(h.0, path.as_ptr(), &mut out, &mut len) },
1702 AsdfValueErr::Ok
1703 );
1704 assert_eq!(len, value.len());
1705 assert_eq!(unsafe { CStr::from_ptr(out) }, c"embedded");
1706 }
1707
1708 #[test]
1709 fn set_mapping_attaches_a_built_container() {
1710 let file = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1711 let h = Handle(file);
1712
1713 let mapping = unsafe { crate::value_ffi::asdf_mapping_create(h.0) };
1714 let inner = cpath("inner");
1715 assert_eq!(
1716 unsafe { crate::value_ffi::asdf_mapping_set_int32(mapping, inner.as_ptr(), 5) },
1717 AsdfValueErr::Ok
1718 );
1719
1720 let path = cpath("outer/nested");
1721 assert_eq!(unsafe { asdf_set_mapping(h.0, path.as_ptr(), mapping) }, AsdfValueErr::Ok);
1722 unsafe { asdf_value_destroy(mapping) };
1723
1724 let full = cpath("outer/nested/inner");
1726 let mut got: i32 = 0;
1727 assert_eq!(unsafe { asdf_get_int32(h.0, full.as_ptr(), &mut got) }, AsdfValueErr::Ok);
1728 assert_eq!(got, 5);
1729 }
1730
1731 #[test]
1732 fn opens_and_closes_a_memory_buffer() {
1733 let h = open();
1734 assert_eq!(unsafe { asdf_error_code(h.0) }, 0);
1735 assert!(unsafe { asdf_error(h.0) }.is_null());
1736 }
1737
1738 #[test]
1739 fn rejects_bad_arguments_without_crashing() {
1740 assert!(
1741 unsafe {
1742 asdf_open_file_ex(core::ptr::null(), core::ptr::null(), core::ptr::null_mut())
1743 }
1744 .is_null()
1745 );
1746 unsafe { asdf_close(core::ptr::null_mut()) };
1748 assert_eq!(unsafe { asdf_error_code(core::ptr::null_mut()) }, 0);
1749 }
1750
1751 #[test]
1755 fn opening_a_null_buffer_creates_a_writable_file() {
1756 let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1757 assert!(!f.is_null());
1758 let h = Handle(f);
1759
1760 let path = cpath("foo");
1761 assert_eq!(unsafe { asdf_set_int64(h.0, path.as_ptr(), 42) }, AsdfValueErr::Ok);
1762 }
1763
1764 fn sample_on_disk(name: &str) -> std::path::PathBuf {
1766 let dir = std::env::temp_dir().join(format!("asdf-file-ffi-{}", std::process::id()));
1767 std::fs::create_dir_all(&dir).unwrap();
1768 let path = dir.join(name);
1769 std::fs::write(&path, sample()).unwrap();
1770 path
1771 }
1772
1773 #[test]
1774 fn writing_to_a_read_only_file_is_refused() {
1775 let path = sample_on_disk("read-only.asdf");
1776 let name = CString::new(path.to_str().unwrap()).unwrap();
1777 let f = unsafe { asdf_open_file_ex(name.as_ptr(), c"r".as_ptr(), core::ptr::null_mut()) };
1778 assert!(!f.is_null());
1779 let h = Handle(f);
1780
1781 let key = cpath("foo");
1782 assert_eq!(unsafe { asdf_set_int64(h.0, key.as_ptr(), 1) }, AsdfValueErr::ReadOnly);
1783 }
1784
1785 #[test]
1789 fn a_memory_backed_file_is_writable() {
1790 let h = open();
1791 let key = cpath("foo");
1792 assert_eq!(unsafe { asdf_set_int64(h.0, key.as_ptr(), 1) }, AsdfValueErr::Ok);
1793
1794 let name = cpath("name");
1797 let mut out = core::ptr::null();
1798 assert_eq!(unsafe { asdf_get_string0(h.0, name.as_ptr(), &mut out) }, AsdfValueErr::Ok);
1799 let mut got: i64 = 0;
1800 assert_eq!(unsafe { asdf_get_int64(h.0, key.as_ptr(), &mut got) }, AsdfValueErr::Ok);
1801 assert_eq!(got, 1);
1802 }
1803
1804 #[test]
1805 fn open_modes_are_the_three_libasdf_accepts() {
1806 let path = sample_on_disk("modes.asdf");
1807 let name = CString::new(path.to_str().unwrap()).unwrap();
1808
1809 let f = unsafe { asdf_open_file_ex(name.as_ptr(), c"rw".as_ptr(), core::ptr::null_mut()) };
1811 assert!(!f.is_null());
1812 let h = Handle(f);
1813 let key = cpath("foo");
1814 assert_eq!(unsafe { asdf_set_int64(h.0, key.as_ptr(), 7) }, AsdfValueErr::Ok);
1815
1816 let w = unsafe { asdf_open_file_ex(name.as_ptr(), c"W".as_ptr(), core::ptr::null_mut()) };
1819 assert!(!w.is_null());
1820 let wh = Handle(w);
1821 assert_eq!(unsafe { asdf_block_count(wh.0) }, 0);
1822 assert_eq!(unsafe { asdf_set_int64(wh.0, key.as_ptr(), 1) }, AsdfValueErr::Ok);
1823
1824 for bad in [c"rb", c"a", c"r+", c""] {
1826 let bad_open =
1827 unsafe { asdf_open_file_ex(name.as_ptr(), bad.as_ptr(), core::ptr::null_mut()) };
1828 assert!(bad_open.is_null(), "{bad:?} should not be a valid mode");
1829 }
1830 }
1831
1832 #[test]
1833 fn a_written_file_reads_back() {
1834 let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1835 let h = Handle(f);
1836
1837 let name = cpath("name");
1838 let value = CString::new("Dennis Richie").unwrap();
1839 assert_eq!(
1840 unsafe { asdf_set_string0(h.0, name.as_ptr(), value.as_ptr()) },
1841 AsdfValueErr::Ok
1842 );
1843 let foo = cpath("foo");
1844 assert_eq!(unsafe { asdf_set_int64(h.0, foo.as_ptr(), 42) }, AsdfValueErr::Ok);
1845 let nested = cpath("powers/squares");
1847 assert_eq!(unsafe { asdf_set_uint64(h.0, nested.as_ptr(), 1764) }, AsdfValueErr::Ok);
1848
1849 let mut buf: *mut c_void = core::ptr::null_mut();
1850 let mut size: usize = 0;
1851 assert_eq!(unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) }, 0);
1852 assert!(!buf.is_null() && size > 0);
1853
1854 let reopened = unsafe { asdf_open_mem_ex(buf, size, core::ptr::null_mut()) };
1856 assert!(!reopened.is_null());
1857 let r = Handle(reopened);
1858
1859 let mut out: *const c_char = core::ptr::null();
1860 assert_eq!(unsafe { asdf_get_string0(r.0, name.as_ptr(), &mut out) }, AsdfValueErr::Ok);
1861 assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "Dennis Richie");
1862
1863 let mut v: i64 = 0;
1864 assert_eq!(unsafe { asdf_get_int64(r.0, foo.as_ptr(), &mut v) }, AsdfValueErr::Ok);
1865 assert_eq!(v, 42);
1866
1867 let mut u: u64 = 0;
1868 assert_eq!(unsafe { asdf_get_uint64(r.0, nested.as_ptr(), &mut u) }, AsdfValueErr::Ok);
1869 assert_eq!(u, 1764);
1870
1871 unsafe { libc::free(buf) };
1872 }
1873
1874 #[test]
1876 fn string_setters_preserve_stringness() {
1877 let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
1878 let h = Handle(f);
1879
1880 let key = cpath("version");
1881 let value = CString::new("42").unwrap();
1882 unsafe { asdf_set_string0(h.0, key.as_ptr(), value.as_ptr()) };
1883
1884 let mut buf: *mut c_void = core::ptr::null_mut();
1885 let mut size: usize = 0;
1886 unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) };
1887 let reopened = unsafe { asdf_open_mem_ex(buf, size, core::ptr::null_mut()) };
1888 let r = Handle(reopened);
1889
1890 let mut out: *const c_char = core::ptr::null();
1891 assert_eq!(
1892 unsafe { asdf_get_string0(r.0, key.as_ptr(), &mut out) },
1893 AsdfValueErr::Ok,
1894 "a quoted numeric string must read back as a string"
1895 );
1896 assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "42");
1897 unsafe { libc::free(buf) };
1898 }
1899
1900 #[test]
1901 fn a_missing_file_returns_null() {
1902 let name = cpath("/definitely/not/here.asdf");
1903 let mode = cpath("r");
1904 let f = unsafe { asdf_open_file_ex(name.as_ptr(), mode.as_ptr(), core::ptr::null_mut()) };
1905 assert!(f.is_null());
1906 }
1907
1908 #[test]
1909 fn reads_a_string() {
1910 let h = open();
1911 let mut out: *const c_char = core::ptr::null();
1912 let path = cpath("name");
1913 assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut out) }, AsdfValueErr::Ok);
1914 assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "Dennis Richie");
1915 }
1916
1917 #[test]
1918 fn reads_integers_at_every_width() {
1919 let h = open();
1920 let path = cpath("foo");
1921
1922 let mut v8: i8 = 0;
1923 assert_eq!(unsafe { asdf_get_int8(h.0, path.as_ptr(), &mut v8) }, AsdfValueErr::Ok);
1924 assert_eq!(v8, 42);
1925
1926 let mut v64: i64 = 0;
1927 assert_eq!(unsafe { asdf_get_int64(h.0, path.as_ptr(), &mut v64) }, AsdfValueErr::Ok);
1928 assert_eq!(v64, 42);
1929
1930 let mut u8v: u8 = 0;
1931 assert_eq!(unsafe { asdf_get_uint8(h.0, path.as_ptr(), &mut u8v) }, AsdfValueErr::Ok);
1932 assert_eq!(u8v, 42);
1933 }
1934
1935 #[test]
1936 fn too_small_a_type_overflows_rather_than_truncating() {
1937 let h = open();
1938 let path = cpath("big");
1939 let mut v: u8 = 0;
1940 assert_eq!(unsafe { asdf_get_uint8(h.0, path.as_ptr(), &mut v) }, AsdfValueErr::Overflow);
1941 let mut w: u64 = 0;
1943 assert_eq!(unsafe { asdf_get_uint64(h.0, path.as_ptr(), &mut w) }, AsdfValueErr::Ok);
1944 assert_eq!(w, 5_000_000_000);
1945 }
1946
1947 #[test]
1948 fn a_negative_value_does_not_read_as_unsigned() {
1949 let h = open();
1950 let path = cpath("neg");
1951 let mut v: u32 = 0;
1952 assert_eq!(unsafe { asdf_get_uint32(h.0, path.as_ptr(), &mut v) }, AsdfValueErr::Overflow);
1953 let mut s: i32 = 0;
1954 assert_eq!(unsafe { asdf_get_int32(h.0, path.as_ptr(), &mut s) }, AsdfValueErr::Ok);
1955 assert_eq!(s, -7);
1956 }
1957
1958 #[test]
1959 fn reads_floats_and_accepts_integers_as_doubles() {
1960 let h = open();
1961 let mut d: f64 = 0.0;
1962 let pi = cpath("pi");
1963 assert_eq!(unsafe { asdf_get_double(h.0, pi.as_ptr(), &mut d) }, AsdfValueErr::Ok);
1964 assert_eq!(d, 3.5);
1965
1966 let foo = cpath("foo");
1967 assert_eq!(unsafe { asdf_get_double(h.0, foo.as_ptr(), &mut d) }, AsdfValueErr::Ok);
1968 assert_eq!(d, 42.0);
1969 }
1970
1971 #[test]
1972 fn reads_booleans() {
1973 let h = open();
1974 let mut b = false;
1975 let path = cpath("yes_flag");
1976 assert_eq!(unsafe { asdf_get_bool(h.0, path.as_ptr(), &mut b) }, AsdfValueErr::Ok);
1977 assert!(b);
1978 }
1979
1980 #[test]
1981 fn a_quoted_number_is_a_string_not_an_integer() {
1982 let h = open();
1983 let path = cpath("quoted");
1984
1985 let mut v: i64 = 0;
1986 assert_eq!(
1987 unsafe { asdf_get_int64(h.0, path.as_ptr(), &mut v) },
1988 AsdfValueErr::TypeMismatch
1989 );
1990
1991 let mut s: *const c_char = core::ptr::null();
1992 assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut s) }, AsdfValueErr::Ok);
1993 assert_eq!(unsafe { CStr::from_ptr(s) }.to_str().unwrap(), "1");
1994 }
1995
1996 #[test]
1997 fn a_missing_path_is_not_found() {
1998 let h = open();
1999 let path = cpath("nope");
2000 let mut v: i64 = 0;
2001 assert_eq!(unsafe { asdf_get_int64(h.0, path.as_ptr(), &mut v) }, AsdfValueErr::NotFound);
2002 }
2003
2004 #[test]
2005 fn nested_and_indexed_paths_resolve() {
2006 let h = open();
2007 let mut out: *const c_char = core::ptr::null();
2008 let path = cpath("nested/inner");
2009 assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut out) }, AsdfValueErr::Ok);
2010 assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "deep");
2011
2012 let path = cpath("list/1");
2013 assert_eq!(unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut out) }, AsdfValueErr::Ok);
2014 assert_eq!(unsafe { CStr::from_ptr(out) }.to_str().unwrap(), "b");
2015 }
2016
2017 #[test]
2018 fn nulls_and_type_predicates() {
2019 let h = open();
2020 let nothing = cpath("nothing");
2021 assert!(unsafe { asdf_is_null(h.0, nothing.as_ptr()) });
2022
2023 let nested = cpath("nested");
2024 assert!(unsafe { asdf_is_mapping(h.0, nested.as_ptr()) });
2025 assert!(!unsafe { asdf_is_sequence(h.0, nested.as_ptr()) });
2026
2027 let list = cpath("list");
2028 assert!(unsafe { asdf_is_sequence(h.0, list.as_ptr()) });
2029
2030 let name = cpath("name");
2031 assert!(unsafe { asdf_is_string(h.0, name.as_ptr()) });
2032 }
2033
2034 #[test]
2035 fn value_handles_report_type_and_tag() {
2036 let h = open();
2037 let root = cpath("");
2038 let v = unsafe { asdf_get_value(h.0, root.as_ptr()) };
2039 assert!(!v.is_null());
2040 assert_eq!(unsafe { asdf_value_get_type(v) }, AsdfValueType::Mapping);
2041
2042 let tag = unsafe { asdf_value_tag(v) };
2043 assert!(!tag.is_null());
2044 assert_eq!(
2045 unsafe { CStr::from_ptr(tag) }.to_str().unwrap(),
2046 "tag:stsci.edu:asdf/core/asdf-1.1.0"
2047 );
2048 unsafe { asdf_value_destroy(v) };
2049 unsafe { asdf_value_destroy(core::ptr::null_mut()) };
2050 }
2051
2052 #[test]
2053 fn type_names_match_libasdf() {
2054 let name = |t| unsafe { CStr::from_ptr(asdf_value_type_string(t)) }.to_str().unwrap();
2055 assert_eq!(name(AsdfValueType::Uint8 as c_int), "uint8");
2056 assert_eq!(name(AsdfValueType::Mapping as c_int), "mapping");
2057 assert_eq!(name(AsdfValueType::Unknown as c_int), "<unknown>");
2058 assert_eq!(name(AsdfValueType::Extension as c_int), "<extension>");
2059 }
2060
2061 #[test]
2062 fn interned_strings_stay_valid_while_the_file_is_open() {
2063 let h = open();
2064 let mut first: *const c_char = core::ptr::null();
2065 let path = cpath("name");
2066 unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut first) };
2067
2068 for _ in 0..100 {
2071 let mut other: *const c_char = core::ptr::null();
2072 unsafe { asdf_get_string0(h.0, path.as_ptr(), &mut other) };
2073 }
2074 assert_eq!(unsafe { CStr::from_ptr(first) }.to_str().unwrap(), "Dennis Richie");
2075 }
2076
2077 #[test]
2078 fn null_out_pointers_are_accepted() {
2079 let h = open();
2080 let path = cpath("foo");
2081 assert_eq!(
2083 unsafe { asdf_get_int64(h.0, path.as_ptr(), core::ptr::null_mut()) },
2084 AsdfValueErr::Ok
2085 );
2086 }
2087
2088 #[test]
2089 fn block_count_is_reported() {
2090 let h = open();
2091 assert_eq!(unsafe { asdf_block_count(h.0) }, 0);
2092 assert_eq!(unsafe { asdf_block_count(core::ptr::null_mut()) }, 0);
2093 }
2094}