1use core::ffi::{CStr, c_char, c_int, c_void};
19use std::sync::Mutex;
20
21use crate::panic::guard;
22use crate::types::AsdfValueType;
23use crate::version_ffi::asdf_version_t;
24
25#[repr(C)]
27#[derive(Debug)]
28pub struct asdf_tag_t {
29 pub name: *const c_char,
31 pub version: *const asdf_version_t,
33}
34
35#[repr(C)]
40#[derive(Debug)]
41pub struct asdf_software_t {
42 pub name: *const c_char,
44 pub version: *const asdf_version_t,
46 pub author: *const c_char,
48 pub homepage: *const c_char,
50}
51
52pub type AsdfExtensionSerialize = Option<
54 unsafe extern "C" fn(
55 file: *mut crate::file_ffi::AsdfFile,
56 obj: *const c_void,
57 userdata: *const c_void,
58 ) -> *mut crate::file_ffi::AsdfValue,
59>;
60
61pub type AsdfExtensionDeserialize = Option<
63 unsafe extern "C" fn(
64 value: *mut crate::file_ffi::AsdfValue,
65 userdata: *const c_void,
66 out: *mut *mut c_void,
67 ) -> crate::types::AsdfValueErr,
68>;
69
70pub type AsdfExtensionCopy = Option<
72 unsafe extern "C" fn(
73 file: *mut crate::file_ffi::AsdfFile,
74 src: *const c_void,
75 dst: *mut c_void,
76 ) -> bool,
77>;
78
79pub type AsdfExtensionDeinit = Option<unsafe extern "C" fn(obj: *mut c_void)>;
81
82pub type AsdfExtensionMethod = Option<unsafe extern "C" fn()>;
84
85pub const ASDF_EXTENSION_VTAB_MAX_METHODS: usize = 8;
87pub const ASDF_EXTENSION_VTAB_METHODS: usize = 4;
89
90#[repr(C)]
96#[derive(Debug)]
97pub struct asdf_extension_vtab_t {
98 pub serialize: AsdfExtensionSerialize,
100 pub deserialize: AsdfExtensionDeserialize,
102 pub copy: AsdfExtensionCopy,
104 pub deinit: AsdfExtensionDeinit,
106 pub _reserved:
108 [AsdfExtensionMethod; ASDF_EXTENSION_VTAB_MAX_METHODS - ASDF_EXTENSION_VTAB_METHODS],
109}
110
111#[repr(C)]
113#[derive(Debug)]
114pub struct asdf_extension_t {
115 pub tags: *const *const c_char,
120 pub software: *mut asdf_software_t,
122 pub vtab: *const asdf_extension_vtab_t,
124 pub size: usize,
126 pub userdata: *mut c_void,
128}
129
130#[derive(Clone, Copy)]
136struct Registration {
137 extension: *const asdf_extension_t,
138}
139
140unsafe impl Send for Registration {}
144
145static REGISTRY: Mutex<Vec<Registration>> = Mutex::new(Vec::new());
150
151#[unsafe(no_mangle)]
161pub unsafe extern "C" fn asdf_extension_register(ext: *mut asdf_extension_t) {
162 guard("asdf_extension_register", (), || {
163 if ext.is_null() {
164 return;
165 }
166 let mut registry = REGISTRY.lock().unwrap_or_else(|e| e.into_inner());
167 if registry.iter().any(|r| core::ptr::eq(r.extension, ext)) {
170 return;
171 }
172 registry.push(Registration { extension: ext });
173 })
174}
175
176#[unsafe(no_mangle)]
188pub unsafe extern "C" fn asdf_extension_get(
189 file: *mut crate::file_ffi::AsdfFile,
190 tag: *const c_char,
191) -> *const asdf_extension_t {
192 guard("asdf_extension_get", core::ptr::null(), || extension_get(file, tag))
193}
194
195pub(crate) fn extension_get(
201 file: *mut crate::file_ffi::AsdfFile,
202 tag: *const c_char,
203) -> *const asdf_extension_t {
204 let _ = file;
205 if tag.is_null() {
206 return core::ptr::null();
207 }
208 let wanted = unsafe { crate::ffi::c_string_lossy(tag) }.unwrap_or_default();
209 let registry = REGISTRY.lock().unwrap_or_else(|e| e.into_inner());
210
211 for entry in registry.iter() {
212 let extension = unsafe { &*entry.extension };
214 if extension.tags.is_null() {
215 continue;
216 }
217 let mut index = 0isize;
218 loop {
219 let tag_ptr = unsafe { *extension.tags.offset(index) };
220 if tag_ptr.is_null() {
221 break;
222 }
223 let declared = unsafe { CStr::from_ptr(tag_ptr) }.to_string_lossy();
224 if tags_match(&declared, &wanted) {
225 return entry.extension;
226 }
227 index += 1;
228 }
229 }
230 core::ptr::null()
231}
232
233#[cfg(test)]
239pub(crate) fn registrations_of(ext: *const asdf_extension_t) -> usize {
240 REGISTRY
241 .lock()
242 .unwrap_or_else(|e| e.into_inner())
243 .iter()
244 .filter(|r| core::ptr::eq(r.extension, ext))
245 .count()
246}
247
248#[unsafe(no_mangle)]
258pub unsafe extern "C" fn asdf_tag_parse(tag: *const c_char) -> *mut asdf_tag_t {
259 use alloc::ffi::CString;
260
261 guard("asdf_tag_parse", core::ptr::null_mut(), || {
262 if tag.is_null() {
263 return core::ptr::null_mut();
264 }
265 let text = unsafe { crate::ffi::c_string_lossy(tag) }.unwrap_or_default();
266 let (name, version) = asdf_core::yaml::tag::split_tag_version(&text);
267
268 let Ok(name) = CString::new(name) else {
269 return core::ptr::null_mut();
270 };
271 let version_ptr = match version {
272 Some(v) => {
273 let Ok(v) = CString::new(v) else {
274 return core::ptr::null_mut();
275 };
276 let parsed = unsafe { crate::version_ffi::asdf_version_parse(v.as_ptr()) };
277 if parsed.is_null() {
278 return core::ptr::null_mut();
279 }
280 parsed.cast_const()
281 }
282 None => core::ptr::null(),
283 };
284
285 Box::into_raw(Box::new(asdf_tag_t {
286 name: name.into_raw().cast_const(),
287 version: version_ptr,
288 }))
289 })
290}
291
292#[unsafe(no_mangle)]
298pub unsafe extern "C" fn asdf_tag_destroy(tag: *mut asdf_tag_t) {
299 use alloc::ffi::CString;
300
301 guard("asdf_tag_destroy", (), || {
302 if tag.is_null() {
303 return;
304 }
305 let boxed = unsafe { Box::from_raw(tag) };
306 if !boxed.name.is_null() {
307 drop(unsafe { CString::from_raw(boxed.name.cast_mut()) });
308 }
309 if !boxed.version.is_null() {
310 unsafe { crate::version_ffi::asdf_version_destroy(boxed.version.cast_mut()) };
311 }
312 })
313}
314
315fn tags_match(left: &str, right: &str) -> bool {
323 fn bare(tag: &str) -> &str {
324 tag.strip_prefix("tag:").unwrap_or(tag)
325 }
326 bare(left) == bare(right)
327}
328
329#[unsafe(no_mangle)]
335pub unsafe extern "C" fn asdf_value_is_extension_type(
336 value: *mut crate::file_ffi::AsdfValue,
337 ext: *const asdf_extension_t,
338) -> bool {
339 guard("asdf_value_is_extension_type", false, || value_is_extension_type(value, ext))
340}
341
342pub(crate) fn value_is_extension_type(
348 value: *mut crate::file_ffi::AsdfValue,
349 ext: *const asdf_extension_t,
350) -> bool {
351 use crate::file_ffi::{value_document, value_node};
352
353 if ext.is_null() {
354 return false;
355 }
356 let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
357 return false;
358 };
359 let Some(tag) = doc.tag_of(node) else {
360 return false;
361 };
362 let full = tag.full();
363
364 let extension = unsafe { &*ext };
365 if extension.tags.is_null() {
366 return false;
367 }
368 let mut index = 0isize;
369 loop {
370 let tag_ptr = unsafe { *extension.tags.offset(index) };
371 if tag_ptr.is_null() {
372 return false;
373 }
374 if tags_match(&unsafe { CStr::from_ptr(tag_ptr) }.to_string_lossy(), &full) {
375 return true;
376 }
377 index += 1;
378 }
379}
380
381#[unsafe(no_mangle)]
386pub unsafe extern "C" fn asdf_is_extension_type(
387 file: *mut crate::file_ffi::AsdfFile,
388 path: *const c_char,
389 ext: *mut asdf_extension_t,
390) -> bool {
391 guard("asdf_is_extension_type", false, || {
392 let value = unsafe { crate::file_ffi::asdf_get_value(file, path) };
393 if value.is_null() {
394 return false;
395 }
396 let matched = value_is_extension_type(value, ext);
397 unsafe { crate::file_ffi::asdf_value_destroy(value) };
398 matched
399 })
400}
401
402#[unsafe(no_mangle)]
408pub unsafe extern "C" fn asdf_value_as_extension_type(
409 value: *mut crate::file_ffi::AsdfValue,
410 ext: *const asdf_extension_t,
411 out: *mut *mut c_void,
412) -> crate::types::AsdfValueErr {
413 use crate::types::AsdfValueErr;
414
415 guard("asdf_value_as_extension_type", AsdfValueErr::Unknown, || {
416 if ext.is_null() || out.is_null() {
417 return AsdfValueErr::Unknown;
418 }
419 if !value_is_extension_type(value, ext) {
420 return AsdfValueErr::TypeMismatch;
421 }
422 let extension = unsafe { &*ext };
423 if extension.vtab.is_null() {
424 return AsdfValueErr::Unknown;
425 }
426 let Some(deserialize) = (unsafe { &*extension.vtab }).deserialize else {
427 return AsdfValueErr::Unknown;
428 };
429 unsafe { deserialize(value, extension.userdata.cast_const(), out) }
432 })
433}
434
435#[unsafe(no_mangle)]
440pub unsafe extern "C" fn asdf_get_extension_type(
441 file: *mut crate::file_ffi::AsdfFile,
442 path: *const c_char,
443 ext: *const asdf_extension_t,
444 out: *mut *mut c_void,
445) -> crate::types::AsdfValueErr {
446 use crate::types::AsdfValueErr;
447
448 guard("asdf_get_extension_type", AsdfValueErr::Unknown, || {
449 let value = unsafe { crate::file_ffi::asdf_get_value(file, path) };
450 if value.is_null() {
451 return AsdfValueErr::NotFound;
452 }
453 let result = unsafe { asdf_value_as_extension_type(value, ext, out) };
454 unsafe { crate::file_ffi::asdf_value_destroy(value) };
455 result
456 })
457}
458
459#[repr(transparent)]
468#[derive(Debug)]
469pub struct Identity<T>(pub T);
470
471unsafe impl<T> Sync for Identity<T> {}
474
475#[unsafe(no_mangle)]
484pub static libasdf_version: Identity<asdf_version_t> = Identity(asdf_version_t {
485 version: c"0.2.0".as_ptr(),
486 major: 0,
487 minor: 2,
488 patch: 0,
489 extra: core::ptr::null(),
490});
491
492#[unsafe(no_mangle)]
498pub static libasdf_software: Identity<asdf_software_t> = Identity(asdf_software_t {
499 name: c"libasdf-rs".as_ptr(),
500 version: (&raw const libasdf_version).cast::<asdf_version_t>(),
501 author: c"The libasdf-rs Developers".as_ptr(),
502 homepage: c"https://github.com/cruzzil/asdf".as_ptr(),
503});
504
505#[unsafe(no_mangle)]
512pub unsafe extern "C" fn asdf_value_of_extension_type(
513 file: *mut crate::file_ffi::AsdfFile,
514 obj: *const c_void,
515 ext: *const asdf_extension_t,
516) -> *mut crate::file_ffi::AsdfValue {
517 guard("asdf_value_of_extension_type", core::ptr::null_mut(), || {
518 value_of_extension_type(file, obj, ext)
519 })
520}
521
522pub(crate) fn value_of_extension_type(
528 file: *mut crate::file_ffi::AsdfFile,
529 obj: *const c_void,
530 ext: *const asdf_extension_t,
531) -> *mut crate::file_ffi::AsdfValue {
532 if ext.is_null() {
533 return core::ptr::null_mut();
534 }
535 let extension = unsafe { &*ext };
536 if extension.vtab.is_null() {
537 return core::ptr::null_mut();
538 }
539 let Some(serialize) = (unsafe { &*extension.vtab }).serialize else {
540 return core::ptr::null_mut();
543 };
544
545 if extension.tags.is_null() {
549 return core::ptr::null_mut();
550 }
551 let first = unsafe { *extension.tags };
552 if first.is_null() {
553 return core::ptr::null_mut();
554 }
555 let tag = unsafe { CStr::from_ptr(first) }.to_string_lossy().into_owned();
556
557 let value = unsafe { serialize(file, obj, extension.userdata.cast_const()) };
558 if value.is_null() {
559 return value;
560 }
561
562 let (Some(owner), Some(node)) =
563 (crate::file_ffi::value_file(value), crate::file_ffi::value_node(value))
564 else {
565 return value;
566 };
567 let full = if tag.starts_with("tag:") { tag } else { format!("tag:{tag}") };
570 if let Some(doc) = unsafe { &mut *owner }.document_for_values() {
573 doc.node_mut(node).tag = Some(asdf_core::yaml::Tag::parse(&full));
574 }
575 value
576}
577
578#[unsafe(no_mangle)]
584pub unsafe extern "C" fn asdf_set_extension_type(
585 file: *mut crate::file_ffi::AsdfFile,
586 path: *const c_char,
587 obj: *const c_void,
588 ext: *const asdf_extension_t,
589) -> crate::types::AsdfValueErr {
590 use crate::types::AsdfValueErr;
591
592 guard("asdf_set_extension_type", AsdfValueErr::Unknown, || {
593 let value = value_of_extension_type(file, obj, ext);
594 if value.is_null() {
595 return AsdfValueErr::EmitFailure;
597 }
598 let result = unsafe { crate::file_ffi::set_value_at(file, path, value) };
599 unsafe { crate::file_ffi::asdf_value_destroy(value) };
600 result
601 })
602}
603
604fn is_equivalent_type(found: AsdfValueType, wanted: AsdfValueType) -> bool {
616 use AsdfValueType as T;
617 match wanted {
618 T::Uint64 => matches!(found, T::Uint64 | T::Uint32 | T::Uint16 | T::Uint8),
619 T::Uint32 => matches!(found, T::Uint32 | T::Uint16 | T::Uint8),
620 T::Uint16 => matches!(found, T::Uint16 | T::Uint8),
621 T::Uint8 => found == T::Uint8,
622 T::Int64 => matches!(
623 found,
624 T::Int64 | T::Uint32 | T::Int32 | T::Uint16 | T::Int16 | T::Uint8 | T::Int8
625 ),
626 T::Int32 => matches!(found, T::Int32 | T::Uint16 | T::Int16 | T::Uint8 | T::Int8),
627 T::Int16 => matches!(found, T::Int16 | T::Uint8 | T::Int8),
628 T::Int8 => found == T::Int8,
629 T::Double => matches!(
630 found,
631 T::Double
632 | T::Float
633 | T::Int64
634 | T::Int32
635 | T::Int16
636 | T::Int8
637 | T::Uint64
638 | T::Uint32
639 | T::Uint16
640 | T::Uint8
641 ),
642 other => found == other,
643 }
644}
645
646unsafe fn get_property(
652 mapping: *mut crate::value_ffi::AsdfMapping,
653 name: *const c_char,
654 value_type: c_int,
655 tag: *const c_char,
656 out: *mut c_void,
657) -> crate::types::AsdfValueErr {
658 use crate::types::AsdfValueErr;
659
660 let prop = unsafe { crate::value_ffi::asdf_mapping_get(mapping, name) };
661 if prop.is_null() {
662 return AsdfValueErr::NotFound;
663 }
664 let release = |value| unsafe { crate::file_ffi::asdf_value_destroy(value) };
665
666 let Some(wanted) = AsdfValueType::from_i32(value_type) else {
667 release(prop);
668 return AsdfValueErr::TypeMismatch;
669 };
670
671 if wanted == AsdfValueType::Extension && !tag.is_null() {
673 let file = crate::file_ffi::value_file(mapping).unwrap_or(core::ptr::null_mut());
674 let ext = extension_get(file, tag);
675 if ext.is_null() || !value_is_extension_type(prop, ext) {
676 release(prop);
677 return AsdfValueErr::TypeMismatch;
678 }
679 let err = unsafe { asdf_value_as_extension_type(prop, ext, out.cast()) };
680 release(prop);
681 return err;
682 }
683
684 if wanted != AsdfValueType::Unknown && wanted != AsdfValueType::Extension {
685 let found = unsafe { crate::file_ffi::asdf_value_get_type(prop) };
686 if !is_equivalent_type(found, wanted) {
687 release(prop);
688 return AsdfValueErr::TypeMismatch;
689 }
690 }
691
692 let err = unsafe { crate::value_ffi::asdf_value_as_type(prop, value_type, out) };
693
694 let out_aliases_prop = matches!(wanted, AsdfValueType::Mapping | AsdfValueType::Sequence)
701 && err == AsdfValueErr::Ok;
702 if !out_aliases_prop {
703 release(prop);
704 }
705 err
706}
707
708#[unsafe(no_mangle)]
713pub unsafe extern "C" fn asdf_get_required_property(
714 mapping: *mut crate::value_ffi::AsdfMapping,
715 name: *const c_char,
716 value_type: c_int,
717 tag: *const c_char,
718 out: *mut c_void,
719) -> crate::types::AsdfValueErr {
720 guard("asdf_get_required_property", crate::types::AsdfValueErr::Unknown, || unsafe {
721 get_property(mapping, name, value_type, tag, out)
722 })
723}
724
725#[unsafe(no_mangle)]
734pub unsafe extern "C" fn asdf_get_optional_property(
735 mapping: *mut crate::value_ffi::AsdfMapping,
736 name: *const c_char,
737 value_type: c_int,
738 tag: *const c_char,
739 out: *mut c_void,
740) -> crate::types::AsdfValueErr {
741 guard("asdf_get_optional_property", crate::types::AsdfValueErr::Unknown, || unsafe {
742 get_property(mapping, name, value_type, tag, out)
743 })
744}
745
746#[cfg(test)]
747mod tests {
748 use super::*;
749 use alloc::ffi::CString;
750
751 #[test]
752 fn parses_a_versioned_tag() {
753 let tag = CString::new("tag:stsci.edu:asdf/core/ndarray-1.1.0").unwrap();
754 let parsed = unsafe { asdf_tag_parse(tag.as_ptr()) };
755 assert!(!parsed.is_null());
756
757 let view = unsafe { &*parsed };
758 assert_eq!(
759 unsafe { CStr::from_ptr(view.name) }.to_str().unwrap(),
760 "tag:stsci.edu:asdf/core/ndarray"
761 );
762 assert!(!view.version.is_null());
763 let version = unsafe { &*view.version };
764 assert_eq!((version.major, version.minor, version.patch), (1, 1, 0));
765
766 unsafe { asdf_tag_destroy(parsed) };
767 }
768
769 #[test]
770 fn a_tag_without_a_version_has_a_null_version() {
771 let tag = CString::new("tag:example.com:plain").unwrap();
772 let parsed = unsafe { asdf_tag_parse(tag.as_ptr()) };
773 let view = unsafe { &*parsed };
774 assert_eq!(unsafe { CStr::from_ptr(view.name) }.to_str().unwrap(), "tag:example.com:plain");
775 assert!(view.version.is_null());
776 unsafe { asdf_tag_destroy(parsed) };
777 }
778
779 #[test]
780 fn tag_parsing_tolerates_null() {
781 assert!(unsafe { asdf_tag_parse(core::ptr::null()) }.is_null());
782 unsafe { asdf_tag_destroy(core::ptr::null_mut()) };
783 }
784
785 fn make_extension(tags: &[&str]) -> &'static mut asdf_extension_t {
795 let names: Vec<CString> = tags.iter().map(|t| CString::new(*t).unwrap()).collect();
796 let mut array: Vec<*const c_char> = names.iter().map(|n| n.as_ptr()).collect();
797 array.push(core::ptr::null());
798
799 let names: &'static [CString] = Vec::leak(names);
801 let _ = names;
802 let array: &'static [*const c_char] = Vec::leak(array);
803
804 Box::leak(Box::new(asdf_extension_t {
805 tags: array.as_ptr(),
806 software: core::ptr::null_mut(),
807 vtab: core::ptr::null(),
808 size: 0,
809 userdata: core::ptr::null_mut(),
810 }))
811 }
812
813 #[test]
814 fn registers_and_looks_up_by_tag() {
815 let ext = make_extension(&["tag:example.com:thing-1.0.0"]);
816 assert_eq!(registrations_of(ext), 0);
817 unsafe { asdf_extension_register(ext) };
818 assert_eq!(registrations_of(ext), 1);
819
820 let wanted = CString::new("tag:example.com:thing-1.0.0").unwrap();
821 let found = unsafe { asdf_extension_get(core::ptr::null_mut(), wanted.as_ptr()) };
822 assert!(core::ptr::eq(found, ext));
823
824 let missing = CString::new("tag:example.com:other-1.0.0").unwrap();
825 assert!(unsafe { asdf_extension_get(core::ptr::null_mut(), missing.as_ptr()) }.is_null());
826 }
827
828 #[test]
829 fn one_extension_can_serve_several_tag_versions() {
830 let ext = make_extension(&["tag:example.com:multi-1.1.0", "tag:example.com:multi-1.0.0"]);
833 unsafe { asdf_extension_register(ext) };
834
835 for tag in ["tag:example.com:multi-1.1.0", "tag:example.com:multi-1.0.0"] {
836 let c = CString::new(tag).unwrap();
837 let found = unsafe { asdf_extension_get(core::ptr::null_mut(), c.as_ptr()) };
838 assert!(core::ptr::eq(found, ext), "{tag}");
839 }
840 }
841
842 #[test]
843 fn registering_twice_keeps_one_entry() {
844 let ext = make_extension(&["tag:example.com:dup-1.0.0"]);
845 unsafe { asdf_extension_register(ext) };
846 unsafe { asdf_extension_register(ext) };
847 assert_eq!(registrations_of(ext), 1, "a repeated registration must not add a second entry");
848 }
849
850 #[test]
851 fn registration_tolerates_null() {
852 unsafe { asdf_extension_register(core::ptr::null_mut()) };
853 assert_eq!(registrations_of(core::ptr::null()), 0);
854 assert!(unsafe { asdf_extension_get(core::ptr::null_mut(), core::ptr::null()) }.is_null());
855 }
856
857 #[test]
858 fn value_type_matching_uses_the_full_tag() {
859 use crate::file_ffi::{asdf_close, asdf_get_value, asdf_open_mem_ex, asdf_value_destroy};
860
861 let mut buf = Vec::new();
862 buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
863 buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
864 buf.extend_from_slice(b"d: !core/ndarray-1.1.0\n source: 0\n...\n");
865
866 let file =
867 unsafe { asdf_open_mem_ex(buf.as_ptr().cast(), buf.len(), core::ptr::null_mut()) };
868 assert!(!file.is_null());
869
870 let path = CString::new("d").unwrap();
871 let value = unsafe { asdf_get_value(file, path.as_ptr()) };
872 assert!(!value.is_null());
873
874 let matching = make_extension(&["tag:stsci.edu:asdf/core/ndarray-1.1.0"]);
875 assert!(unsafe { asdf_value_is_extension_type(value, matching) });
876
877 let other = make_extension(&["tag:stsci.edu:asdf/core/ndarray-1.0.0"]);
880 assert!(!unsafe { asdf_value_is_extension_type(value, other) });
881
882 assert!(!unsafe { asdf_value_is_extension_type(value, core::ptr::null()) });
883
884 unsafe { asdf_value_destroy(value) };
885 unsafe { asdf_close(file) };
886 }
887
888 #[test]
889 fn deserializing_without_a_vtable_is_an_error_not_a_crash() {
890 use crate::types::AsdfValueErr;
891
892 let ext = make_extension(&["tag:example.com:novtab-1.0.0"]);
893 let mut out: *mut c_void = core::ptr::null_mut();
894 assert_eq!(
896 unsafe { asdf_value_as_extension_type(core::ptr::null_mut(), ext, &mut out) },
897 AsdfValueErr::TypeMismatch
898 );
899 }
900
901 #[test]
902 fn the_vtable_keeps_its_reserved_width() {
903 use core::mem::size_of;
906 assert_eq!(
907 size_of::<asdf_extension_vtab_t>(),
908 ASDF_EXTENSION_VTAB_MAX_METHODS * size_of::<AsdfExtensionMethod>()
909 );
910 }
911
912 #[test]
913 fn the_library_reports_its_own_version() {
914 assert_eq!(unsafe { CStr::from_ptr(libasdf_version.0.version) }.to_str().unwrap(), "0.2.0");
915 assert_eq!(
916 unsafe { CStr::from_ptr(libasdf_software.0.name) }.to_str().unwrap(),
917 "libasdf-rs"
918 );
919 assert!(core::ptr::eq(
922 libasdf_software.0.version,
923 (&raw const libasdf_version).cast::<asdf_version_t>()
924 ));
925 }
926 #[test]
937 fn a_container_property_is_usable_after_the_call() {
938 use crate::file_ffi::{AsdfFile, asdf_close, asdf_open_mem_ex};
939 use crate::types::{AsdfValueErr, AsdfValueType};
940
941 let doc = b"#ASDF 1.0.0\n#ASDF_STANDARD 1.5.0\n%YAML 1.1\n\
942--- !<tag:stsci.edu:asdf/core/asdf-1.1.0>\n\
943transform:\n inputs: [x, y]\n meta: {unit: deg}\n...\n";
944
945 let file: *mut AsdfFile =
946 unsafe { asdf_open_mem_ex(doc.as_ptr().cast(), doc.len(), core::ptr::null_mut()) };
947 assert!(!file.is_null());
948
949 let path = CString::new("transform").unwrap();
950 let transform = unsafe { crate::file_ffi::asdf_get_value(file, path.as_ptr()) };
951 assert!(!transform.is_null());
952
953 let mut map: *mut crate::value_ffi::AsdfMapping = core::ptr::null_mut();
954 assert_eq!(
955 unsafe { crate::value_ffi::asdf_value_as_mapping(transform, &mut map) },
956 AsdfValueErr::Ok
957 );
958
959 let key = CString::new("inputs").unwrap();
961 let mut seq: *mut crate::value_ffi::AsdfSequence = core::ptr::null_mut();
962 assert_eq!(
963 unsafe {
964 asdf_get_optional_property(
965 map,
966 key.as_ptr(),
967 AsdfValueType::Sequence as c_int,
968 core::ptr::null(),
969 (&raw mut seq).cast(),
970 )
971 },
972 AsdfValueErr::Ok
973 );
974 assert!(!seq.is_null());
975 assert_eq!(unsafe { crate::value_ffi::asdf_sequence_size(seq) }, 2);
976 unsafe { crate::file_ffi::asdf_value_destroy(seq) };
977
978 let key = CString::new("meta").unwrap();
980 let mut inner: *mut crate::value_ffi::AsdfMapping = core::ptr::null_mut();
981 assert_eq!(
982 unsafe {
983 asdf_get_optional_property(
984 map,
985 key.as_ptr(),
986 AsdfValueType::Mapping as c_int,
987 core::ptr::null(),
988 (&raw mut inner).cast(),
989 )
990 },
991 AsdfValueErr::Ok
992 );
993 assert!(!inner.is_null());
994 assert_eq!(unsafe { crate::value_ffi::asdf_mapping_size(inner) }, 1);
995 unsafe { crate::file_ffi::asdf_value_destroy(inner) };
996
997 unsafe { crate::file_ffi::asdf_value_destroy(transform) };
998 unsafe { asdf_close(file) };
999 }
1000
1001 #[test]
1007 fn an_absent_container_property_leaves_out_untouched() {
1008 use crate::file_ffi::{AsdfFile, asdf_close, asdf_open_mem_ex};
1009 use crate::types::{AsdfValueErr, AsdfValueType};
1010
1011 let doc = b"#ASDF 1.0.0\n#ASDF_STANDARD 1.5.0\n%YAML 1.1\n\
1012--- !<tag:stsci.edu:asdf/core/asdf-1.1.0>\n\
1013transform:\n name: shifty\n...\n";
1014
1015 let file: *mut AsdfFile =
1016 unsafe { asdf_open_mem_ex(doc.as_ptr().cast(), doc.len(), core::ptr::null_mut()) };
1017 let path = CString::new("transform").unwrap();
1018 let transform = unsafe { crate::file_ffi::asdf_get_value(file, path.as_ptr()) };
1019 let mut map: *mut crate::value_ffi::AsdfMapping = core::ptr::null_mut();
1020 unsafe { crate::value_ffi::asdf_value_as_mapping(transform, &mut map) };
1021
1022 let key = CString::new("inputs").unwrap();
1023 let mut seq: *mut crate::value_ffi::AsdfSequence = core::ptr::null_mut();
1024 let err = unsafe {
1025 asdf_get_optional_property(
1026 map,
1027 key.as_ptr(),
1028 AsdfValueType::Sequence as c_int,
1029 core::ptr::null(),
1030 (&raw mut seq).cast(),
1031 )
1032 };
1033 assert_eq!(err, AsdfValueErr::NotFound);
1034 assert!(seq.is_null(), "an absent property must not write to `out`");
1035
1036 unsafe { crate::file_ffi::asdf_value_destroy(transform) };
1037 unsafe { asdf_close(file) };
1038 }
1039}