Skip to main content

asdf/
value_ffi.rs

1//! `asdf/value.h`: working with values, mappings and sequences.
2//!
3//! # Iterator contract
4//!
5//! libasdf's iterators are unusual and the shape is part of the ABI:
6//!
7//! ```c
8//! asdf_mapping_iter_t *iter = asdf_mapping_iter_init(mapping);
9//! while (asdf_mapping_iter_next(&iter)) {
10//!     use(iter->key, iter->value);
11//! }
12//! asdf_mapping_iter_destroy(iter);
13//! ```
14//!
15//! `next` takes a *pointer to* the iterator pointer, and on reaching the end
16//! it destroys the iterator and sets the caller's pointer to `NULL` -- so the
17//! trailing `destroy` is a no-op in the normal case and only matters when the
18//! loop breaks early. Each step also frees the previous `value`, which the
19//! iterator owns.
20
21use crate::ffi::write_out;
22use crate::file_ffi::file_document_mut;
23use alloc::ffi::CString;
24use core::ffi::{CStr, c_char, c_int};
25
26use asdf_core::yaml::{NodeData, NodeId, Resolved, ScalarStyle, Schema, resolve};
27
28use crate::file_ffi::{AsdfFile, AsdfValue, value_document, value_file, value_node};
29use crate::panic::guard;
30use crate::types::{
31    AsdfValueErr, AsdfValueType, asdf_container_iter_t, asdf_mapping_iter_t, asdf_sequence_iter_t,
32};
33
34/// A mapping handle. In libasdf this is a value known to be a mapping, and
35/// the two are freely cast between, so they share a representation here too.
36pub type AsdfMapping = AsdfValue;
37
38/// A sequence handle. See [`AsdfMapping`].
39pub type AsdfSequence = AsdfValue;
40
41/// The resolution of a value's scalar, if it has one.
42fn resolved_of(value: *mut AsdfValue) -> Option<Resolved> {
43    let doc = value_document(value)?;
44    let node = value_node(value)?;
45    let resolved = doc.resolved(node);
46    let NodeData::Scalar { value: text, style } = &resolved.data else {
47        return None;
48    };
49    // An explicit YAML common-schema tag wins over inference.
50    if let Some(tag) = doc.tag_of(node)
51        && tag.is_yaml_builtin()
52        && let Some(r) = asdf_core::yaml::resolve_tagged(text, tag.suffix(), Schema::Libasdf)
53    {
54        return Some(r);
55    }
56    Some(resolve(text, *style, Schema::Libasdf))
57}
58
59/// Build a value handle for a node of the same file.
60pub(crate) fn make_value(file: *mut AsdfFile, node: NodeId) -> *mut AsdfValue {
61    Box::into_raw(Box::new(AsdfValue::new(file, node)))
62}
63
64// ---- Generic value accessors ----------------------------------------
65
66/// Duplicate a value handle.
67///
68/// The copy refers to the same node; it does not copy the value itself.
69///
70/// # Safety
71/// `value` must be null or a valid value handle. The result must be released
72/// with `asdf_value_destroy`.
73#[unsafe(no_mangle)]
74pub unsafe extern "C" fn asdf_value_copy(value: *mut AsdfValue) -> *mut AsdfValue {
75    guard("asdf_value_copy", core::ptr::null_mut(), || {
76        let (Some(file), Some(node)) = (value_file(value), value_node(value)) else {
77            return core::ptr::null_mut();
78        };
79        make_value(file, node)
80    })
81}
82
83/// The file a value belongs to.
84///
85/// # Safety
86/// `value` must be null or a valid value handle.
87#[unsafe(no_mangle)]
88pub unsafe extern "C" fn asdf_value_file(value: *mut AsdfValue) -> *mut AsdfFile {
89    guard("asdf_value_file", core::ptr::null_mut(), || {
90        value_file(value).unwrap_or(core::ptr::null_mut())
91    })
92}
93
94/// Whether a value is a mapping or a sequence.
95///
96/// # Safety
97/// `value` must be null or a valid value handle.
98#[unsafe(no_mangle)]
99pub unsafe extern "C" fn asdf_value_is_container(value: *mut AsdfValue) -> bool {
100    guard("asdf_value_is_container", false, || {
101        let (Some(doc), Some(node)) = (value_document(value), value_node(value)) else {
102            return false;
103        };
104        let resolved = doc.resolved(node);
105        resolved.is_mapping() || resolved.is_sequence()
106    })
107}
108
109/// The number of children of a container, or `-1` if it is not one.
110///
111/// # Safety
112/// `container` must be null or a valid value handle.
113#[unsafe(no_mangle)]
114pub unsafe extern "C" fn asdf_container_size(container: *mut AsdfValue) -> c_int {
115    guard("asdf_container_size", -1, || container_size(container))
116}
117
118/// Safe internal form of [`asdf_container_size`].
119///
120/// The exported entry point is `unsafe extern "C"`, so calling it from
121/// inside the crate would need an `unsafe` block at every site to assert a
122/// contract the crate itself is upholding. Callers use this instead.
123pub(crate) fn container_size(container: *mut AsdfValue) -> c_int {
124    let (Some(doc), Some(node)) = (value_document(container), value_node(container)) else {
125        return -1;
126    };
127    doc.container_len(node).and_then(|n| c_int::try_from(n).ok()).unwrap_or(-1)
128}
129
130/// Whether a value matches a given type.
131///
132/// # Safety
133/// `value` must be null or a valid value handle.
134#[unsafe(no_mangle)]
135pub unsafe extern "C" fn asdf_value_is_type(value: *mut AsdfValue, value_type: c_int) -> bool {
136    guard("asdf_value_is_type", false, || {
137        // Taken as an `int`: C may pass anything, and an out-of-range value
138        // in a Rust enum is undefined behaviour. See `AsdfValueType::from_i32`.
139        let Some(wanted) = AsdfValueType::from_i32(value_type) else {
140            return false;
141        };
142        match wanted {
143            // `Unknown` names no type, so nothing is of it.
144            AsdfValueType::Unknown => false,
145            // `Scalar` is the category, not a resolution: a string, a
146            // boolean and an integer are all scalars.
147            AsdfValueType::Scalar => value_is_scalar(value),
148            AsdfValueType::Mapping => value_is_mapping(value),
149            AsdfValueType::Sequence => value_is_sequence(value),
150            AsdfValueType::Bool => unsafe { asdf_value_is_bool(value) },
151            AsdfValueType::Int8 => unsafe { asdf_value_is_int8(value) },
152            AsdfValueType::Int16 => unsafe { asdf_value_is_int16(value) },
153            AsdfValueType::Int32 => unsafe { asdf_value_is_int32(value) },
154            AsdfValueType::Int64 => unsafe { asdf_value_is_int64(value) },
155            AsdfValueType::Uint8 => unsafe { asdf_value_is_uint8(value) },
156            AsdfValueType::Uint16 => unsafe { asdf_value_is_uint16(value) },
157            AsdfValueType::Uint32 => unsafe { asdf_value_is_uint32(value) },
158            AsdfValueType::Uint64 => unsafe { asdf_value_is_uint64(value) },
159            other => {
160                let actual = unsafe { crate::file_ffi::asdf_value_get_type(value) };
161                actual == other
162            }
163        }
164    })
165}
166
167// ---- Mappings --------------------------------------------------------
168
169/// Whether a value is a mapping.
170///
171/// # Safety
172/// `value` must be null or a valid value handle.
173#[unsafe(no_mangle)]
174pub unsafe extern "C" fn asdf_value_is_mapping(value: *mut AsdfValue) -> bool {
175    guard("asdf_value_is_mapping", false, || value_is_mapping(value))
176}
177
178/// Safe internal form of [`asdf_value_is_mapping`].
179///
180/// The exported entry point is `unsafe extern "C"`, so calling it from
181/// inside the crate would need an `unsafe` block at every site to assert a
182/// contract the crate itself is upholding. Callers use this instead.
183pub(crate) fn value_is_mapping(value: *mut AsdfValue) -> bool {
184    value_document(value)
185        .zip(value_node(value))
186        .is_some_and(|(doc, node)| doc.resolved(node).is_mapping())
187}
188
189/// View a value as a mapping.
190///
191/// # Safety
192/// `value` must be a valid value handle and `out` writable or null.
193#[unsafe(no_mangle)]
194pub unsafe extern "C" fn asdf_value_as_mapping(
195    value: *mut AsdfValue,
196    out: *mut *mut AsdfMapping,
197) -> AsdfValueErr {
198    guard("asdf_value_as_mapping", AsdfValueErr::Unknown, || {
199        if !value_is_mapping(value) {
200            return AsdfValueErr::TypeMismatch;
201        }
202        if !out.is_null() {
203            unsafe { write_out(out, value) };
204        }
205        AsdfValueErr::Ok
206    })
207}
208
209/// The number of entries in a mapping, or `-1` if it is not one.
210///
211/// # Safety
212/// `mapping` must be null or a valid handle.
213#[unsafe(no_mangle)]
214pub unsafe extern "C" fn asdf_mapping_size(mapping: *mut AsdfMapping) -> c_int {
215    guard("asdf_mapping_size", -1, || {
216        if !value_is_mapping(mapping) {
217            return -1;
218        }
219        container_size(mapping)
220    })
221}
222
223/// Look up a mapping entry by key.
224///
225/// # Safety
226/// `mapping` must be a valid handle and `key` a valid NUL-terminated string.
227/// The result must be released with `asdf_value_destroy`.
228#[unsafe(no_mangle)]
229pub unsafe extern "C" fn asdf_mapping_get(
230    mapping: *mut AsdfMapping,
231    key: *const c_char,
232) -> *mut AsdfValue {
233    guard("asdf_mapping_get", core::ptr::null_mut(), || {
234        if key.is_null() {
235            return core::ptr::null_mut();
236        }
237        let (Some(doc), Some(node), Some(file)) =
238            (value_document(mapping), value_node(mapping), value_file(mapping))
239        else {
240            return core::ptr::null_mut();
241        };
242        let key = unsafe { CStr::from_ptr(key) }.to_string_lossy().into_owned();
243        match doc.mapping_get(node, &key) {
244            Some(found) => make_value(file, found),
245            None => core::ptr::null_mut(),
246        }
247    })
248}
249
250/// A mapping iterator, in file order or reversed.
251///
252/// `repr(C)` is load-bearing, not decoration: C casts a
253/// `*mut asdf_mapping_iter_t` to and from this, so `public` must genuinely
254/// sit at offset 0. Without it Rust may reorder the fields and the cast
255/// reads whatever happens to be first.
256#[repr(C)]
257struct MappingIter {
258    /// The public head, which C casts to. Must stay first.
259    public: asdf_mapping_iter_t,
260    file: *mut AsdfFile,
261    entries: Vec<(Option<String>, NodeId)>,
262    position: usize,
263    /// The key string handed out for the current entry, kept alive for the
264    /// duration of the step.
265    current_key: Option<CString>,
266    /// The value handle handed out for the current entry, which the iterator
267    /// owns and frees on the next step.
268    current_value: *mut AsdfValue,
269}
270
271fn mapping_iter_init(mapping: *mut AsdfMapping, reverse: bool) -> *mut asdf_mapping_iter_t {
272    let (Some(doc), Some(node), Some(file)) =
273        (value_document(mapping), value_node(mapping), value_file(mapping))
274    else {
275        return core::ptr::null_mut();
276    };
277    let Some(entries) = doc.mapping_entries(node) else {
278        return core::ptr::null_mut();
279    };
280
281    let mut collected: Vec<(Option<String>, NodeId)> = entries
282        .iter()
283        .map(|entry| {
284            // A non-scalar key is reported as NULL, as libasdf does: ASDF
285            // does not allow them, but the value is still yielded.
286            let key = doc.resolved(entry.key).as_str().map(str::to_string);
287            (key, entry.value)
288        })
289        .collect();
290    if reverse {
291        collected.reverse();
292    }
293
294    let iter = Box::new(MappingIter {
295        public: asdf_mapping_iter_t { key: core::ptr::null(), value: core::ptr::null_mut() },
296        file,
297        entries: collected,
298        position: 0,
299        current_key: None,
300        current_value: core::ptr::null_mut(),
301    });
302    // The public head is the first field, so the pointers are interchangeable.
303    Box::into_raw(iter).cast::<asdf_mapping_iter_t>()
304}
305
306/// Start iterating a mapping in document order.
307///
308/// # Safety
309/// `mapping` must be null or a valid handle.
310#[unsafe(no_mangle)]
311pub unsafe extern "C" fn asdf_mapping_iter_init(
312    mapping: *mut AsdfMapping,
313) -> *mut asdf_mapping_iter_t {
314    guard("asdf_mapping_iter_init", core::ptr::null_mut(), || mapping_iter_init(mapping, false))
315}
316
317/// Start iterating a mapping in reverse.
318///
319/// # Safety
320/// `mapping` must be null or a valid handle.
321#[unsafe(no_mangle)]
322pub unsafe extern "C" fn asdf_mapping_reverse_iter_init(
323    mapping: *mut AsdfMapping,
324) -> *mut asdf_mapping_iter_t {
325    guard("asdf_mapping_reverse_iter_init", core::ptr::null_mut(), || {
326        mapping_iter_init(mapping, true)
327    })
328}
329
330/// Advance a mapping iterator.
331///
332/// Returns `false` at the end, having destroyed the iterator and set
333/// `*iter_ptr` to `NULL` -- so a `while` loop over this needs no cleanup of
334/// its own, and the trailing `destroy` only matters on an early break.
335///
336/// # Safety
337/// `iter_ptr` must be null or point to an iterator obtained from one of the
338/// init functions.
339#[unsafe(no_mangle)]
340pub unsafe extern "C" fn asdf_mapping_iter_next(iter_ptr: *mut *mut asdf_mapping_iter_t) -> bool {
341    guard("asdf_mapping_iter_next", false, || {
342        if iter_ptr.is_null() {
343            return false;
344        }
345        let raw = unsafe { *iter_ptr };
346        if raw.is_null() {
347            return false;
348        }
349        let iter = unsafe { &mut *raw.cast::<MappingIter>() };
350
351        // Each step releases the handle the previous step handed out.
352        if !iter.current_value.is_null() {
353            drop(unsafe { Box::from_raw(iter.current_value) });
354            iter.current_value = core::ptr::null_mut();
355        }
356
357        if iter.position >= iter.entries.len() {
358            mapping_iter_destroy(raw);
359            unsafe { write_out(iter_ptr, core::ptr::null_mut()) };
360            return false;
361        }
362
363        let (key, node) = iter.entries[iter.position].clone();
364        iter.position += 1;
365
366        iter.current_key = key.and_then(|k| CString::new(k).ok());
367        iter.public.key = iter.current_key.as_ref().map_or(core::ptr::null(), |k| k.as_ptr());
368
369        iter.current_value = make_value(iter.file, node);
370        iter.public.value = iter.current_value.cast();
371        true
372    })
373}
374
375/// Release a mapping iterator.
376///
377/// # Safety
378/// `iter` must be null or an iterator that has not already been destroyed.
379#[unsafe(no_mangle)]
380pub unsafe extern "C" fn asdf_mapping_iter_destroy(iter: *mut asdf_mapping_iter_t) {
381    guard("asdf_mapping_iter_destroy", (), || mapping_iter_destroy(iter))
382}
383
384/// Safe internal form of [`asdf_mapping_iter_destroy`].
385///
386/// The exported entry point is `unsafe extern "C"`, so calling it from
387/// inside the crate would need an `unsafe` block at every site to assert a
388/// contract the crate itself is upholding. Callers use this instead.
389pub(crate) fn mapping_iter_destroy(iter: *mut asdf_mapping_iter_t) {
390    if iter.is_null() {
391        return;
392    }
393    let mut boxed = unsafe { Box::from_raw(iter.cast::<MappingIter>()) };
394    if !boxed.current_value.is_null() {
395        drop(unsafe { Box::from_raw(boxed.current_value) });
396        boxed.current_value = core::ptr::null_mut();
397    }
398}
399
400// ---- Sequences -------------------------------------------------------
401
402/// Whether a value is a sequence.
403///
404/// # Safety
405/// `value` must be null or a valid value handle.
406#[unsafe(no_mangle)]
407pub unsafe extern "C" fn asdf_value_is_sequence(value: *mut AsdfValue) -> bool {
408    guard("asdf_value_is_sequence", false, || value_is_sequence(value))
409}
410
411/// Safe internal form of [`asdf_value_is_sequence`].
412///
413/// The exported entry point is `unsafe extern "C"`, so calling it from
414/// inside the crate would need an `unsafe` block at every site to assert a
415/// contract the crate itself is upholding. Callers use this instead.
416pub(crate) fn value_is_sequence(value: *mut AsdfValue) -> bool {
417    value_document(value)
418        .zip(value_node(value))
419        .is_some_and(|(doc, node)| doc.resolved(node).is_sequence())
420}
421
422/// View a value as a sequence.
423///
424/// # Safety
425/// `value` must be a valid value handle and `out` writable or null.
426#[unsafe(no_mangle)]
427pub unsafe extern "C" fn asdf_value_as_sequence(
428    value: *mut AsdfValue,
429    out: *mut *mut AsdfSequence,
430) -> AsdfValueErr {
431    guard("asdf_value_as_sequence", AsdfValueErr::Unknown, || {
432        if !value_is_sequence(value) {
433            return AsdfValueErr::TypeMismatch;
434        }
435        if !out.is_null() {
436            unsafe { write_out(out, value) };
437        }
438        AsdfValueErr::Ok
439    })
440}
441
442/// The number of items in a sequence, or `-1` if it is not one.
443///
444/// # Safety
445/// `sequence` must be null or a valid handle.
446#[unsafe(no_mangle)]
447pub unsafe extern "C" fn asdf_sequence_size(sequence: *mut AsdfSequence) -> c_int {
448    guard("asdf_sequence_size", -1, || {
449        if !value_is_sequence(sequence) {
450            return -1;
451        }
452        container_size(sequence)
453    })
454}
455
456/// Index into a sequence. Negative indices count from the end.
457///
458/// # Safety
459/// `sequence` must be a valid handle. The result must be released with
460/// `asdf_value_destroy`.
461#[unsafe(no_mangle)]
462pub unsafe extern "C" fn asdf_sequence_get(
463    sequence: *mut AsdfSequence,
464    index: c_int,
465) -> *mut AsdfValue {
466    guard("asdf_sequence_get", core::ptr::null_mut(), || {
467        let (Some(doc), Some(node), Some(file)) =
468            (value_document(sequence), value_node(sequence), value_file(sequence))
469        else {
470            return core::ptr::null_mut();
471        };
472        match doc.sequence_get(node, i64::from(index)) {
473            Some(found) => make_value(file, found),
474            None => core::ptr::null_mut(),
475        }
476    })
477}
478
479/// A sequence iterator. See [`MappingIter`] on why this is `repr(C)`.
480#[repr(C)]
481struct SequenceIter {
482    /// The public head, which C casts to. Must stay first.
483    public: asdf_sequence_iter_t,
484    file: *mut AsdfFile,
485    items: Vec<NodeId>,
486    position: usize,
487    /// Index reported for the current item, which for a reversed iterator
488    /// still counts from the sequence's start.
489    indices: Vec<c_int>,
490    current_value: *mut AsdfValue,
491}
492
493fn sequence_iter_init(sequence: *mut AsdfSequence, reverse: bool) -> *mut asdf_sequence_iter_t {
494    let (Some(doc), Some(node), Some(file)) =
495        (value_document(sequence), value_node(sequence), value_file(sequence))
496    else {
497        return core::ptr::null_mut();
498    };
499    let Some(items) = doc.sequence_items(node) else {
500        return core::ptr::null_mut();
501    };
502
503    let mut items = items.to_vec();
504    let mut indices: Vec<c_int> =
505        (0..items.len()).map(|i| c_int::try_from(i).unwrap_or(c_int::MAX)).collect();
506    if reverse {
507        items.reverse();
508        indices.reverse();
509    }
510
511    let iter = Box::new(SequenceIter {
512        public: asdf_sequence_iter_t { index: -1, value: core::ptr::null_mut() },
513        file,
514        items,
515        position: 0,
516        indices,
517        current_value: core::ptr::null_mut(),
518    });
519    Box::into_raw(iter).cast::<asdf_sequence_iter_t>()
520}
521
522/// Start iterating a sequence.
523///
524/// # Safety
525/// `sequence` must be null or a valid handle.
526#[unsafe(no_mangle)]
527pub unsafe extern "C" fn asdf_sequence_iter_init(
528    sequence: *mut AsdfSequence,
529) -> *mut asdf_sequence_iter_t {
530    guard("asdf_sequence_iter_init", core::ptr::null_mut(), || sequence_iter_init(sequence, false))
531}
532
533/// Start iterating a sequence in reverse.
534///
535/// # Safety
536/// `sequence` must be null or a valid handle.
537#[unsafe(no_mangle)]
538pub unsafe extern "C" fn asdf_sequence_reverse_iter_init(
539    sequence: *mut AsdfSequence,
540) -> *mut asdf_sequence_iter_t {
541    guard("asdf_sequence_reverse_iter_init", core::ptr::null_mut(), || {
542        sequence_iter_init(sequence, true)
543    })
544}
545
546/// Advance a sequence iterator. See [`asdf_mapping_iter_next`] for the
547/// contract, which is the same.
548///
549/// # Safety
550/// `iter_ptr` must be null or point to an iterator from one of the init
551/// functions.
552#[unsafe(no_mangle)]
553pub unsafe extern "C" fn asdf_sequence_iter_next(iter_ptr: *mut *mut asdf_sequence_iter_t) -> bool {
554    guard("asdf_sequence_iter_next", false, || {
555        if iter_ptr.is_null() {
556            return false;
557        }
558        let raw = unsafe { *iter_ptr };
559        if raw.is_null() {
560            return false;
561        }
562        let iter = unsafe { &mut *raw.cast::<SequenceIter>() };
563
564        if !iter.current_value.is_null() {
565            drop(unsafe { Box::from_raw(iter.current_value) });
566            iter.current_value = core::ptr::null_mut();
567        }
568
569        if iter.position >= iter.items.len() {
570            sequence_iter_destroy(raw);
571            unsafe { write_out(iter_ptr, core::ptr::null_mut()) };
572            return false;
573        }
574
575        let node = iter.items[iter.position];
576        iter.public.index = iter.indices[iter.position];
577        iter.position += 1;
578
579        iter.current_value = make_value(iter.file, node);
580        iter.public.value = iter.current_value.cast();
581        true
582    })
583}
584
585/// Release a sequence iterator.
586///
587/// # Safety
588/// `iter` must be null or an iterator that has not already been destroyed.
589#[unsafe(no_mangle)]
590pub unsafe extern "C" fn asdf_sequence_iter_destroy(iter: *mut asdf_sequence_iter_t) {
591    guard("asdf_sequence_iter_destroy", (), || sequence_iter_destroy(iter))
592}
593
594/// Safe internal form of [`asdf_sequence_iter_destroy`].
595///
596/// The exported entry point is `unsafe extern "C"`, so calling it from
597/// inside the crate would need an `unsafe` block at every site to assert a
598/// contract the crate itself is upholding. Callers use this instead.
599pub(crate) fn sequence_iter_destroy(iter: *mut asdf_sequence_iter_t) {
600    if iter.is_null() {
601        return;
602    }
603    let mut boxed = unsafe { Box::from_raw(iter.cast::<SequenceIter>()) };
604    if !boxed.current_value.is_null() {
605        drop(unsafe { Box::from_raw(boxed.current_value) });
606        boxed.current_value = core::ptr::null_mut();
607    }
608}
609
610// ---- Typed accessors on a value --------------------------------------
611
612/// Generate `asdf_value_is_<type>` and `asdf_value_as_<type>` for an integer.
613macro_rules! value_int_accessors {
614    ($is:ident, $as:ident, $ty:ty, $variant:ident) => {
615        /// Whether the value is an integer that this type can hold.
616        ///
617        /// Not "whose inferred type is exactly this": an `int8` of `-127`
618        /// *is* an `int16`, and a caller asking whether it can read one is
619        /// asking whether the value fits, not how it was spelled.
620        ///
621        /// # Safety
622        /// `value` must be null or a valid value handle.
623        #[unsafe(no_mangle)]
624        pub unsafe extern "C" fn $is(value: *mut AsdfValue) -> bool {
625            guard(stringify!($is), false, || match resolved_of(value) {
626                Some(Resolved::Uint(v, _)) => <$ty>::try_from(v).is_ok(),
627                Some(Resolved::Int(v, _)) => <$ty>::try_from(v).is_ok(),
628                _ => false,
629            })
630        }
631
632        /// Read the value as this type.
633        ///
634        /// A value too large for the type is still written, truncated to the
635        /// type's width as a C cast would, *and* reported as an overflow --
636        /// the caller decides whether the truncation is acceptable. Only a
637        /// value that is not an integer at all leaves `out` untouched.
638        ///
639        /// # Safety
640        /// `value` must be null or a valid value handle; `out` writable or null.
641        #[unsafe(no_mangle)]
642        pub unsafe extern "C" fn $as(value: *mut AsdfValue, out: *mut $ty) -> AsdfValueErr {
643            guard(stringify!($as), AsdfValueErr::Unknown, || {
644                let Some(resolved) = resolved_of(value) else {
645                    return AsdfValueErr::TypeMismatch;
646                };
647                let (truncated, fits): ($ty, bool) = match resolved {
648                    Resolved::Uint(v, _) => (v as $ty, <$ty>::try_from(v).is_ok()),
649                    Resolved::Int(v, _) => (v as $ty, <$ty>::try_from(v).is_ok()),
650                    // The text is an integer, just not one any type holds,
651                    // so this is an overflow rather than a type mismatch.
652                    Resolved::IntOverflow => return AsdfValueErr::Overflow,
653                    _ => return AsdfValueErr::TypeMismatch,
654                };
655                if !out.is_null() {
656                    unsafe { write_out(out, truncated) };
657                }
658                if fits { AsdfValueErr::Ok } else { AsdfValueErr::Overflow }
659            })
660        }
661    };
662}
663
664value_int_accessors!(asdf_value_is_int8, asdf_value_as_int8, i8, Int8);
665value_int_accessors!(asdf_value_is_int16, asdf_value_as_int16, i16, Int16);
666value_int_accessors!(asdf_value_is_int32, asdf_value_as_int32, i32, Int32);
667value_int_accessors!(asdf_value_is_int64, asdf_value_as_int64, i64, Int64);
668value_int_accessors!(asdf_value_is_uint8, asdf_value_as_uint8, u8, Uint8);
669value_int_accessors!(asdf_value_is_uint16, asdf_value_as_uint16, u16, Uint16);
670value_int_accessors!(asdf_value_is_uint32, asdf_value_as_uint32, u32, Uint32);
671value_int_accessors!(asdf_value_is_uint64, asdf_value_as_uint64, u64, Uint64);
672
673/// Whether the value is any integer type.
674///
675/// # Safety
676/// `value` must be null or a valid value handle.
677#[unsafe(no_mangle)]
678pub unsafe extern "C" fn asdf_value_is_int(value: *mut AsdfValue) -> bool {
679    guard("asdf_value_is_int", false, || {
680        matches!(resolved_of(value), Some(Resolved::Int(..) | Resolved::Uint(..)))
681    })
682}
683
684/// Read the value as a `double`.
685///
686/// # Safety
687/// `value` must be null or a valid value handle; `out` writable or null.
688#[unsafe(no_mangle)]
689pub unsafe extern "C" fn asdf_value_as_double(
690    value: *mut AsdfValue,
691    out: *mut f64,
692) -> AsdfValueErr {
693    guard("asdf_value_as_double", AsdfValueErr::Unknown, || value_as_double(value, out))
694}
695
696/// Safe internal form of [`asdf_value_as_double`].
697///
698/// The exported entry point is `unsafe extern "C"`, so calling it from
699/// inside the crate would need an `unsafe` block at every site to assert a
700/// contract the crate itself is upholding. Callers use this instead.
701pub(crate) fn value_as_double(value: *mut AsdfValue, out: *mut f64) -> AsdfValueErr {
702    let converted = match resolved_of(value) {
703        Some(Resolved::Double(d)) => d,
704        Some(Resolved::Uint(v, _)) => v as f64,
705        Some(Resolved::Int(v, _)) => v as f64,
706        _ => return AsdfValueErr::TypeMismatch,
707    };
708    if !out.is_null() {
709        unsafe { write_out(out, converted) };
710    }
711    AsdfValueErr::Ok
712}
713
714/// Read the value as a `float`.
715///
716/// # Safety
717/// See [`asdf_value_as_double`].
718#[unsafe(no_mangle)]
719pub unsafe extern "C" fn asdf_value_as_float(value: *mut AsdfValue, out: *mut f32) -> AsdfValueErr {
720    guard("asdf_value_as_float", AsdfValueErr::Unknown, || {
721        let mut wide = 0f64;
722        let err = value_as_double(value, &mut wide);
723        if err != AsdfValueErr::Ok {
724            return err;
725        }
726        let narrow = wide as f32;
727        if !out.is_null() {
728            unsafe { write_out(out, narrow) };
729        }
730        // A finite `double` with no `float` becomes an infinity. The value
731        // is still handed over -- the caller may not care -- but the loss is
732        // reported. A value that was already infinite loses nothing.
733        if wide.is_finite() && narrow.is_infinite() {
734            return AsdfValueErr::Overflow;
735        }
736        AsdfValueErr::Ok
737    })
738}
739
740/// Whether the value is a `double`.
741///
742/// # Safety
743/// `value` must be null or a valid value handle.
744#[unsafe(no_mangle)]
745pub unsafe extern "C" fn asdf_value_is_double(value: *mut AsdfValue) -> bool {
746    guard("asdf_value_is_double", false, || matches!(resolved_of(value), Some(Resolved::Double(_))))
747}
748
749/// Whether the value is a float. libasdf resolves every float as a double,
750/// so this matches the same values.
751///
752/// # Safety
753/// `value` must be null or a valid value handle.
754#[unsafe(no_mangle)]
755pub unsafe extern "C" fn asdf_value_is_float(value: *mut AsdfValue) -> bool {
756    guard("asdf_value_is_float", false, || unsafe { asdf_value_is_double(value) })
757}
758
759/// Whether the value is a boolean.
760///
761/// # Safety
762/// `value` must be null or a valid value handle.
763#[unsafe(no_mangle)]
764pub unsafe extern "C" fn asdf_value_is_bool(value: *mut AsdfValue) -> bool {
765    guard("asdf_value_is_bool", false, || bool_of(value).is_some())
766}
767
768/// A value's boolean reading, if it has one.
769///
770/// libasdf's boolean parser accepts `0` and `1`, but tries integers first,
771/// so a bare `1` *resolves* as `uint8` while still reading as `true`. Both
772/// halves are the contract: the reported type is the integer one, and asking
773/// for a boolean succeeds.
774fn bool_of(value: *mut AsdfValue) -> Option<bool> {
775    match resolved_of(value)? {
776        Resolved::Bool(v) => Some(v),
777        Resolved::Uint(0, _) => Some(false),
778        Resolved::Uint(1, _) => Some(true),
779        _ => None,
780    }
781}
782
783/// Read the value as a boolean.
784///
785/// As libasdf documents, the integers 0 and 1 are accepted here even though
786/// they resolve as integers, because integers are resolved before booleans.
787///
788/// # Safety
789/// `value` must be null or a valid value handle; `out` writable or null.
790#[unsafe(no_mangle)]
791pub unsafe extern "C" fn asdf_value_as_bool(value: *mut AsdfValue, out: *mut bool) -> AsdfValueErr {
792    guard("asdf_value_as_bool", AsdfValueErr::Unknown, || {
793        let Some(converted) = bool_of(value) else {
794            return AsdfValueErr::TypeMismatch;
795        };
796        if !out.is_null() {
797            unsafe { write_out(out, converted) };
798        }
799        AsdfValueErr::Ok
800    })
801}
802
803/// Whether the value is null.
804///
805/// # Safety
806/// `value` must be null or a valid value handle.
807#[unsafe(no_mangle)]
808pub unsafe extern "C" fn asdf_value_is_null(value: *mut AsdfValue) -> bool {
809    guard("asdf_value_is_null", false, || matches!(resolved_of(value), Some(Resolved::Null)))
810}
811
812/// Whether the value is a string.
813///
814/// # Safety
815/// `value` must be null or a valid value handle.
816#[unsafe(no_mangle)]
817pub unsafe extern "C" fn asdf_value_is_string(value: *mut AsdfValue) -> bool {
818    guard("asdf_value_is_string", false, || matches!(resolved_of(value), Some(Resolved::String)))
819}
820
821/// Read the value as a NUL-terminated string.
822///
823/// # Safety
824/// `value` must be null or a valid value handle; `out` writable or null. The
825/// string is owned by the value's file.
826#[unsafe(no_mangle)]
827pub unsafe extern "C" fn asdf_value_as_string0(
828    value: *mut AsdfValue,
829    out: *mut *const c_char,
830) -> AsdfValueErr {
831    guard("asdf_value_as_string0", AsdfValueErr::Unknown, || {
832        if !matches!(resolved_of(value), Some(Resolved::String)) {
833            return AsdfValueErr::TypeMismatch;
834        }
835        intern_scalar(value, out)
836    })
837}
838
839/// Whether the value is a scalar of any kind.
840///
841/// # Safety
842/// `value` must be null or a valid value handle.
843#[unsafe(no_mangle)]
844pub unsafe extern "C" fn asdf_value_is_scalar(value: *mut AsdfValue) -> bool {
845    guard("asdf_value_is_scalar", false, || value_is_scalar(value))
846}
847
848/// Safe internal form of [`asdf_value_is_scalar`].
849///
850/// The exported entry point is `unsafe extern "C"`, so calling it from
851/// inside the crate would need an `unsafe` block at every site to assert a
852/// contract the crate itself is upholding. Callers use this instead.
853pub(crate) fn value_is_scalar(value: *mut AsdfValue) -> bool {
854    value_document(value)
855        .zip(value_node(value))
856        .is_some_and(|(doc, node)| doc.resolved(node).is_scalar())
857}
858
859/// Read a scalar's raw text, whatever its resolved type.
860///
861/// # Safety
862/// `value` must be null or a valid value handle; `out` writable or null.
863#[unsafe(no_mangle)]
864pub unsafe extern "C" fn asdf_value_as_scalar0(
865    value: *mut AsdfValue,
866    out: *mut *const c_char,
867) -> AsdfValueErr {
868    guard("asdf_value_as_scalar0", AsdfValueErr::Unknown, || {
869        // A null handle is not a value of the wrong type; there is no value.
870        if value.is_null() {
871            return AsdfValueErr::Unknown;
872        }
873        if !value_is_scalar(value) {
874            return AsdfValueErr::TypeMismatch;
875        }
876        intern_scalar(value, out)
877    })
878}
879
880/// Hand out a scalar's text, interned in the file so it stays valid.
881fn intern_scalar(value: *mut AsdfValue, out: *mut *const c_char) -> AsdfValueErr {
882    let (Some(doc), Some(node), Some(file)) =
883        (value_document(value), value_node(value), value_file(value))
884    else {
885        return AsdfValueErr::Unknown;
886    };
887    let Some(text) = doc.resolved(node).as_str() else {
888        return AsdfValueErr::TypeMismatch;
889    };
890    let ptr = unsafe { &*file }.intern(text);
891    if ptr.is_null() {
892        return AsdfValueErr::Oom;
893    }
894    if !out.is_null() {
895        unsafe { write_out(out, ptr) };
896    }
897    AsdfValueErr::Ok
898}
899
900// ---- Container iteration --------------------------------------------
901
902/// An iterator over either kind of container.
903///
904/// `repr(C)` for the same reason as the other two: C casts a
905/// `*mut asdf_container_iter_t` to and from this.
906#[repr(C)]
907struct ContainerIter {
908    /// The public head, which C casts to. Must stay first.
909    public: asdf_container_iter_t,
910    file: *mut AsdfFile,
911    /// The children, each with its key (for a mapping) and the position it
912    /// holds in the container -- which is what `index` reports, counting
913    /// from the container's own start even when iterating in reverse.
914    entries: Vec<(Option<String>, NodeId, c_int)>,
915    position: usize,
916    current_key: Option<CString>,
917    current_value: *mut AsdfValue,
918    is_mapping: bool,
919}
920
921fn container_iter_init(container: *mut AsdfValue, reverse: bool) -> *mut asdf_container_iter_t {
922    let (Some(doc), Some(node), Some(file)) =
923        (value_document(container), value_node(container), value_file(container))
924    else {
925        return core::ptr::null_mut();
926    };
927
928    let resolved = doc.resolved(node);
929    let is_mapping = resolved.is_mapping();
930
931    let mut entries: Vec<(Option<String>, NodeId)> = if is_mapping {
932        doc.mapping_entries(node)
933            .unwrap_or(&[])
934            .iter()
935            .map(|e| (doc.resolved(e.key).as_str().map(str::to_string), e.value))
936            .collect()
937    } else if resolved.is_sequence() {
938        doc.sequence_items(node).unwrap_or(&[]).iter().map(|n| (None, *n)).collect()
939    } else {
940        return core::ptr::null_mut();
941    };
942
943    let mut numbered: Vec<(Option<String>, NodeId, c_int)> = entries
944        .drain(..)
945        .enumerate()
946        .map(|(index, (key, node))| (key, node, c_int::try_from(index).unwrap_or(c_int::MAX)))
947        .collect();
948    if reverse {
949        numbered.reverse();
950    }
951
952    let iter = Box::new(ContainerIter {
953        public: asdf_container_iter_t {
954            key: core::ptr::null(),
955            index: -1,
956            value: core::ptr::null_mut(),
957        },
958        file,
959        entries: numbered,
960        position: 0,
961        current_key: None,
962        current_value: core::ptr::null_mut(),
963        is_mapping,
964    });
965    Box::into_raw(iter).cast::<asdf_container_iter_t>()
966}
967
968/// Start iterating a mapping or sequence.
969///
970/// # Safety
971/// `container` must be null or a valid value handle.
972#[unsafe(no_mangle)]
973pub unsafe extern "C" fn asdf_container_iter_init(
974    container: *mut AsdfValue,
975) -> *mut asdf_container_iter_t {
976    guard("asdf_container_iter_init", core::ptr::null_mut(), || {
977        container_iter_init(container, false)
978    })
979}
980
981/// Start iterating a container in reverse.
982///
983/// # Safety
984/// `container` must be null or a valid value handle.
985#[unsafe(no_mangle)]
986pub unsafe extern "C" fn asdf_container_reverse_iter_init(
987    container: *mut AsdfValue,
988) -> *mut asdf_container_iter_t {
989    guard("asdf_container_reverse_iter_init", core::ptr::null_mut(), || {
990        container_iter_init(container, true)
991    })
992}
993
994/// Advance a container iterator. Same contract as
995/// [`asdf_mapping_iter_next`].
996///
997/// # Safety
998/// `iter_ptr` must be null or point to an iterator from one of the init
999/// functions.
1000#[unsafe(no_mangle)]
1001pub unsafe extern "C" fn asdf_container_iter_next(
1002    iter_ptr: *mut *mut asdf_container_iter_t,
1003) -> bool {
1004    guard("asdf_container_iter_next", false, || {
1005        if iter_ptr.is_null() {
1006            return false;
1007        }
1008        let raw = unsafe { *iter_ptr };
1009        if raw.is_null() {
1010            return false;
1011        }
1012        let iter = unsafe { &mut *raw.cast::<ContainerIter>() };
1013
1014        if !iter.current_value.is_null() {
1015            drop(unsafe { Box::from_raw(iter.current_value) });
1016            iter.current_value = core::ptr::null_mut();
1017        }
1018
1019        if iter.position >= iter.entries.len() {
1020            container_iter_destroy(raw);
1021            unsafe { write_out(iter_ptr, core::ptr::null_mut()) };
1022            return false;
1023        }
1024
1025        let (key, node, index) = iter.entries[iter.position].clone();
1026        iter.position += 1;
1027
1028        // A mapping's entries are numbered too: the index is the position in
1029        // the container, which a caller can use to address the entry either
1030        // way round.
1031        if iter.is_mapping {
1032            iter.current_key = key.and_then(|k| CString::new(k).ok());
1033            iter.public.key = iter.current_key.as_ref().map_or(core::ptr::null(), |k| k.as_ptr());
1034        } else {
1035            iter.current_key = None;
1036            iter.public.key = core::ptr::null();
1037        }
1038        iter.public.index = index;
1039
1040        iter.current_value = make_value(iter.file, node);
1041        iter.public.value = iter.current_value.cast();
1042        true
1043    })
1044}
1045
1046/// Release a container iterator.
1047///
1048/// # Safety
1049/// `iter` must be null or an iterator that has not already been destroyed.
1050#[unsafe(no_mangle)]
1051pub unsafe extern "C" fn asdf_container_iter_destroy(iter: *mut asdf_container_iter_t) {
1052    guard("asdf_container_iter_destroy", (), || container_iter_destroy(iter))
1053}
1054
1055/// Safe internal form of [`asdf_container_iter_destroy`].
1056///
1057/// The exported entry point is `unsafe extern "C"`, so calling it from
1058/// inside the crate would need an `unsafe` block at every site to assert a
1059/// contract the crate itself is upholding. Callers use this instead.
1060pub(crate) fn container_iter_destroy(iter: *mut asdf_container_iter_t) {
1061    if iter.is_null() {
1062        return;
1063    }
1064    let mut boxed = unsafe { Box::from_raw(iter.cast::<ContainerIter>()) };
1065    if !boxed.current_value.is_null() {
1066        drop(unsafe { Box::from_raw(boxed.current_value) });
1067        boxed.current_value = core::ptr::null_mut();
1068    }
1069}
1070
1071// ---- Building values -------------------------------------------------
1072
1073/// Allocate a node in a file's document, creating the document if needed.
1074fn add_node(
1075    file: *mut AsdfFile,
1076    make: impl FnOnce(&mut asdf_core::yaml::Document) -> NodeId,
1077) -> *mut AsdfValue {
1078    if file.is_null() {
1079        return core::ptr::null_mut();
1080    }
1081    let Some(doc) = file_document_mut(file) else {
1082        return core::ptr::null_mut();
1083    };
1084    let node = make(doc);
1085    make_value(file, node)
1086}
1087
1088/// Generate `asdf_value_of_<type>` for a scalar.
1089macro_rules! value_of {
1090    ($name:ident, $ty:ty) => {
1091        /// Build a value holding this scalar.
1092        ///
1093        /// # Safety
1094        /// `file` must be a valid file handle. The result must be released
1095        /// with `asdf_value_destroy`.
1096        #[unsafe(no_mangle)]
1097        pub unsafe extern "C" fn $name(file: *mut AsdfFile, value: $ty) -> *mut AsdfValue {
1098            guard(stringify!($name), core::ptr::null_mut(), || {
1099                add_node(file, |doc| doc.add_scalar(value.to_string()))
1100            })
1101        }
1102    };
1103}
1104
1105value_of!(asdf_value_of_int8, i8);
1106value_of!(asdf_value_of_int16, i16);
1107value_of!(asdf_value_of_int32, i32);
1108value_of!(asdf_value_of_int64, i64);
1109value_of!(asdf_value_of_uint8, u8);
1110value_of!(asdf_value_of_uint16, u16);
1111value_of!(asdf_value_of_uint32, u32);
1112value_of!(asdf_value_of_uint64, u64);
1113
1114/// Build a value holding a `double`.
1115///
1116/// # Safety
1117/// See the integer constructors.
1118#[unsafe(no_mangle)]
1119pub unsafe extern "C" fn asdf_value_of_double(file: *mut AsdfFile, value: f64) -> *mut AsdfValue {
1120    guard("asdf_value_of_double", core::ptr::null_mut(), || {
1121        add_node(file, |doc| doc.add_scalar(asdf_core::core::elements::format_float(value)))
1122    })
1123}
1124
1125/// Build a value holding a `float`.
1126///
1127/// # Safety
1128/// See the integer constructors.
1129#[unsafe(no_mangle)]
1130pub unsafe extern "C" fn asdf_value_of_float(file: *mut AsdfFile, value: f32) -> *mut AsdfValue {
1131    guard("asdf_value_of_float", core::ptr::null_mut(), || {
1132        add_node(file, |doc| {
1133            doc.add_scalar(asdf_core::core::elements::format_float(f64::from(value)))
1134        })
1135    })
1136}
1137
1138/// Build a value holding a boolean.
1139///
1140/// # Safety
1141/// See the integer constructors.
1142#[unsafe(no_mangle)]
1143pub unsafe extern "C" fn asdf_value_of_bool(file: *mut AsdfFile, value: bool) -> *mut AsdfValue {
1144    guard("asdf_value_of_bool", core::ptr::null_mut(), || {
1145        add_node(file, |doc| doc.add_scalar(if value { "true" } else { "false" }))
1146    })
1147}
1148
1149/// Build a null value.
1150///
1151/// # Safety
1152/// See the integer constructors.
1153#[unsafe(no_mangle)]
1154pub unsafe extern "C" fn asdf_value_of_null(file: *mut AsdfFile) -> *mut AsdfValue {
1155    guard("asdf_value_of_null", core::ptr::null_mut(), || value_of_null(file))
1156}
1157
1158/// Safe internal form of [`asdf_value_of_null`].
1159///
1160/// The exported entry point is `unsafe extern "C"`, so calling it from
1161/// inside the crate would need an `unsafe` block at every site to assert a
1162/// contract the crate itself is upholding. Callers use this instead.
1163pub(crate) fn value_of_null(file: *mut AsdfFile) -> *mut AsdfValue {
1164    add_node(file, |doc| doc.add_scalar("null"))
1165}
1166
1167/// Build a value holding a NUL-terminated string.
1168///
1169/// The string is quoted where needed so it reads back as a string rather
1170/// than as a number or boolean.
1171///
1172/// # Safety
1173/// `value` must be a valid NUL-terminated string or null.
1174#[unsafe(no_mangle)]
1175pub unsafe extern "C" fn asdf_value_of_string0(
1176    file: *mut AsdfFile,
1177    value: *const c_char,
1178) -> *mut AsdfValue {
1179    guard("asdf_value_of_string0", core::ptr::null_mut(), || value_of_string0(file, value))
1180}
1181
1182/// Safe internal form of [`asdf_value_of_string0`].
1183///
1184/// The exported entry point is `unsafe extern "C"`, so calling it from
1185/// inside the crate would need an `unsafe` block at every site to assert a
1186/// contract the crate itself is upholding. Callers use this instead.
1187pub(crate) fn value_of_string0(file: *mut AsdfFile, value: *const c_char) -> *mut AsdfValue {
1188    if value.is_null() {
1189        return core::ptr::null_mut();
1190    }
1191    let text = unsafe { CStr::from_ptr(value) }.to_string_lossy().into_owned();
1192    add_node(file, |doc| {
1193        let style = match resolve(&text, ScalarStyle::Plain, Schema::Libasdf) {
1194            Resolved::String => ScalarStyle::Plain,
1195            _ => ScalarStyle::SingleQuoted,
1196        };
1197        doc.add_scalar_styled(text, style)
1198    })
1199}
1200
1201/// Build a value holding a string of `len` bytes.
1202///
1203/// # Safety
1204/// `value` must point to at least `len` readable bytes.
1205#[unsafe(no_mangle)]
1206pub unsafe extern "C" fn asdf_value_of_string(
1207    file: *mut AsdfFile,
1208    value: *const c_char,
1209    len: usize,
1210) -> *mut AsdfValue {
1211    guard("asdf_value_of_string", core::ptr::null_mut(), || value_of_string(file, value, len))
1212}
1213
1214/// Safe internal form of [`asdf_value_of_string`].
1215///
1216/// The exported entry point is `unsafe extern "C"`, so calling it from
1217/// inside the crate would need an `unsafe` block at every site to assert a
1218/// contract the crate itself is upholding. Callers use this instead.
1219pub(crate) fn value_of_string(
1220    file: *mut AsdfFile,
1221    value: *const c_char,
1222    len: usize,
1223) -> *mut AsdfValue {
1224    if value.is_null() {
1225        return core::ptr::null_mut();
1226    }
1227    let bytes = unsafe { core::slice::from_raw_parts(value.cast::<u8>(), len) };
1228    let text = String::from_utf8_lossy(bytes).into_owned();
1229    add_node(file, |doc| {
1230        let style = match resolve(&text, ScalarStyle::Plain, Schema::Libasdf) {
1231            Resolved::String => ScalarStyle::Plain,
1232            _ => ScalarStyle::SingleQuoted,
1233        };
1234        doc.add_scalar_styled(text, style)
1235    })
1236}
1237
1238/// Create an empty mapping.
1239///
1240/// # Safety
1241/// `file` must be a valid file handle. The result must be released with
1242/// `asdf_mapping_destroy`.
1243#[unsafe(no_mangle)]
1244pub unsafe extern "C" fn asdf_mapping_create(file: *mut AsdfFile) -> *mut AsdfMapping {
1245    guard("asdf_mapping_create", core::ptr::null_mut(), || {
1246        add_node(file, |doc| doc.add(asdf_core::yaml::Node::mapping()))
1247    })
1248}
1249
1250/// Create an empty sequence.
1251///
1252/// # Safety
1253/// `file` must be a valid file handle. The result must be released with
1254/// `asdf_sequence_destroy`.
1255#[unsafe(no_mangle)]
1256pub unsafe extern "C" fn asdf_sequence_create(file: *mut AsdfFile) -> *mut AsdfSequence {
1257    guard("asdf_sequence_create", core::ptr::null_mut(), || sequence_create(file))
1258}
1259
1260/// Safe internal form of [`asdf_sequence_create`].
1261///
1262/// The exported entry point is `unsafe extern "C"`, so calling it from
1263/// inside the crate would need an `unsafe` block at every site to assert a
1264/// contract the crate itself is upholding. Callers use this instead.
1265pub(crate) fn sequence_create(file: *mut AsdfFile) -> *mut AsdfSequence {
1266    add_node(file, |doc| doc.add(asdf_core::yaml::Node::sequence()))
1267}
1268
1269/// Release a mapping handle.
1270///
1271/// # Safety
1272/// See `asdf_value_destroy`, which this is equivalent to.
1273#[unsafe(no_mangle)]
1274pub unsafe extern "C" fn asdf_mapping_destroy(mapping: *mut AsdfMapping) {
1275    unsafe { crate::file_ffi::asdf_value_destroy(mapping) }
1276}
1277
1278/// Release a sequence handle.
1279///
1280/// # Safety
1281/// See `asdf_value_destroy`, which this is equivalent to.
1282#[unsafe(no_mangle)]
1283pub unsafe extern "C" fn asdf_sequence_destroy(sequence: *mut AsdfSequence) {
1284    unsafe { crate::file_ffi::asdf_value_destroy(sequence) }
1285}
1286
1287/// Set how a mapping is written: inline, block, or the emitter's choice.
1288///
1289/// # Safety
1290/// `mapping` must be null or a valid handle.
1291#[unsafe(no_mangle)]
1292pub unsafe extern "C" fn asdf_mapping_set_style(
1293    mapping: *mut AsdfMapping,
1294    style: crate::types::AsdfYamlNodeStyle,
1295) {
1296    guard("asdf_mapping_set_style", (), || set_collection_style(mapping, style))
1297}
1298
1299/// Set how a sequence is written.
1300///
1301/// # Safety
1302/// `sequence` must be null or a valid handle.
1303#[unsafe(no_mangle)]
1304pub unsafe extern "C" fn asdf_sequence_set_style(
1305    sequence: *mut AsdfSequence,
1306    style: crate::types::AsdfYamlNodeStyle,
1307) {
1308    guard("asdf_sequence_set_style", (), || set_collection_style(sequence, style))
1309}
1310
1311fn set_collection_style(value: *mut AsdfValue, style: crate::types::AsdfYamlNodeStyle) {
1312    use crate::types::AsdfYamlNodeStyle;
1313    use asdf_core::yaml::CollectionStyle;
1314
1315    let Some(file) = value_file(value) else { return };
1316    let Some(node) = value_node(value) else { return };
1317    let Some(doc) = file_document_mut(file) else { return };
1318
1319    let target = doc.resolve(node);
1320    let wanted = match style {
1321        AsdfYamlNodeStyle::Auto => CollectionStyle::Auto,
1322        AsdfYamlNodeStyle::Flow => CollectionStyle::Flow,
1323        AsdfYamlNodeStyle::Block => CollectionStyle::Block,
1324    };
1325    match &mut doc.node_mut(target).data {
1326        NodeData::Mapping { style, .. } | NodeData::Sequence { style, .. } => *style = wanted,
1327        _ => {}
1328    }
1329}
1330
1331/// Release an inserted handle, which the container now owns.
1332///
1333/// The insertion entry points that take an existing `asdf_value_t *` consume
1334/// it: `value.h` says "ownership of ``value`` transfers to the mapping on
1335/// success", and callers -- libasdf-gwcs among them -- destroy the handle
1336/// themselves only on the failure path. The node itself lives in the
1337/// document from here on; what is released is the handle box that named it.
1338///
1339/// This sits at the exported boundary rather than inside `mapping_set` /
1340/// `sequence_append`, because the `asdf_mapping_set_<type>` variants build a
1341/// value, insert it and then release it themselves -- for them the internal
1342/// helpers must stay non-consuming.
1343fn consume_inserted(err: AsdfValueErr, value: *mut AsdfValue) -> AsdfValueErr {
1344    if err == AsdfValueErr::Ok {
1345        unsafe { crate::file_ffi::asdf_value_destroy(value) };
1346    }
1347    err
1348}
1349
1350/// Put a value into a mapping under `key`.
1351///
1352/// Consumes `value` on success: ownership of it transfers to the mapping.
1353///
1354/// # Safety
1355/// `mapping` and `value` must be valid handles from the same file; `key` a
1356/// valid NUL-terminated string.
1357#[unsafe(no_mangle)]
1358pub unsafe extern "C" fn asdf_mapping_set(
1359    mapping: *mut AsdfMapping,
1360    key: *const c_char,
1361    value: *mut AsdfValue,
1362) -> AsdfValueErr {
1363    guard("asdf_mapping_set", AsdfValueErr::Unknown, || {
1364        consume_inserted(mapping_set(mapping, key, value), value)
1365    })
1366}
1367
1368/// Safe internal form of [`asdf_mapping_set`].
1369///
1370/// The exported entry point is `unsafe extern "C"`, so calling it from
1371/// inside the crate would need an `unsafe` block at every site to assert a
1372/// contract the crate itself is upholding. Callers use this instead.
1373pub(crate) fn mapping_set(
1374    mapping: *mut AsdfMapping,
1375    key: *const c_char,
1376    value: *mut AsdfValue,
1377) -> AsdfValueErr {
1378    if key.is_null() {
1379        return AsdfValueErr::Unknown;
1380    }
1381    let (Some(file), Some(target)) = (value_file(mapping), value_node(mapping)) else {
1382        return AsdfValueErr::Unknown;
1383    };
1384    let Some(child) = value_node(value) else {
1385        return AsdfValueErr::Unknown;
1386    };
1387    let key = unsafe { CStr::from_ptr(key) }.to_string_lossy().into_owned();
1388
1389    let Some(doc) = file_document_mut(file) else {
1390        return AsdfValueErr::Unknown;
1391    };
1392    if !doc.resolved(target).is_mapping() {
1393        return AsdfValueErr::TypeMismatch;
1394    }
1395    doc.mapping_set(target, &key, child);
1396    AsdfValueErr::Ok
1397}
1398
1399/// Remove an entry from a mapping, returning it.
1400///
1401/// # Safety
1402/// `mapping` must be a valid handle and `key` a valid NUL-terminated string.
1403/// The result must be released with `asdf_value_destroy`.
1404#[unsafe(no_mangle)]
1405pub unsafe extern "C" fn asdf_mapping_pop(
1406    mapping: *mut AsdfMapping,
1407    key: *const c_char,
1408) -> *mut AsdfValue {
1409    guard("asdf_mapping_pop", core::ptr::null_mut(), || {
1410        if key.is_null() {
1411            return core::ptr::null_mut();
1412        }
1413        let (Some(file), Some(target)) = (value_file(mapping), value_node(mapping)) else {
1414            return core::ptr::null_mut();
1415        };
1416        let key = unsafe { CStr::from_ptr(key) }.to_string_lossy().into_owned();
1417
1418        let Some(doc) = file_document_mut(file) else {
1419            return core::ptr::null_mut();
1420        };
1421        match doc.mapping_remove(target, &key) {
1422            Some(node) => make_value(file, node),
1423            None => core::ptr::null_mut(),
1424        }
1425    })
1426}
1427
1428/// Append a value to a sequence.
1429///
1430/// # Safety
1431/// `sequence` and `value` must be valid handles from the same file.
1432#[unsafe(no_mangle)]
1433pub unsafe extern "C" fn asdf_sequence_append(
1434    sequence: *mut AsdfSequence,
1435    value: *mut AsdfValue,
1436) -> AsdfValueErr {
1437    guard("asdf_sequence_append", AsdfValueErr::Unknown, || {
1438        consume_inserted(sequence_append(sequence, value), value)
1439    })
1440}
1441
1442/// Safe internal form of [`asdf_sequence_append`].
1443///
1444/// The exported entry point is `unsafe extern "C"`, so calling it from
1445/// inside the crate would need an `unsafe` block at every site to assert a
1446/// contract the crate itself is upholding. Callers use this instead.
1447pub(crate) fn sequence_append(sequence: *mut AsdfSequence, value: *mut AsdfValue) -> AsdfValueErr {
1448    let (Some(file), Some(target)) = (value_file(sequence), value_node(sequence)) else {
1449        return AsdfValueErr::Unknown;
1450    };
1451    let Some(child) = value_node(value) else {
1452        return AsdfValueErr::Unknown;
1453    };
1454    let Some(doc) = file_document_mut(file) else {
1455        return AsdfValueErr::Unknown;
1456    };
1457
1458    let resolved = doc.resolve(target);
1459    if !doc.node(resolved).is_sequence() {
1460        return AsdfValueErr::TypeMismatch;
1461    }
1462    match &mut doc.node_mut(resolved).data {
1463        NodeData::Sequence { items, .. } => {
1464            items.push(child);
1465            AsdfValueErr::Ok
1466        }
1467        _ => AsdfValueErr::TypeMismatch,
1468    }
1469}
1470
1471/// Remove an item from a sequence, returning it.
1472///
1473/// # Safety
1474/// `sequence` must be a valid handle. The result must be released with
1475/// `asdf_value_destroy`.
1476#[unsafe(no_mangle)]
1477pub unsafe extern "C" fn asdf_sequence_pop(
1478    sequence: *mut AsdfSequence,
1479    index: c_int,
1480) -> *mut AsdfValue {
1481    guard("asdf_sequence_pop", core::ptr::null_mut(), || {
1482        let (Some(file), Some(target)) = (value_file(sequence), value_node(sequence)) else {
1483            return core::ptr::null_mut();
1484        };
1485        let Some(doc) = file_document_mut(file) else {
1486            return core::ptr::null_mut();
1487        };
1488        match doc.sequence_remove(target, i64::from(index)) {
1489            Some(node) => make_value(file, node),
1490            None => core::ptr::null_mut(),
1491        }
1492    })
1493}
1494
1495// ---- Typed setters on containers -------------------------------------
1496
1497/// Generate `asdf_mapping_set_<type>` and `asdf_sequence_append_<type>` for
1498/// one scalar type, along with `asdf_sequence_of_<type>`.
1499///
1500/// Each is the composition of the matching `asdf_value_of_<type>` with
1501/// `asdf_mapping_set` / `asdf_sequence_append`, which is how the C header
1502/// documents them.
1503macro_rules! container_setters {
1504    ($set:ident, $append:ident, $of:ident, $ctor:ident, $ty:ty) => {
1505        /// Put a scalar into a mapping under `key`.
1506        ///
1507        /// # Safety
1508        /// `mapping` must be a valid handle and `key` a valid string.
1509        #[unsafe(no_mangle)]
1510        pub unsafe extern "C" fn $set(
1511            mapping: *mut AsdfMapping,
1512            key: *const c_char,
1513            value: $ty,
1514        ) -> AsdfValueErr {
1515            guard(stringify!($set), AsdfValueErr::Unknown, || {
1516                let Some(file) = value_file(mapping) else {
1517                    return AsdfValueErr::Unknown;
1518                };
1519                let node = unsafe { $ctor(file, value) };
1520                if node.is_null() {
1521                    return AsdfValueErr::Unknown;
1522                }
1523                let result = mapping_set(mapping, key, node);
1524                unsafe { crate::file_ffi::asdf_value_destroy(node) };
1525                result
1526            })
1527        }
1528
1529        /// Append a scalar to a sequence.
1530        ///
1531        /// # Safety
1532        /// `sequence` must be a valid handle.
1533        #[unsafe(no_mangle)]
1534        pub unsafe extern "C" fn $append(sequence: *mut AsdfSequence, value: $ty) -> AsdfValueErr {
1535            guard(stringify!($append), AsdfValueErr::Unknown, || {
1536                let Some(file) = value_file(sequence) else {
1537                    return AsdfValueErr::Unknown;
1538                };
1539                let node = unsafe { $ctor(file, value) };
1540                if node.is_null() {
1541                    return AsdfValueErr::Unknown;
1542                }
1543                let result = sequence_append(sequence, node);
1544                unsafe { crate::file_ffi::asdf_value_destroy(node) };
1545                result
1546            })
1547        }
1548
1549        /// Build a sequence from a C array of scalars.
1550        ///
1551        /// # Safety
1552        /// `file` must be a valid file handle and `arr` must point to at
1553        /// least `size` readable values. The result must be released with
1554        /// `asdf_sequence_destroy`.
1555        #[unsafe(no_mangle)]
1556        pub unsafe extern "C" fn $of(
1557            file: *mut AsdfFile,
1558            arr: *const $ty,
1559            size: c_int,
1560        ) -> *mut AsdfSequence {
1561            guard(stringify!($of), core::ptr::null_mut(), || {
1562                if arr.is_null() || size < 0 {
1563                    return core::ptr::null_mut();
1564                }
1565                let sequence = sequence_create(file);
1566                if sequence.is_null() {
1567                    return core::ptr::null_mut();
1568                }
1569                let items = unsafe { core::slice::from_raw_parts(arr, size as usize) };
1570                for value in items {
1571                    if unsafe { $append(sequence, *value) } != AsdfValueErr::Ok {
1572                        unsafe { asdf_sequence_destroy(sequence) };
1573                        return core::ptr::null_mut();
1574                    }
1575                }
1576                sequence
1577            })
1578        }
1579    };
1580}
1581
1582container_setters!(
1583    asdf_mapping_set_int8,
1584    asdf_sequence_append_int8,
1585    asdf_sequence_of_int8,
1586    asdf_value_of_int8,
1587    i8
1588);
1589container_setters!(
1590    asdf_mapping_set_int16,
1591    asdf_sequence_append_int16,
1592    asdf_sequence_of_int16,
1593    asdf_value_of_int16,
1594    i16
1595);
1596container_setters!(
1597    asdf_mapping_set_int32,
1598    asdf_sequence_append_int32,
1599    asdf_sequence_of_int32,
1600    asdf_value_of_int32,
1601    i32
1602);
1603container_setters!(
1604    asdf_mapping_set_int64,
1605    asdf_sequence_append_int64,
1606    asdf_sequence_of_int64,
1607    asdf_value_of_int64,
1608    i64
1609);
1610container_setters!(
1611    asdf_mapping_set_uint8,
1612    asdf_sequence_append_uint8,
1613    asdf_sequence_of_uint8,
1614    asdf_value_of_uint8,
1615    u8
1616);
1617container_setters!(
1618    asdf_mapping_set_uint16,
1619    asdf_sequence_append_uint16,
1620    asdf_sequence_of_uint16,
1621    asdf_value_of_uint16,
1622    u16
1623);
1624container_setters!(
1625    asdf_mapping_set_uint32,
1626    asdf_sequence_append_uint32,
1627    asdf_sequence_of_uint32,
1628    asdf_value_of_uint32,
1629    u32
1630);
1631container_setters!(
1632    asdf_mapping_set_uint64,
1633    asdf_sequence_append_uint64,
1634    asdf_sequence_of_uint64,
1635    asdf_value_of_uint64,
1636    u64
1637);
1638container_setters!(
1639    asdf_mapping_set_float,
1640    asdf_sequence_append_float,
1641    asdf_sequence_of_float,
1642    asdf_value_of_float,
1643    f32
1644);
1645container_setters!(
1646    asdf_mapping_set_double,
1647    asdf_sequence_append_double,
1648    asdf_sequence_of_double,
1649    asdf_value_of_double,
1650    f64
1651);
1652container_setters!(
1653    asdf_mapping_set_bool,
1654    asdf_sequence_append_bool,
1655    asdf_sequence_of_bool,
1656    asdf_value_of_bool,
1657    bool
1658);
1659
1660/// Put a NUL-terminated string into a mapping.
1661///
1662/// # Safety
1663/// `mapping` must be a valid handle; `key` and `value` valid strings.
1664#[unsafe(no_mangle)]
1665pub unsafe extern "C" fn asdf_mapping_set_string0(
1666    mapping: *mut AsdfMapping,
1667    key: *const c_char,
1668    value: *const c_char,
1669) -> AsdfValueErr {
1670    guard("asdf_mapping_set_string0", AsdfValueErr::Unknown, || {
1671        let Some(file) = value_file(mapping) else {
1672            return AsdfValueErr::Unknown;
1673        };
1674        let node = value_of_string0(file, value);
1675        if node.is_null() {
1676            return AsdfValueErr::Unknown;
1677        }
1678        let result = mapping_set(mapping, key, node);
1679        unsafe { crate::file_ffi::asdf_value_destroy(node) };
1680        result
1681    })
1682}
1683
1684/// Put a counted string into a mapping.
1685///
1686/// # Safety
1687/// `value` must point to at least `len` readable bytes.
1688#[unsafe(no_mangle)]
1689pub unsafe extern "C" fn asdf_mapping_set_string(
1690    mapping: *mut AsdfMapping,
1691    key: *const c_char,
1692    value: *const c_char,
1693    len: usize,
1694) -> AsdfValueErr {
1695    guard("asdf_mapping_set_string", AsdfValueErr::Unknown, || {
1696        let Some(file) = value_file(mapping) else {
1697            return AsdfValueErr::Unknown;
1698        };
1699        let node = value_of_string(file, value, len);
1700        if node.is_null() {
1701            return AsdfValueErr::Unknown;
1702        }
1703        let result = mapping_set(mapping, key, node);
1704        unsafe { crate::file_ffi::asdf_value_destroy(node) };
1705        result
1706    })
1707}
1708
1709/// Put a null into a mapping.
1710///
1711/// # Safety
1712/// `mapping` must be a valid handle and `key` a valid string.
1713#[unsafe(no_mangle)]
1714pub unsafe extern "C" fn asdf_mapping_set_null(
1715    mapping: *mut AsdfMapping,
1716    key: *const c_char,
1717) -> AsdfValueErr {
1718    guard("asdf_mapping_set_null", AsdfValueErr::Unknown, || {
1719        let Some(file) = value_file(mapping) else {
1720            return AsdfValueErr::Unknown;
1721        };
1722        let node = value_of_null(file);
1723        if node.is_null() {
1724            return AsdfValueErr::Unknown;
1725        }
1726        let result = mapping_set(mapping, key, node);
1727        unsafe { crate::file_ffi::asdf_value_destroy(node) };
1728        result
1729    })
1730}
1731
1732/// Put a nested mapping into a mapping.
1733///
1734/// # Safety
1735/// Both handles must belong to the same file.
1736#[unsafe(no_mangle)]
1737pub unsafe extern "C" fn asdf_mapping_set_mapping(
1738    mapping: *mut AsdfMapping,
1739    key: *const c_char,
1740    value: *mut AsdfMapping,
1741) -> AsdfValueErr {
1742    consume_inserted(mapping_set(mapping, key, value), value)
1743}
1744
1745/// Put a sequence into a mapping.
1746///
1747/// # Safety
1748/// Both handles must belong to the same file.
1749#[unsafe(no_mangle)]
1750pub unsafe extern "C" fn asdf_mapping_set_sequence(
1751    mapping: *mut AsdfMapping,
1752    key: *const c_char,
1753    value: *mut AsdfSequence,
1754) -> AsdfValueErr {
1755    consume_inserted(mapping_set(mapping, key, value), value)
1756}
1757
1758/// Append a NUL-terminated string to a sequence.
1759///
1760/// # Safety
1761/// `sequence` must be a valid handle and `value` a valid string.
1762#[unsafe(no_mangle)]
1763pub unsafe extern "C" fn asdf_sequence_append_string0(
1764    sequence: *mut AsdfSequence,
1765    value: *const c_char,
1766) -> AsdfValueErr {
1767    guard("asdf_sequence_append_string0", AsdfValueErr::Unknown, || {
1768        sequence_append_string0(sequence, value)
1769    })
1770}
1771
1772/// Safe internal form of [`asdf_sequence_append_string0`].
1773///
1774/// The exported entry point is `unsafe extern "C"`, so calling it from
1775/// inside the crate would need an `unsafe` block at every site to assert a
1776/// contract the crate itself is upholding. Callers use this instead.
1777pub(crate) fn sequence_append_string0(
1778    sequence: *mut AsdfSequence,
1779    value: *const c_char,
1780) -> AsdfValueErr {
1781    let Some(file) = value_file(sequence) else {
1782        return AsdfValueErr::Unknown;
1783    };
1784    let node = value_of_string0(file, value);
1785    if node.is_null() {
1786        return AsdfValueErr::Unknown;
1787    }
1788    let result = sequence_append(sequence, node);
1789    unsafe { crate::file_ffi::asdf_value_destroy(node) };
1790    result
1791}
1792
1793/// Append a counted string to a sequence.
1794///
1795/// # Safety
1796/// `value` must point to at least `len` readable bytes.
1797#[unsafe(no_mangle)]
1798pub unsafe extern "C" fn asdf_sequence_append_string(
1799    sequence: *mut AsdfSequence,
1800    value: *const c_char,
1801    len: usize,
1802) -> AsdfValueErr {
1803    guard("asdf_sequence_append_string", AsdfValueErr::Unknown, || {
1804        sequence_append_string(sequence, value, len)
1805    })
1806}
1807
1808/// Safe internal form of [`asdf_sequence_append_string`].
1809///
1810/// The exported entry point is `unsafe extern "C"`, so calling it from
1811/// inside the crate would need an `unsafe` block at every site to assert a
1812/// contract the crate itself is upholding. Callers use this instead.
1813pub(crate) fn sequence_append_string(
1814    sequence: *mut AsdfSequence,
1815    value: *const c_char,
1816    len: usize,
1817) -> AsdfValueErr {
1818    let Some(file) = value_file(sequence) else {
1819        return AsdfValueErr::Unknown;
1820    };
1821    let node = value_of_string(file, value, len);
1822    if node.is_null() {
1823        return AsdfValueErr::Unknown;
1824    }
1825    let result = sequence_append(sequence, node);
1826    unsafe { crate::file_ffi::asdf_value_destroy(node) };
1827    result
1828}
1829
1830/// Append a null to a sequence.
1831///
1832/// # Safety
1833/// `sequence` must be a valid handle.
1834#[unsafe(no_mangle)]
1835pub unsafe extern "C" fn asdf_sequence_append_null(sequence: *mut AsdfSequence) -> AsdfValueErr {
1836    guard("asdf_sequence_append_null", AsdfValueErr::Unknown, || sequence_append_null(sequence))
1837}
1838
1839/// Safe internal form of [`asdf_sequence_append_null`].
1840///
1841/// The exported entry point is `unsafe extern "C"`, so calling it from
1842/// inside the crate would need an `unsafe` block at every site to assert a
1843/// contract the crate itself is upholding. Callers use this instead.
1844pub(crate) fn sequence_append_null(sequence: *mut AsdfSequence) -> AsdfValueErr {
1845    let Some(file) = value_file(sequence) else {
1846        return AsdfValueErr::Unknown;
1847    };
1848    let node = value_of_null(file);
1849    if node.is_null() {
1850        return AsdfValueErr::Unknown;
1851    }
1852    let result = sequence_append(sequence, node);
1853    unsafe { crate::file_ffi::asdf_value_destroy(node) };
1854    result
1855}
1856
1857/// Append a mapping to a sequence.
1858///
1859/// # Safety
1860/// Both handles must belong to the same file.
1861#[unsafe(no_mangle)]
1862pub unsafe extern "C" fn asdf_sequence_append_mapping(
1863    sequence: *mut AsdfSequence,
1864    value: *mut AsdfMapping,
1865) -> AsdfValueErr {
1866    consume_inserted(sequence_append(sequence, value), value)
1867}
1868
1869/// Append a nested sequence to a sequence.
1870///
1871/// # Safety
1872/// Both handles must belong to the same file.
1873#[unsafe(no_mangle)]
1874pub unsafe extern "C" fn asdf_sequence_append_sequence(
1875    sequence: *mut AsdfSequence,
1876    value: *mut AsdfSequence,
1877) -> AsdfValueErr {
1878    consume_inserted(sequence_append(sequence, value), value)
1879}
1880
1881/// Build a sequence of nulls.
1882///
1883/// # Safety
1884/// `file` must be a valid file handle.
1885#[unsafe(no_mangle)]
1886pub unsafe extern "C" fn asdf_sequence_of_null(
1887    file: *mut AsdfFile,
1888    size: c_int,
1889) -> *mut AsdfSequence {
1890    guard("asdf_sequence_of_null", core::ptr::null_mut(), || {
1891        if size < 0 {
1892            return core::ptr::null_mut();
1893        }
1894        let sequence = sequence_create(file);
1895        if sequence.is_null() {
1896            return core::ptr::null_mut();
1897        }
1898        for _ in 0..size {
1899            if sequence_append_null(sequence) != AsdfValueErr::Ok {
1900                unsafe { asdf_sequence_destroy(sequence) };
1901                return core::ptr::null_mut();
1902            }
1903        }
1904        sequence
1905    })
1906}
1907
1908/// Build a sequence from an array of NUL-terminated strings.
1909///
1910/// # Safety
1911/// `arr` must point to at least `size` valid string pointers.
1912#[unsafe(no_mangle)]
1913pub unsafe extern "C" fn asdf_sequence_of_string0(
1914    file: *mut AsdfFile,
1915    arr: *const *const c_char,
1916    size: c_int,
1917) -> *mut AsdfSequence {
1918    guard("asdf_sequence_of_string0", core::ptr::null_mut(), || {
1919        if arr.is_null() || size < 0 {
1920            return core::ptr::null_mut();
1921        }
1922        let sequence = sequence_create(file);
1923        if sequence.is_null() {
1924            return core::ptr::null_mut();
1925        }
1926        for index in 0..size as isize {
1927            let text = unsafe { *arr.offset(index) };
1928            if sequence_append_string0(sequence, text) != AsdfValueErr::Ok {
1929                unsafe { asdf_sequence_destroy(sequence) };
1930                return core::ptr::null_mut();
1931            }
1932        }
1933        sequence
1934    })
1935}
1936
1937/// Build a sequence from an array of counted strings.
1938///
1939/// # Safety
1940/// `arr` and `lens` must each point to at least `size` readable entries.
1941#[unsafe(no_mangle)]
1942pub unsafe extern "C" fn asdf_sequence_of_string(
1943    file: *mut AsdfFile,
1944    arr: *const *const c_char,
1945    lens: *const usize,
1946    size: c_int,
1947) -> *mut AsdfSequence {
1948    guard("asdf_sequence_of_string", core::ptr::null_mut(), || {
1949        if arr.is_null() || lens.is_null() || size < 0 {
1950            return core::ptr::null_mut();
1951        }
1952        let sequence = sequence_create(file);
1953        if sequence.is_null() {
1954            return core::ptr::null_mut();
1955        }
1956        for index in 0..size as isize {
1957            let text = unsafe { *arr.offset(index) };
1958            let len = unsafe { *lens.offset(index) };
1959            if sequence_append_string(sequence, text, len) != AsdfValueErr::Ok {
1960                unsafe { asdf_sequence_destroy(sequence) };
1961                return core::ptr::null_mut();
1962            }
1963        }
1964        sequence
1965    })
1966}
1967
1968/// Copy a mapping's entries into a new mapping.
1969///
1970/// A shallow copy: the entries refer to the same value nodes.
1971///
1972/// # Safety
1973/// `mapping` must be a valid handle. The result must be released with
1974/// `asdf_mapping_destroy`.
1975#[unsafe(no_mangle)]
1976pub unsafe extern "C" fn asdf_mapping_copy(mapping: *mut AsdfMapping) -> *mut AsdfMapping {
1977    guard("asdf_mapping_copy", core::ptr::null_mut(), || {
1978        let (Some(file), Some(source)) = (value_file(mapping), value_node(mapping)) else {
1979            return core::ptr::null_mut();
1980        };
1981        let Some(doc) = file_document_mut(file) else {
1982            return core::ptr::null_mut();
1983        };
1984        let Some(entries) = doc.mapping_entries(source).map(<[_]>::to_vec) else {
1985            return core::ptr::null_mut();
1986        };
1987        let pairs: Vec<_> = entries.iter().map(|e| (e.key, e.value)).collect();
1988        let fresh = doc.add_mapping(pairs);
1989        make_value(file, fresh)
1990    })
1991}
1992
1993/// Merge one mapping's entries into another.
1994///
1995/// Existing keys are replaced and new ones appended, in the update's order.
1996///
1997/// # Safety
1998/// Both handles must be valid and belong to the same file.
1999#[unsafe(no_mangle)]
2000pub unsafe extern "C" fn asdf_mapping_update(
2001    mapping: *mut AsdfMapping,
2002    update: *mut AsdfMapping,
2003) -> AsdfValueErr {
2004    guard("asdf_mapping_update", AsdfValueErr::Unknown, || {
2005        let (Some(file), Some(target)) = (value_file(mapping), value_node(mapping)) else {
2006            return AsdfValueErr::Unknown;
2007        };
2008        let Some(source) = value_node(update) else {
2009            return AsdfValueErr::Unknown;
2010        };
2011        let Some(doc) = file_document_mut(file) else {
2012            return AsdfValueErr::Unknown;
2013        };
2014        if !doc.resolved(target).is_mapping() || !doc.resolved(source).is_mapping() {
2015            return AsdfValueErr::TypeMismatch;
2016        }
2017
2018        let entries = doc.mapping_entries(source).map(<[_]>::to_vec).unwrap_or_default();
2019        for entry in entries {
2020            let Some(key) = doc.resolved(entry.key).as_str().map(str::to_string) else {
2021                continue;
2022            };
2023            doc.mapping_set(target, &key, entry.value);
2024        }
2025        AsdfValueErr::Ok
2026    })
2027}
2028
2029// ---- Counted-string and generic accessors ----------------------------
2030
2031/// Hand out a scalar's text and its length.
2032fn scalar_with_len(
2033    value: *mut AsdfValue,
2034    out: *mut *const c_char,
2035    out_len: *mut usize,
2036) -> AsdfValueErr {
2037    let (Some(doc), Some(node), Some(file)) =
2038        (value_document(value), value_node(value), value_file(value))
2039    else {
2040        return AsdfValueErr::Unknown;
2041    };
2042    let Some(text) = doc.resolved(node).as_str() else {
2043        return AsdfValueErr::TypeMismatch;
2044    };
2045    let ptr = unsafe { &*file }.intern(text);
2046    if ptr.is_null() {
2047        return AsdfValueErr::Oom;
2048    }
2049    if !out.is_null() {
2050        unsafe { write_out(out, ptr) };
2051    }
2052    if !out_len.is_null() {
2053        unsafe { write_out(out_len, text.len()) };
2054    }
2055    AsdfValueErr::Ok
2056}
2057
2058/// Read the value as a counted string.
2059///
2060/// The text is NUL-terminated as well, so `out_len` is a convenience rather
2061/// than the only way to know where it ends.
2062///
2063/// # Safety
2064/// `value` must be null or a valid value handle; `out` and `out_len` writable
2065/// or null. The string is owned by the value's file.
2066#[unsafe(no_mangle)]
2067pub unsafe extern "C" fn asdf_value_as_string(
2068    value: *mut AsdfValue,
2069    out: *mut *const c_char,
2070    out_len: *mut usize,
2071) -> AsdfValueErr {
2072    guard("asdf_value_as_string", AsdfValueErr::Unknown, || {
2073        if !matches!(resolved_of(value), Some(Resolved::String)) {
2074            return AsdfValueErr::TypeMismatch;
2075        }
2076        scalar_with_len(value, out, out_len)
2077    })
2078}
2079
2080/// Read a scalar's raw text and length, whatever its resolved type.
2081///
2082/// # Safety
2083/// See [`asdf_value_as_string`].
2084#[unsafe(no_mangle)]
2085pub unsafe extern "C" fn asdf_value_as_scalar(
2086    value: *mut AsdfValue,
2087    out: *mut *const c_char,
2088    out_len: *mut usize,
2089) -> AsdfValueErr {
2090    guard("asdf_value_as_scalar", AsdfValueErr::Unknown, || {
2091        // See `asdf_value_as_scalar0`.
2092        if value.is_null() {
2093            return AsdfValueErr::Unknown;
2094        }
2095        if !value_is_scalar(value) {
2096            return AsdfValueErr::TypeMismatch;
2097        }
2098        scalar_with_len(value, out, out_len)
2099    })
2100}
2101
2102/// Read a value as the type named by `value_type`.
2103///
2104/// `out` points at storage of the C type matching `value_type`; for
2105/// `ASDF_VALUE_STRING` and `ASDF_VALUE_SCALAR` that is a `const char *`,
2106/// holding a NUL-terminated string.
2107///
2108/// # Safety
2109/// `out` must point at writable storage of the right type and size for
2110/// `value_type`, or be null.
2111#[unsafe(no_mangle)]
2112pub unsafe extern "C" fn asdf_value_as_type(
2113    value: *mut AsdfValue,
2114    value_type: c_int,
2115    out: *mut core::ffi::c_void,
2116) -> AsdfValueErr {
2117    guard("asdf_value_as_type", AsdfValueErr::Unknown, || unsafe {
2118        // See `asdf_value_is_type` on why this arrives as an `int`.
2119        let Some(value_type) = AsdfValueType::from_i32(value_type) else {
2120            return AsdfValueErr::TypeMismatch;
2121        };
2122        // `Unknown` names no type, so the request is for the value itself:
2123        // hand back a copy the caller destroys.
2124        if value_type == AsdfValueType::Unknown {
2125            if value.is_null() {
2126                return AsdfValueErr::Unknown;
2127            }
2128            let copy = asdf_value_copy(value);
2129            if copy.is_null() {
2130                return AsdfValueErr::Oom;
2131            }
2132            if out.is_null() {
2133                crate::file_ffi::asdf_value_destroy(copy);
2134            } else {
2135                *out.cast::<*mut AsdfValue>() = copy;
2136            }
2137            return AsdfValueErr::Ok;
2138        }
2139        match value_type {
2140            AsdfValueType::Int8 => asdf_value_as_int8(value, out.cast()),
2141            AsdfValueType::Int16 => asdf_value_as_int16(value, out.cast()),
2142            AsdfValueType::Int32 => asdf_value_as_int32(value, out.cast()),
2143            AsdfValueType::Int64 => asdf_value_as_int64(value, out.cast()),
2144            AsdfValueType::Uint8 => asdf_value_as_uint8(value, out.cast()),
2145            AsdfValueType::Uint16 => asdf_value_as_uint16(value, out.cast()),
2146            AsdfValueType::Uint32 => asdf_value_as_uint32(value, out.cast()),
2147            AsdfValueType::Uint64 => asdf_value_as_uint64(value, out.cast()),
2148            AsdfValueType::Float => asdf_value_as_float(value, out.cast()),
2149            AsdfValueType::Double => asdf_value_as_double(value, out.cast()),
2150            AsdfValueType::Bool => asdf_value_as_bool(value, out.cast()),
2151            AsdfValueType::String => asdf_value_as_string0(value, out.cast()),
2152            AsdfValueType::Scalar => asdf_value_as_scalar0(value, out.cast()),
2153            AsdfValueType::Mapping => asdf_value_as_mapping(value, out.cast()),
2154            AsdfValueType::Sequence => asdf_value_as_sequence(value, out.cast()),
2155            // Null carries no data: report only whether it matches.
2156            AsdfValueType::Null => {
2157                if asdf_value_is_null(value) {
2158                    AsdfValueErr::Ok
2159                } else {
2160                    AsdfValueErr::TypeMismatch
2161                }
2162            }
2163            AsdfValueType::Unknown | AsdfValueType::Extension => AsdfValueErr::TypeMismatch,
2164        }
2165    })
2166}
2167
2168/// View a mapping as a generic value.
2169///
2170/// The two share a representation, so this is the identity; it exists for
2171/// type-checking on the C side.
2172///
2173/// # Safety
2174/// `mapping` must be null or a valid mapping handle.
2175#[unsafe(no_mangle)]
2176pub unsafe extern "C" fn asdf_value_of_mapping(mapping: *mut AsdfMapping) -> *mut AsdfValue {
2177    mapping
2178}
2179
2180/// View a sequence as a generic value. See [`asdf_value_of_mapping`].
2181///
2182/// # Safety
2183/// `sequence` must be null or a valid sequence handle.
2184#[unsafe(no_mangle)]
2185pub unsafe extern "C" fn asdf_value_of_sequence(sequence: *mut AsdfSequence) -> *mut AsdfValue {
2186    sequence
2187}
2188
2189/// The YAML-pointer path of a value within its document.
2190///
2191/// Null for a value that is not reachable from the root -- one built with
2192/// `asdf_value_of_*` and not yet attached, for instance.
2193///
2194/// # Safety
2195/// `value` must be null or a valid value handle. The string is owned by the
2196/// value's file.
2197#[unsafe(no_mangle)]
2198pub unsafe extern "C" fn asdf_value_path(value: *mut AsdfValue) -> *const c_char {
2199    guard("asdf_value_path", core::ptr::null(), || {
2200        let (Some(doc), Some(node), Some(file)) =
2201            (value_document(value), value_node(value), value_file(value))
2202        else {
2203            return core::ptr::null();
2204        };
2205        match doc.path_of(node) {
2206            Some(path) => unsafe { &*file }.intern(&path),
2207            None => core::ptr::null(),
2208        }
2209    })
2210}
2211
2212/// The container holding a value, or null for the root or a detached value.
2213///
2214/// # Safety
2215/// `value` must be null or a valid value handle. The result must be released
2216/// with `asdf_value_destroy`.
2217#[unsafe(no_mangle)]
2218pub unsafe extern "C" fn asdf_value_parent(value: *mut AsdfValue) -> *mut AsdfValue {
2219    guard("asdf_value_parent", core::ptr::null_mut(), || {
2220        let (Some(doc), Some(node), Some(file)) =
2221            (value_document(value), value_node(value), value_file(value))
2222        else {
2223            return core::ptr::null_mut();
2224        };
2225        match doc.parent_of(node) {
2226            Some(parent) => make_value(file, parent),
2227            None => core::ptr::null_mut(),
2228        }
2229    })
2230}
2231
2232// ---- Tree traversal --------------------------------------------------
2233
2234/// A predicate over a value, as C passes it in.
2235pub type AsdfValuePred = Option<unsafe extern "C" fn(*mut AsdfValue) -> bool>;
2236
2237/// A find iterator. The public head must stay first; see [`MappingIter`].
2238#[repr(C)]
2239struct FindIter {
2240    /// The public head, which C casts to.
2241    public: crate::types::asdf_find_iter_t,
2242    file: *mut AsdfFile,
2243    /// Nodes still to visit, each with the depth at which it was reached.
2244    queue: alloc::collections::VecDeque<(NodeId, i64)>,
2245    pred: AsdfValuePred,
2246    descend_pred: AsdfValuePred,
2247    depth_first: bool,
2248    max_depth: i64,
2249    /// Nodes already queued, so an aliased subtree is visited once.
2250    seen: Vec<NodeId>,
2251}
2252
2253impl FindIter {
2254    /// Push a node's children in the order the traversal wants them.
2255    fn enqueue_children(&mut self, doc: &asdf_core::yaml::Document, node: NodeId, depth: i64) {
2256        // `max_depth` counts containers entered *below* the root, so a
2257        // container at depth `d` may be opened while `d <= max_depth`: with
2258        // a limit of 1 the root's children are visited and one container
2259        // among them is entered, but nothing inside that one.
2260        if self.max_depth >= 0 && depth > self.max_depth {
2261            return;
2262        }
2263        let resolved = doc.resolve(node);
2264        let mut children: Vec<NodeId> = Vec::new();
2265        match &doc.node(resolved).data {
2266            NodeData::Mapping { entries, .. } => {
2267                children.extend(entries.iter().map(|e| e.value));
2268            }
2269            NodeData::Sequence { items, .. } => children.extend(items.iter().copied()),
2270            _ => return,
2271        }
2272        if self.depth_first {
2273            // Pushed onto the front, so reverse to keep document order.
2274            for child in children.into_iter().rev() {
2275                self.queue.push_front((child, depth + 1));
2276            }
2277        } else {
2278            for child in children {
2279                self.queue.push_back((child, depth + 1));
2280            }
2281        }
2282    }
2283
2284    /// Whether the traversal should descend into this container.
2285    ///
2286    /// The search's own root is always descended: `descend_pred` selects
2287    /// which containers *found along the way* are entered, and refusing the
2288    /// root would make a search from a mapping with
2289    /// `asdf_find_descend_sequence_only` find nothing at all.
2290    fn should_descend(&self, node: NodeId, is_root: bool) -> bool {
2291        if is_root {
2292            return true;
2293        }
2294        let Some(pred) = self.descend_pred else {
2295            return true;
2296        };
2297        // The predicate takes a value handle, so one is made for the call and
2298        // released straight after.
2299        let handle = make_value(self.file, node);
2300        let verdict = unsafe { pred(handle) };
2301        unsafe { crate::file_ffi::asdf_value_destroy(handle) };
2302        verdict
2303    }
2304
2305    /// Release the value handed out for the previous step.
2306    fn clear_current(&mut self) {
2307        if !self.public.value.is_null() {
2308            unsafe { crate::file_ffi::asdf_value_destroy(self.public.value.cast::<AsdfValue>()) };
2309            self.public.value = core::ptr::null_mut();
2310        }
2311    }
2312
2313    /// Advance to the next match, or `false` when the traversal is done.
2314    fn step(&mut self) -> bool {
2315        self.clear_current();
2316        let Some(doc) = crate::file_ffi::file_document(self.file) else {
2317            return false;
2318        };
2319        while let Some((node, depth)) = self.queue.pop_front() {
2320            let resolved = doc.resolve(node);
2321            if self.seen.contains(&resolved) {
2322                continue;
2323            }
2324            self.seen.push(resolved);
2325
2326            let is_container = doc.node(resolved).is_mapping() || doc.node(resolved).is_sequence();
2327            if is_container && self.should_descend(node, depth == 0) {
2328                self.enqueue_children(doc, node, depth);
2329            }
2330
2331            let matched = match self.pred {
2332                Some(pred) => {
2333                    let handle = make_value(self.file, node);
2334                    let verdict = unsafe { pred(handle) };
2335                    if verdict {
2336                        self.public.value = handle.cast();
2337                        return true;
2338                    }
2339                    unsafe { crate::file_ffi::asdf_value_destroy(handle) };
2340                    false
2341                }
2342                // A null predicate matches everything, as C's convention has
2343                // it for an omitted filter.
2344                None => {
2345                    self.public.value = make_value(self.file, node).cast();
2346                    return true;
2347                }
2348            };
2349            let _ = matched;
2350        }
2351        false
2352    }
2353}
2354
2355/// Build a find iterator over `root`.
2356fn find_iter_new(
2357    root: *mut AsdfValue,
2358    pred: AsdfValuePred,
2359    depth_first: bool,
2360    descend_pred: AsdfValuePred,
2361    max_depth: i64,
2362) -> *mut FindIter {
2363    let (Some(file), Some(node)) = (value_file(root), value_node(root)) else {
2364        return core::ptr::null_mut();
2365    };
2366    let mut queue = alloc::collections::VecDeque::new();
2367    queue.push_back((node, 0i64));
2368    Box::into_raw(Box::new(FindIter {
2369        public: crate::types::asdf_find_iter_t { value: core::ptr::null_mut() },
2370        file,
2371        queue,
2372        pred,
2373        descend_pred,
2374        depth_first,
2375        max_depth,
2376        seen: Vec::new(),
2377    }))
2378}
2379
2380/// Find the first value at or below `root` matching `pred`, breadth-first.
2381///
2382/// # Safety
2383/// `root` must be a valid value handle. The result must be released with
2384/// `asdf_value_destroy`.
2385#[unsafe(no_mangle)]
2386pub unsafe extern "C" fn asdf_value_find(
2387    root: *mut AsdfValue,
2388    pred: AsdfValuePred,
2389) -> *mut AsdfValue {
2390    value_find_ex(root, pred, false, None, -1)
2391}
2392
2393/// Find the first match with control over traversal order and depth.
2394///
2395/// `depth_first` selects the order, `descend_pred` filters which containers
2396/// are entered (null enters all), and `max_depth` of -1 means no limit.
2397///
2398/// # Safety
2399/// See [`asdf_value_find`].
2400#[unsafe(no_mangle)]
2401pub unsafe extern "C" fn asdf_value_find_ex(
2402    root: *mut AsdfValue,
2403    pred: AsdfValuePred,
2404    depth_first: bool,
2405    descend_pred: AsdfValuePred,
2406    max_depth: i64,
2407) -> *mut AsdfValue {
2408    guard("asdf_value_find_ex", core::ptr::null_mut(), || {
2409        value_find_ex(root, pred, depth_first, descend_pred, max_depth)
2410    })
2411}
2412
2413/// Safe internal form of [`asdf_value_find_ex`].
2414///
2415/// The exported entry point is `unsafe extern "C"`, so calling it from
2416/// inside the crate would need an `unsafe` block at every site to assert a
2417/// contract the crate itself is upholding. Callers use this instead.
2418pub(crate) fn value_find_ex(
2419    root: *mut AsdfValue,
2420    pred: AsdfValuePred,
2421    depth_first: bool,
2422    descend_pred: AsdfValuePred,
2423    max_depth: i64,
2424) -> *mut AsdfValue {
2425    let iter = find_iter_new(root, pred, depth_first, descend_pred, max_depth);
2426    if iter.is_null() {
2427        return core::ptr::null_mut();
2428    }
2429    let mut boxed = unsafe { Box::from_raw(iter) };
2430    if !boxed.step() {
2431        return core::ptr::null_mut();
2432    }
2433    // Hand the match to the caller rather than letting the drop free it.
2434    let found = boxed.public.value.cast::<AsdfValue>();
2435    boxed.public.value = core::ptr::null_mut();
2436    found
2437}
2438
2439/// Start a breadth-first search yielding every value matching `pred`.
2440///
2441/// # Safety
2442/// `root` must be a valid container value handle. The iterator is released by
2443/// running it to exhaustion or with [`asdf_find_iter_destroy`].
2444#[unsafe(no_mangle)]
2445pub unsafe extern "C" fn asdf_find_iter_init(
2446    root: *mut AsdfValue,
2447    pred: AsdfValuePred,
2448) -> *mut crate::types::asdf_find_iter_t {
2449    find_iter_init_ex(root, pred, false, None, -1)
2450}
2451
2452/// Start a search with control over traversal order and depth.
2453///
2454/// # Safety
2455/// See [`asdf_find_iter_init`].
2456#[unsafe(no_mangle)]
2457pub unsafe extern "C" fn asdf_find_iter_init_ex(
2458    root: *mut AsdfValue,
2459    pred: AsdfValuePred,
2460    depth_first: bool,
2461    descend_pred: AsdfValuePred,
2462    max_depth: i64,
2463) -> *mut crate::types::asdf_find_iter_t {
2464    guard("asdf_find_iter_init_ex", core::ptr::null_mut(), || {
2465        find_iter_init_ex(root, pred, depth_first, descend_pred, max_depth)
2466    })
2467}
2468
2469/// Safe internal form of [`asdf_find_iter_init_ex`].
2470///
2471/// The exported entry point is `unsafe extern "C"`, so calling it from
2472/// inside the crate would need an `unsafe` block at every site to assert a
2473/// contract the crate itself is upholding. Callers use this instead.
2474pub(crate) fn find_iter_init_ex(
2475    root: *mut AsdfValue,
2476    pred: AsdfValuePred,
2477    depth_first: bool,
2478    descend_pred: AsdfValuePred,
2479    max_depth: i64,
2480) -> *mut crate::types::asdf_find_iter_t {
2481    find_iter_new(root, pred, depth_first, descend_pred, max_depth)
2482        .cast::<crate::types::asdf_find_iter_t>()
2483}
2484
2485/// Advance a find iterator.
2486///
2487/// On exhaustion the iterator is freed and `*iter` set to null, so the
2488/// trailing `destroy` only matters when the loop breaks early.
2489///
2490/// # Safety
2491/// `iter` must point at a handle from [`asdf_find_iter_init`], or be null.
2492#[unsafe(no_mangle)]
2493pub unsafe extern "C" fn asdf_value_find_iter_next(
2494    iter: *mut *mut crate::types::asdf_find_iter_t,
2495) -> bool {
2496    guard("asdf_value_find_iter_next", false, || {
2497        if iter.is_null() {
2498            return false;
2499        }
2500        let current = unsafe { *iter };
2501        if current.is_null() {
2502            return false;
2503        }
2504        let state = unsafe { &mut *current.cast::<FindIter>() };
2505        if state.step() {
2506            return true;
2507        }
2508        find_iter_destroy(current);
2509        unsafe { write_out(iter, core::ptr::null_mut()) };
2510        false
2511    })
2512}
2513
2514/// Release an iterator abandoned before exhaustion.
2515///
2516/// # Safety
2517/// `iter` must be null or a handle from [`asdf_find_iter_init`] that has not
2518/// already been freed.
2519#[unsafe(no_mangle)]
2520pub unsafe extern "C" fn asdf_find_iter_destroy(iter: *mut crate::types::asdf_find_iter_t) {
2521    guard("asdf_find_iter_destroy", (), || find_iter_destroy(iter))
2522}
2523
2524/// Safe internal form of [`asdf_find_iter_destroy`].
2525///
2526/// The exported entry point is `unsafe extern "C"`, so calling it from
2527/// inside the crate would need an `unsafe` block at every site to assert a
2528/// contract the crate itself is upholding. Callers use this instead.
2529pub(crate) fn find_iter_destroy(iter: *mut crate::types::asdf_find_iter_t) {
2530    if iter.is_null() {
2531        return;
2532    }
2533    let mut boxed = unsafe { Box::from_raw(iter.cast::<FindIter>()) };
2534    boxed.clear_current();
2535}
2536
2537#[cfg(test)]
2538mod tests {
2539    use super::*;
2540    use crate::file_ffi::{asdf_close, asdf_get_value, asdf_open_mem_ex, asdf_value_destroy};
2541
2542    fn sample() -> Vec<u8> {
2543        let mut buf = Vec::new();
2544        buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n");
2545        buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
2546        buf.extend_from_slice(
2547            b"a: 1\nb: two\nc: 3.5\nflag: true\nnothing: null\n\
2548              list: [10, 20, 30]\nnested:\n  inner: deep\n",
2549        );
2550        buf.extend_from_slice(b"...\n");
2551        buf
2552    }
2553
2554    struct Handle(*mut AsdfFile);
2555    impl Drop for Handle {
2556        fn drop(&mut self) {
2557            unsafe { asdf_close(self.0) };
2558        }
2559    }
2560
2561    fn open() -> Handle {
2562        let bytes = sample();
2563        let f =
2564            unsafe { asdf_open_mem_ex(bytes.as_ptr().cast(), bytes.len(), core::ptr::null_mut()) };
2565        assert!(!f.is_null());
2566        Handle(f)
2567    }
2568
2569    fn value_at(h: &Handle, path: &str) -> *mut AsdfValue {
2570        let c = CString::new(path).unwrap();
2571        let v = unsafe { asdf_get_value(h.0, c.as_ptr()) };
2572        assert!(!v.is_null(), "no value at {path}");
2573        v
2574    }
2575
2576    /// A predicate for the find tests: matches any string scalar.
2577    unsafe extern "C" fn is_a_string(value: *mut AsdfValue) -> bool {
2578        unsafe { asdf_value_is_string(value) }
2579    }
2580
2581    /// Matches the scalar `deep`, which sits two levels down.
2582    unsafe extern "C" fn is_deep(value: *mut AsdfValue) -> bool {
2583        let mut out = core::ptr::null();
2584        if unsafe { asdf_value_as_string0(value, &mut out) } != AsdfValueErr::Ok {
2585            return false;
2586        }
2587        let text = unsafe { CStr::from_ptr(out) };
2588        text == c"deep"
2589    }
2590
2591    #[test]
2592    fn find_walks_breadth_first_by_default() {
2593        let h = open();
2594        let root = value_at(&h, "");
2595        // `b: two` is at depth 1; `nested/inner: deep` at depth 2. A
2596        // breadth-first walk reaches the shallower one first.
2597        let found = unsafe { asdf_value_find(root, Some(is_a_string)) };
2598        assert!(!found.is_null());
2599        let mut text = core::ptr::null();
2600        assert_eq!(unsafe { asdf_value_as_string0(found, &mut text) }, AsdfValueErr::Ok);
2601        assert_eq!(unsafe { CStr::from_ptr(text) }, c"two");
2602        unsafe { asdf_value_destroy(found) };
2603        unsafe { asdf_value_destroy(root) };
2604    }
2605
2606    /// `max_depth` counts containers entered *below* the root: with a limit
2607    /// of 0 the root's own children are visited and nothing under them.
2608    #[test]
2609    fn find_respects_max_depth() {
2610        let h = open();
2611        let root = value_at(&h, "");
2612
2613        // `deep` lives inside `nested`, so entering `nested` is the one
2614        // descent a limit of 0 forbids.
2615        let shallow = unsafe { asdf_value_find_ex(root, Some(is_deep), false, None, 0) };
2616        assert!(shallow.is_null());
2617
2618        // A limit of 1 allows exactly that descent.
2619        let found = unsafe { asdf_value_find_ex(root, Some(is_deep), false, None, 1) };
2620        assert!(!found.is_null());
2621        unsafe { asdf_value_destroy(found) };
2622
2623        let deep = unsafe { asdf_value_find_ex(root, Some(is_deep), false, None, -1) };
2624        assert!(!deep.is_null());
2625        unsafe { asdf_value_destroy(deep) };
2626        unsafe { asdf_value_destroy(root) };
2627    }
2628
2629    #[test]
2630    fn find_iterates_every_match() {
2631        let h = open();
2632        let root = value_at(&h, "");
2633
2634        let mut iter = unsafe { asdf_find_iter_init(root, Some(is_a_string)) };
2635        let mut seen = Vec::new();
2636        while unsafe { asdf_value_find_iter_next(&mut iter) } {
2637            let current = unsafe { &*iter }.value.cast::<AsdfValue>();
2638            let mut text = core::ptr::null();
2639            assert_eq!(unsafe { asdf_value_as_string0(current, &mut text) }, AsdfValueErr::Ok);
2640            seen.push(unsafe { CStr::from_ptr(text) }.to_string_lossy().into_owned());
2641        }
2642        // The iterator frees itself on exhaustion.
2643        assert!(iter.is_null());
2644
2645        // Keys are not visited, only values: `two` and `deep`.
2646        assert_eq!(seen, vec!["two".to_string(), "deep".to_string()]);
2647        unsafe { asdf_value_destroy(root) };
2648    }
2649
2650    #[test]
2651    fn abandoning_a_find_iterator_is_safe() {
2652        let h = open();
2653        let root = value_at(&h, "");
2654        let mut iter = unsafe { asdf_find_iter_init(root, None) };
2655        assert!(unsafe { asdf_value_find_iter_next(&mut iter) });
2656        // Break out early, which is exactly when `destroy` has work to do.
2657        unsafe { asdf_find_iter_destroy(iter) };
2658        unsafe { asdf_value_destroy(root) };
2659    }
2660
2661    #[test]
2662    fn values_report_their_path_and_parent() {
2663        let h = open();
2664        let inner = value_at(&h, "nested/inner");
2665        // Paths are absolute, as libasdf reports them.
2666        let path = unsafe { asdf_value_path(inner) };
2667        assert!(!path.is_null());
2668        assert_eq!(unsafe { CStr::from_ptr(path) }, c"/nested/inner");
2669
2670        let parent = unsafe { asdf_value_parent(inner) };
2671        assert!(!parent.is_null());
2672        let parent_path = unsafe { asdf_value_path(parent) };
2673        assert_eq!(unsafe { CStr::from_ptr(parent_path) }, c"/nested");
2674
2675        // The root has no parent, and its path is `/`.
2676        let root = value_at(&h, "");
2677        assert!(unsafe { asdf_value_parent(root) }.is_null());
2678        assert_eq!(unsafe { CStr::from_ptr(asdf_value_path(root)) }, c"/");
2679
2680        for v in [inner, parent, root] {
2681            unsafe { asdf_value_destroy(v) };
2682        }
2683    }
2684
2685    /// A sequence index is written plainly, not bracketed: that is the form
2686    /// libasdf reports, and it reads back as an index because the container
2687    /// it addresses is a sequence.
2688    #[test]
2689    fn sequence_elements_report_plain_indices() {
2690        let h = open();
2691        let item = value_at(&h, "list/1");
2692        assert_eq!(unsafe { CStr::from_ptr(asdf_value_path(item)) }, c"/list/1");
2693
2694        // And the reported path finds the same value again.
2695        let again = value_at(&h, "/list/1");
2696        assert_eq!(value_node(again), value_node(item));
2697
2698        unsafe { asdf_value_destroy(again) };
2699        unsafe { asdf_value_destroy(item) };
2700    }
2701
2702    #[test]
2703    fn counted_string_accessors_report_lengths() {
2704        let h = open();
2705        let b = value_at(&h, "b");
2706        let mut text = core::ptr::null();
2707        let mut len = 0usize;
2708        assert_eq!(unsafe { asdf_value_as_string(b, &mut text, &mut len) }, AsdfValueErr::Ok);
2709        assert_eq!(len, 3);
2710        assert_eq!(unsafe { CStr::from_ptr(text) }, c"two");
2711
2712        // `as_scalar` works on a value that is not a string, `as_string` does
2713        // not.
2714        let a = value_at(&h, "a");
2715        assert_eq!(
2716            unsafe { asdf_value_as_string(a, &mut text, &mut len) },
2717            AsdfValueErr::TypeMismatch
2718        );
2719        assert_eq!(unsafe { asdf_value_as_scalar(a, &mut text, &mut len) }, AsdfValueErr::Ok);
2720        assert_eq!(len, 1);
2721        assert_eq!(unsafe { CStr::from_ptr(text) }, c"1");
2722
2723        for v in [a, b] {
2724            unsafe { asdf_value_destroy(v) };
2725        }
2726    }
2727
2728    #[test]
2729    fn as_type_dispatches_on_the_requested_type() {
2730        let h = open();
2731        let a = value_at(&h, "a");
2732        let mut narrow: i32 = 0;
2733        assert_eq!(
2734            unsafe {
2735                asdf_value_as_type(
2736                    a,
2737                    AsdfValueType::Int32 as c_int,
2738                    core::ptr::from_mut(&mut narrow).cast(),
2739                )
2740            },
2741            AsdfValueErr::Ok
2742        );
2743        assert_eq!(narrow, 1);
2744
2745        // A string request against an integer is a type mismatch.
2746        let mut text = core::ptr::null::<c_char>();
2747        assert_eq!(
2748            unsafe {
2749                asdf_value_as_type(
2750                    a,
2751                    AsdfValueType::String as c_int,
2752                    core::ptr::from_mut(&mut text).cast(),
2753                )
2754            },
2755            AsdfValueErr::TypeMismatch
2756        );
2757
2758        let nothing = value_at(&h, "nothing");
2759        assert_eq!(
2760            unsafe {
2761                asdf_value_as_type(nothing, AsdfValueType::Null as c_int, core::ptr::null_mut())
2762            },
2763            AsdfValueErr::Ok
2764        );
2765
2766        for v in [a, nothing] {
2767            unsafe { asdf_value_destroy(v) };
2768        }
2769    }
2770
2771    #[test]
2772    fn typed_container_setters_build_a_tree() {
2773        use crate::file_ffi::asdf_value_destroy as destroy;
2774
2775        // `asdf_open(NULL)` -- a new, empty file open for writing.
2776        let file = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
2777        assert!(!file.is_null());
2778        let handle = Handle(file);
2779
2780        let mapping = unsafe { asdf_mapping_create(handle.0) };
2781        assert!(!mapping.is_null());
2782        let key = CString::new("count").unwrap();
2783        assert_eq!(unsafe { asdf_mapping_set_int32(mapping, key.as_ptr(), -7) }, AsdfValueErr::Ok);
2784        let name = CString::new("name").unwrap();
2785        let value = CString::new("probe").unwrap();
2786        assert_eq!(
2787            unsafe { asdf_mapping_set_string0(mapping, name.as_ptr(), value.as_ptr()) },
2788            AsdfValueErr::Ok
2789        );
2790        let missing = CString::new("missing").unwrap();
2791        assert_eq!(unsafe { asdf_mapping_set_null(mapping, missing.as_ptr()) }, AsdfValueErr::Ok);
2792        assert_eq!(unsafe { asdf_mapping_size(mapping) }, 3);
2793
2794        let read_back = unsafe { asdf_mapping_get(mapping, key.as_ptr()) };
2795        assert!(!read_back.is_null());
2796        let mut got: i32 = 0;
2797        assert_eq!(unsafe { asdf_value_as_int32(read_back, &mut got) }, AsdfValueErr::Ok);
2798        assert_eq!(got, -7);
2799        unsafe { destroy(read_back) };
2800
2801        let numbers: [f64; 3] = [1.5, 2.5, 3.5];
2802        let sequence = unsafe { asdf_sequence_of_double(handle.0, numbers.as_ptr(), 3) };
2803        assert!(!sequence.is_null());
2804        assert_eq!(unsafe { asdf_sequence_size(sequence) }, 3);
2805        let second = unsafe { asdf_sequence_get(sequence, 1) };
2806        let mut d = 0.0f64;
2807        assert_eq!(unsafe { asdf_value_as_double(second, &mut d) }, AsdfValueErr::Ok);
2808        assert!((d - 2.5).abs() < f64::EPSILON);
2809        unsafe { destroy(second) };
2810
2811        assert_eq!(unsafe { asdf_sequence_append_bool(sequence, true) }, AsdfValueErr::Ok);
2812        assert_eq!(unsafe { asdf_sequence_size(sequence) }, 4);
2813
2814        unsafe { destroy(sequence) };
2815        unsafe { destroy(mapping) };
2816    }
2817
2818    #[test]
2819    fn mapping_update_merges_and_replaces() {
2820        use crate::file_ffi::asdf_value_destroy as destroy;
2821
2822        let file = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
2823        let handle = Handle(file);
2824
2825        let target = unsafe { asdf_mapping_create(handle.0) };
2826        let update = unsafe { asdf_mapping_create(handle.0) };
2827        let shared = CString::new("shared").unwrap();
2828        let only_target = CString::new("target-only").unwrap();
2829        let only_update = CString::new("update-only").unwrap();
2830
2831        unsafe { asdf_mapping_set_int32(target, shared.as_ptr(), 1) };
2832        unsafe { asdf_mapping_set_int32(target, only_target.as_ptr(), 2) };
2833        unsafe { asdf_mapping_set_int32(update, shared.as_ptr(), 99) };
2834        unsafe { asdf_mapping_set_int32(update, only_update.as_ptr(), 3) };
2835
2836        assert_eq!(unsafe { asdf_mapping_update(target, update) }, AsdfValueErr::Ok);
2837        assert_eq!(unsafe { asdf_mapping_size(target) }, 3);
2838
2839        let merged = unsafe { asdf_mapping_get(target, shared.as_ptr()) };
2840        let mut got: i32 = 0;
2841        assert_eq!(unsafe { asdf_value_as_int32(merged, &mut got) }, AsdfValueErr::Ok);
2842        assert_eq!(got, 99, "the update's value should replace the target's");
2843        unsafe { destroy(merged) };
2844
2845        // A shallow copy carries the same entries.
2846        let copy = unsafe { asdf_mapping_copy(target) };
2847        assert!(!copy.is_null());
2848        assert_eq!(unsafe { asdf_mapping_size(copy) }, 3);
2849
2850        for v in [copy, update, target] {
2851            unsafe { destroy(v) };
2852        }
2853    }
2854
2855    #[test]
2856    fn recognises_containers() {
2857        let h = open();
2858        let root = value_at(&h, "");
2859        assert!(unsafe { asdf_value_is_mapping(root) });
2860        assert!(unsafe { asdf_value_is_container(root) });
2861        assert_eq!(unsafe { asdf_mapping_size(root) }, 7);
2862
2863        let list = value_at(&h, "list");
2864        assert!(unsafe { asdf_value_is_sequence(list) });
2865        assert_eq!(unsafe { asdf_sequence_size(list) }, 3);
2866
2867        let scalar = value_at(&h, "a");
2868        assert!(!unsafe { asdf_value_is_container(scalar) });
2869        assert_eq!(unsafe { asdf_mapping_size(scalar) }, -1);
2870        assert_eq!(unsafe { asdf_sequence_size(scalar) }, -1);
2871
2872        for v in [root, list, scalar] {
2873            unsafe { asdf_value_destroy(v) };
2874        }
2875    }
2876
2877    #[test]
2878    fn mapping_lookup_and_typed_reads() {
2879        let h = open();
2880        let root = value_at(&h, "");
2881
2882        let key = CString::new("a").unwrap();
2883        let a = unsafe { asdf_mapping_get(root, key.as_ptr()) };
2884        assert!(!a.is_null());
2885        let mut n: i64 = 0;
2886        assert_eq!(unsafe { asdf_value_as_int64(a, &mut n) }, AsdfValueErr::Ok);
2887        assert_eq!(n, 1);
2888        assert!(unsafe { asdf_value_is_int(a) });
2889
2890        let missing = CString::new("nope").unwrap();
2891        assert!(unsafe { asdf_mapping_get(root, missing.as_ptr()) }.is_null());
2892
2893        unsafe { asdf_value_destroy(a) };
2894        unsafe { asdf_value_destroy(root) };
2895    }
2896
2897    #[test]
2898    fn iterates_a_mapping_in_order() {
2899        let h = open();
2900        let root = value_at(&h, "");
2901
2902        let mut iter = unsafe { asdf_mapping_iter_init(root) };
2903        assert!(!iter.is_null());
2904
2905        let mut keys = Vec::new();
2906        while unsafe { asdf_mapping_iter_next(&mut iter) } {
2907            let head = unsafe { &*iter };
2908            assert!(!head.key.is_null());
2909            keys.push(unsafe { CStr::from_ptr(head.key) }.to_str().unwrap().to_string());
2910            assert!(!head.value.is_null());
2911        }
2912        // The loop's end nulls the caller's pointer, so cleanup is a no-op.
2913        assert!(iter.is_null(), "the iterator must null itself at the end");
2914        unsafe { asdf_mapping_iter_destroy(iter) };
2915
2916        assert_eq!(keys, ["a", "b", "c", "flag", "nothing", "list", "nested"]);
2917        unsafe { asdf_value_destroy(root) };
2918    }
2919
2920    #[test]
2921    fn iterates_a_mapping_in_reverse() {
2922        let h = open();
2923        let root = value_at(&h, "");
2924
2925        let mut iter = unsafe { asdf_mapping_reverse_iter_init(root) };
2926        let mut keys = Vec::new();
2927        while unsafe { asdf_mapping_iter_next(&mut iter) } {
2928            let head = unsafe { &*iter };
2929            keys.push(unsafe { CStr::from_ptr(head.key) }.to_str().unwrap().to_string());
2930        }
2931        assert_eq!(keys, ["nested", "list", "nothing", "flag", "c", "b", "a"]);
2932        unsafe { asdf_value_destroy(root) };
2933    }
2934
2935    #[test]
2936    fn iterates_a_sequence_with_indices() {
2937        let h = open();
2938        let list = value_at(&h, "list");
2939
2940        let mut iter = unsafe { asdf_sequence_iter_init(list) };
2941        let mut seen = Vec::new();
2942        while unsafe { asdf_sequence_iter_next(&mut iter) } {
2943            let head = unsafe { &*iter };
2944            let mut n: i64 = 0;
2945            unsafe { asdf_value_as_int64(head.value.cast(), &mut n) };
2946            seen.push((head.index, n));
2947        }
2948        assert_eq!(seen, [(0, 10), (1, 20), (2, 30)]);
2949        assert!(iter.is_null());
2950        unsafe { asdf_value_destroy(list) };
2951    }
2952
2953    #[test]
2954    fn a_reversed_sequence_keeps_original_indices() {
2955        let h = open();
2956        let list = value_at(&h, "list");
2957
2958        let mut iter = unsafe { asdf_sequence_reverse_iter_init(list) };
2959        let mut seen = Vec::new();
2960        while unsafe { asdf_sequence_iter_next(&mut iter) } {
2961            let head = unsafe { &*iter };
2962            seen.push(head.index);
2963        }
2964        assert_eq!(seen, [2, 1, 0]);
2965        unsafe { asdf_value_destroy(list) };
2966    }
2967
2968    #[test]
2969    fn breaking_out_of_a_loop_leaves_the_iterator_to_destroy() {
2970        let h = open();
2971        let root = value_at(&h, "");
2972
2973        let mut iter = unsafe { asdf_mapping_iter_init(root) };
2974        let mut count = 0;
2975        while unsafe { asdf_mapping_iter_next(&mut iter) } {
2976            count += 1;
2977            if count == 2 {
2978                break;
2979            }
2980        }
2981        // The early break leaves a live iterator; destroying it must be
2982        // clean, including the value handle it still owns.
2983        assert!(!iter.is_null());
2984        unsafe { asdf_mapping_iter_destroy(iter) };
2985        unsafe { asdf_value_destroy(root) };
2986    }
2987
2988    #[test]
2989    fn sequence_indexing_accepts_negatives() {
2990        let h = open();
2991        let list = value_at(&h, "list");
2992
2993        let last = unsafe { asdf_sequence_get(list, -1) };
2994        assert!(!last.is_null());
2995        let mut n: i64 = 0;
2996        unsafe { asdf_value_as_int64(last, &mut n) };
2997        assert_eq!(n, 30);
2998
2999        assert!(unsafe { asdf_sequence_get(list, 3) }.is_null());
3000        unsafe { asdf_value_destroy(last) };
3001        unsafe { asdf_value_destroy(list) };
3002    }
3003
3004    #[test]
3005    fn typed_predicates_and_reads() {
3006        let h = open();
3007
3008        let b = value_at(&h, "b");
3009        assert!(unsafe { asdf_value_is_string(b) });
3010        let mut s: *const c_char = core::ptr::null();
3011        assert_eq!(unsafe { asdf_value_as_string0(b, &mut s) }, AsdfValueErr::Ok);
3012        assert_eq!(unsafe { CStr::from_ptr(s) }.to_str().unwrap(), "two");
3013
3014        let c = value_at(&h, "c");
3015        assert!(unsafe { asdf_value_is_double(c) });
3016        let mut d = 0f64;
3017        assert_eq!(unsafe { asdf_value_as_double(c, &mut d) }, AsdfValueErr::Ok);
3018        assert_eq!(d, 3.5);
3019
3020        let flag = value_at(&h, "flag");
3021        assert!(unsafe { asdf_value_is_bool(flag) });
3022        let mut bl = false;
3023        assert_eq!(unsafe { asdf_value_as_bool(flag, &mut bl) }, AsdfValueErr::Ok);
3024        assert!(bl);
3025
3026        let nothing = value_at(&h, "nothing");
3027        assert!(unsafe { asdf_value_is_null(nothing) });
3028
3029        for v in [b, c, flag, nothing] {
3030            unsafe { asdf_value_destroy(v) };
3031        }
3032    }
3033
3034    #[test]
3035    fn reading_the_wrong_type_is_a_mismatch_not_a_guess() {
3036        let h = open();
3037        let b = value_at(&h, "b");
3038        let mut n: i64 = 0;
3039        assert_eq!(unsafe { asdf_value_as_int64(b, &mut n) }, AsdfValueErr::TypeMismatch);
3040        unsafe { asdf_value_destroy(b) };
3041    }
3042
3043    #[test]
3044    fn as_mapping_and_as_sequence_check_the_type() {
3045        let h = open();
3046        let root = value_at(&h, "");
3047        let list = value_at(&h, "list");
3048
3049        let mut out: *mut AsdfMapping = core::ptr::null_mut();
3050        assert_eq!(unsafe { asdf_value_as_mapping(root, &mut out) }, AsdfValueErr::Ok);
3051        assert_eq!(out, root);
3052        assert_eq!(unsafe { asdf_value_as_mapping(list, &mut out) }, AsdfValueErr::TypeMismatch);
3053
3054        let mut seq: *mut AsdfSequence = core::ptr::null_mut();
3055        assert_eq!(unsafe { asdf_value_as_sequence(list, &mut seq) }, AsdfValueErr::Ok);
3056        assert_eq!(unsafe { asdf_value_as_sequence(root, &mut seq) }, AsdfValueErr::TypeMismatch);
3057
3058        unsafe { asdf_value_destroy(root) };
3059        unsafe { asdf_value_destroy(list) };
3060    }
3061
3062    #[test]
3063    fn copies_are_independent_handles_to_the_same_node() {
3064        let h = open();
3065        let a = value_at(&h, "a");
3066        let copy = unsafe { asdf_value_copy(a) };
3067        assert!(!copy.is_null());
3068        assert_ne!(copy, a);
3069
3070        // Destroying one must leave the other usable.
3071        unsafe { asdf_value_destroy(a) };
3072        let mut n: i64 = 0;
3073        assert_eq!(unsafe { asdf_value_as_int64(copy, &mut n) }, AsdfValueErr::Ok);
3074        assert_eq!(n, 1);
3075        unsafe { asdf_value_destroy(copy) };
3076    }
3077
3078    #[test]
3079    fn null_handles_are_tolerated_everywhere() {
3080        let null = core::ptr::null_mut();
3081        assert!(!unsafe { asdf_value_is_mapping(null) });
3082        assert!(!unsafe { asdf_value_is_sequence(null) });
3083        assert!(!unsafe { asdf_value_is_container(null) });
3084        assert_eq!(unsafe { asdf_container_size(null) }, -1);
3085        assert_eq!(unsafe { asdf_mapping_size(null) }, -1);
3086        assert_eq!(unsafe { asdf_sequence_size(null) }, -1);
3087        assert!(unsafe { asdf_mapping_iter_init(null) }.is_null());
3088        assert!(unsafe { asdf_sequence_iter_init(null) }.is_null());
3089        assert!(!unsafe { asdf_mapping_iter_next(core::ptr::null_mut()) });
3090        assert!(!unsafe { asdf_sequence_iter_next(core::ptr::null_mut()) });
3091        unsafe { asdf_mapping_iter_destroy(core::ptr::null_mut()) };
3092        unsafe { asdf_sequence_iter_destroy(core::ptr::null_mut()) };
3093        assert!(unsafe { asdf_value_copy(null) }.is_null());
3094        assert!(unsafe { asdf_value_file(null) }.is_null());
3095    }
3096
3097    /// The cast between the public head and the implementation is only
3098    /// sound while the head sits at offset 0. Pin it, so removing the
3099    /// `repr(C)` fails here rather than corrupting a C caller's read.
3100    #[test]
3101    fn iterator_public_heads_sit_at_offset_zero() {
3102        use std::mem::offset_of;
3103        assert_eq!(offset_of!(MappingIter, public), 0);
3104        assert_eq!(offset_of!(SequenceIter, public), 0);
3105    }
3106}
3107#[cfg(test)]
3108mod build_tests {
3109    use super::*;
3110    use crate::file_ffi::{asdf_close, asdf_open_mem_ex, asdf_value_destroy, asdf_write_to_mem};
3111    use crate::types::AsdfYamlNodeStyle;
3112    use core::ffi::c_void;
3113
3114    struct Handle(*mut AsdfFile);
3115    impl Drop for Handle {
3116        fn drop(&mut self) {
3117            unsafe { asdf_close(self.0) };
3118        }
3119    }
3120
3121    fn writable() -> Handle {
3122        let f = unsafe { asdf_open_mem_ex(core::ptr::null(), 0, core::ptr::null_mut()) };
3123        assert!(!f.is_null());
3124        Handle(f)
3125    }
3126
3127    fn cstr(s: &str) -> CString {
3128        CString::new(s).unwrap()
3129    }
3130
3131    #[test]
3132    fn builds_a_mapping_from_values() {
3133        let h = writable();
3134
3135        let mapping = unsafe { asdf_mapping_create(h.0) };
3136        assert!(!mapping.is_null());
3137        assert!(unsafe { asdf_value_is_mapping(mapping) });
3138        assert_eq!(unsafe { asdf_mapping_size(mapping) }, 0);
3139
3140        let n = unsafe { asdf_value_of_int64(h.0, 42) };
3141        let key = cstr("answer");
3142        assert_eq!(unsafe { asdf_mapping_set(mapping, key.as_ptr(), n) }, AsdfValueErr::Ok);
3143        assert_eq!(unsafe { asdf_mapping_size(mapping) }, 1);
3144
3145        let found = unsafe { asdf_mapping_get(mapping, key.as_ptr()) };
3146        let mut value: i64 = 0;
3147        assert_eq!(unsafe { asdf_value_as_int64(found, &mut value) }, AsdfValueErr::Ok);
3148        assert_eq!(value, 42);
3149
3150        unsafe { asdf_value_destroy(found) };
3151        // `n` is not destroyed here: the successful `asdf_mapping_set` above
3152        // consumed it, as `value.h` says it does.
3153        unsafe { asdf_mapping_destroy(mapping) };
3154    }
3155
3156    #[test]
3157    fn builds_a_sequence_by_appending() {
3158        let h = writable();
3159        let sequence = unsafe { asdf_sequence_create(h.0) };
3160        assert!(unsafe { asdf_value_is_sequence(sequence) });
3161
3162        for value in [10i64, 20, 30] {
3163            let item = unsafe { asdf_value_of_int64(h.0, value) };
3164            // A successful append consumes `item`.
3165            assert_eq!(unsafe { asdf_sequence_append(sequence, item) }, AsdfValueErr::Ok);
3166        }
3167        assert_eq!(unsafe { asdf_sequence_size(sequence) }, 3);
3168
3169        let second = unsafe { asdf_sequence_get(sequence, 1) };
3170        let mut value: i64 = 0;
3171        unsafe { asdf_value_as_int64(second, &mut value) };
3172        assert_eq!(value, 20);
3173
3174        unsafe { asdf_value_destroy(second) };
3175        unsafe { asdf_sequence_destroy(sequence) };
3176    }
3177
3178    #[test]
3179    fn popping_removes_and_returns() {
3180        let h = writable();
3181
3182        let mapping = unsafe { asdf_mapping_create(h.0) };
3183        let n = unsafe { asdf_value_of_int64(h.0, 7) };
3184        let key = cstr("gone");
3185        unsafe { asdf_mapping_set(mapping, key.as_ptr(), n) };
3186
3187        let popped = unsafe { asdf_mapping_pop(mapping, key.as_ptr()) };
3188        assert!(!popped.is_null());
3189        let mut value: i64 = 0;
3190        unsafe { asdf_value_as_int64(popped, &mut value) };
3191        assert_eq!(value, 7);
3192        assert_eq!(unsafe { asdf_mapping_size(mapping) }, 0);
3193        // Popping again finds nothing.
3194        assert!(unsafe { asdf_mapping_pop(mapping, key.as_ptr()) }.is_null());
3195
3196        let sequence = unsafe { asdf_sequence_create(h.0) };
3197        for value in [1i64, 2] {
3198            let item = unsafe { asdf_value_of_int64(h.0, value) };
3199            unsafe { asdf_sequence_append(sequence, item) };
3200        }
3201        let first = unsafe { asdf_sequence_pop(sequence, 0) };
3202        assert!(!first.is_null());
3203        assert_eq!(unsafe { asdf_sequence_size(sequence) }, 1);
3204
3205        // `n` is absent: the `asdf_mapping_set` above consumed it. Popping
3206        // its entry hands back a fresh handle (`popped`), not that one.
3207        for v in [popped, first, mapping, sequence] {
3208            unsafe { asdf_value_destroy(v) };
3209        }
3210    }
3211
3212    #[test]
3213    fn a_built_tree_writes_and_reads_back() {
3214        let h = writable();
3215
3216        // Build `meta: {name: 'obs', frames: [1, 2, 3]}` from values, then
3217        // attach it at a path and write the file.
3218        let meta = unsafe { asdf_mapping_create(h.0) };
3219        let name = unsafe { asdf_value_of_string0(h.0, cstr("obs").as_ptr()) };
3220        let name_key = cstr("name");
3221        unsafe { asdf_mapping_set(meta, name_key.as_ptr(), name) };
3222
3223        let frames = unsafe { asdf_sequence_create(h.0) };
3224        for value in [1i64, 2, 3] {
3225            let item = unsafe { asdf_value_of_int64(h.0, value) };
3226            unsafe { asdf_sequence_append(frames, item) };
3227        }
3228        let frames_key = cstr("frames");
3229        unsafe { asdf_mapping_set(meta, frames_key.as_ptr(), frames) };
3230
3231        let path = cstr("meta");
3232        assert_eq!(
3233            unsafe { crate::file_ffi::set_value_at(h.0, path.as_ptr(), meta) },
3234            AsdfValueErr::Ok
3235        );
3236
3237        let mut buf: *mut c_void = core::ptr::null_mut();
3238        let mut size = 0usize;
3239        assert_eq!(unsafe { asdf_write_to_mem(h.0, &mut buf, &mut size) }, 0);
3240
3241        let reopened = unsafe { asdf_open_mem_ex(buf, size, core::ptr::null_mut()) };
3242        let r = Handle(reopened);
3243
3244        let inner = cstr("meta/name");
3245        let mut text: *const c_char = core::ptr::null();
3246        assert_eq!(
3247            unsafe { crate::file_ffi::asdf_get_string0(r.0, inner.as_ptr(), &mut text) },
3248            AsdfValueErr::Ok
3249        );
3250        assert_eq!(unsafe { CStr::from_ptr(text) }.to_str().unwrap(), "obs");
3251
3252        let third = cstr("meta/frames/2");
3253        let mut value: i64 = 0;
3254        assert_eq!(
3255            unsafe { crate::file_ffi::asdf_get_int64(r.0, third.as_ptr(), &mut value) },
3256            AsdfValueErr::Ok
3257        );
3258        assert_eq!(value, 3);
3259
3260        unsafe { libc::free(buf) };
3261        // `name` and `frames` were consumed by the sets that inserted them,
3262        // and `meta` by `set_value_at`.
3263        let _ = (meta, name, frames);
3264    }
3265
3266    #[test]
3267    fn a_numeric_string_value_stays_a_string() {
3268        let h = writable();
3269        let value = unsafe { asdf_value_of_string0(h.0, cstr("42").as_ptr()) };
3270        assert!(unsafe { asdf_value_is_string(value) });
3271        let mut n: i64 = 0;
3272        assert_eq!(unsafe { asdf_value_as_int64(value, &mut n) }, AsdfValueErr::TypeMismatch);
3273        unsafe { asdf_value_destroy(value) };
3274    }
3275
3276    #[test]
3277    fn scalar_constructors_produce_the_right_types() {
3278        let h = writable();
3279
3280        let cases: Vec<(*mut AsdfValue, AsdfValueType)> = vec![
3281            (unsafe { asdf_value_of_bool(h.0, true) }, AsdfValueType::Bool),
3282            (unsafe { asdf_value_of_null(h.0) }, AsdfValueType::Null),
3283            (unsafe { asdf_value_of_double(h.0, 1.5) }, AsdfValueType::Double),
3284            // Small positives narrow to uint8, as libasdf resolves them.
3285            (unsafe { asdf_value_of_int64(h.0, 7) }, AsdfValueType::Uint8),
3286            (unsafe { asdf_value_of_int64(h.0, -7) }, AsdfValueType::Int8),
3287        ];
3288        for (value, expected) in cases {
3289            assert!(!value.is_null());
3290            assert_eq!(unsafe { crate::file_ffi::asdf_value_get_type(value) }, expected);
3291            unsafe { asdf_value_destroy(value) };
3292        }
3293    }
3294
3295    #[test]
3296    fn iterates_either_container_kind() {
3297        let h = writable();
3298
3299        let mapping = unsafe { asdf_mapping_create(h.0) };
3300        for (key, value) in [("a", 1i64), ("b", 2)] {
3301            let item = unsafe { asdf_value_of_int64(h.0, value) };
3302            unsafe { asdf_mapping_set(mapping, cstr(key).as_ptr(), item) };
3303        }
3304
3305        let mut iter = unsafe { asdf_container_iter_init(mapping) };
3306        let mut seen = Vec::new();
3307        while unsafe { asdf_container_iter_next(&mut iter) } {
3308            let head = unsafe { &*iter };
3309            assert!(!head.key.is_null(), "a mapping must report keys");
3310            // A mapping's entries are numbered too: the index is the
3311            // position in the container, not a sequence-only field.
3312            assert_eq!(head.index, seen.len() as c_int);
3313            seen.push(unsafe { CStr::from_ptr(head.key) }.to_str().unwrap().to_string());
3314        }
3315        assert_eq!(seen, ["a", "b"]);
3316        assert!(iter.is_null());
3317
3318        let sequence = unsafe { asdf_sequence_create(h.0) };
3319        for value in [10i64, 20] {
3320            let item = unsafe { asdf_value_of_int64(h.0, value) };
3321            unsafe { asdf_sequence_append(sequence, item) };
3322        }
3323        let mut iter = unsafe { asdf_container_iter_init(sequence) };
3324        let mut indices = Vec::new();
3325        while unsafe { asdf_container_iter_next(&mut iter) } {
3326            let head = unsafe { &*iter };
3327            assert!(head.key.is_null(), "a sequence reports no key");
3328            indices.push(head.index);
3329        }
3330        assert_eq!(indices, [0, 1]);
3331
3332        unsafe { asdf_mapping_destroy(mapping) };
3333        unsafe { asdf_sequence_destroy(sequence) };
3334    }
3335
3336    #[test]
3337    fn container_iteration_can_be_reversed() {
3338        let h = writable();
3339        let sequence = unsafe { asdf_sequence_create(h.0) };
3340        for value in [1i64, 2, 3] {
3341            let item = unsafe { asdf_value_of_int64(h.0, value) };
3342            unsafe { asdf_sequence_append(sequence, item) };
3343        }
3344
3345        let mut iter = unsafe { asdf_container_reverse_iter_init(sequence) };
3346        let mut values = Vec::new();
3347        while unsafe { asdf_container_iter_next(&mut iter) } {
3348            let head = unsafe { &*iter };
3349            let mut n: i64 = 0;
3350            unsafe { asdf_value_as_int64(head.value.cast(), &mut n) };
3351            values.push(n);
3352        }
3353        assert_eq!(values, [3, 2, 1]);
3354        unsafe { asdf_sequence_destroy(sequence) };
3355    }
3356
3357    #[test]
3358    fn styles_can_be_set() {
3359        let h = writable();
3360        let sequence = unsafe { asdf_sequence_create(h.0) };
3361        // Setting a style must not disturb the contents.
3362        unsafe { asdf_sequence_set_style(sequence, AsdfYamlNodeStyle::Flow) };
3363        unsafe { asdf_sequence_set_style(sequence, AsdfYamlNodeStyle::Block) };
3364        assert_eq!(unsafe { asdf_sequence_size(sequence) }, 0);
3365
3366        let mapping = unsafe { asdf_mapping_create(h.0) };
3367        unsafe { asdf_mapping_set_style(mapping, AsdfYamlNodeStyle::Flow) };
3368        assert_eq!(unsafe { asdf_mapping_size(mapping) }, 0);
3369
3370        unsafe { asdf_sequence_destroy(sequence) };
3371        unsafe { asdf_mapping_destroy(mapping) };
3372    }
3373
3374    #[test]
3375    fn setting_the_wrong_kind_is_a_mismatch() {
3376        let h = writable();
3377        let sequence = unsafe { asdf_sequence_create(h.0) };
3378        let item = unsafe { asdf_value_of_int64(h.0, 1) };
3379
3380        // A sequence is not a mapping, and vice versa.
3381        assert_eq!(
3382            unsafe { asdf_mapping_set(sequence, cstr("k").as_ptr(), item) },
3383            AsdfValueErr::TypeMismatch
3384        );
3385
3386        let mapping = unsafe { asdf_mapping_create(h.0) };
3387        assert_eq!(unsafe { asdf_sequence_append(mapping, item) }, AsdfValueErr::TypeMismatch);
3388
3389        unsafe { asdf_value_destroy(item) };
3390        unsafe { asdf_sequence_destroy(sequence) };
3391        unsafe { asdf_mapping_destroy(mapping) };
3392    }
3393
3394    #[test]
3395    fn null_handles_are_tolerated() {
3396        assert!(unsafe { asdf_mapping_create(core::ptr::null_mut()) }.is_null());
3397        assert!(unsafe { asdf_sequence_create(core::ptr::null_mut()) }.is_null());
3398        assert!(unsafe { asdf_value_of_int64(core::ptr::null_mut(), 0) }.is_null());
3399        assert!(unsafe { asdf_value_of_null(core::ptr::null_mut()) }.is_null());
3400        assert!(
3401            unsafe { asdf_value_of_string0(core::ptr::null_mut(), core::ptr::null()) }.is_null()
3402        );
3403        assert!(unsafe { asdf_container_iter_init(core::ptr::null_mut()) }.is_null());
3404        assert!(!unsafe { asdf_container_iter_next(core::ptr::null_mut()) });
3405        unsafe { asdf_container_iter_destroy(core::ptr::null_mut()) };
3406    }
3407}