Skip to main content

asdf/
types.rs

1//! `#[repr(C)]` mirrors of libasdf's public, non-opaque structs.
2//!
3//! These layouts are load-bearing: callers construct and read them directly,
4//! so a wrong field order or width is silent memory corruption in a C caller
5//! rather than a compile error. Every one is checked against the vendored
6//! headers by the `public_struct_layouts_match` gate in `tests/abi.rs`.
7//!
8//! Opaque types -- `asdf_file_t`, `asdf_value_t`, `asdf_block_t` and friends --
9//! deliberately do *not* appear here. Their contents are private to the
10//! implementation, so they are free to be ordinary Rust types.
11
12use core::ffi::{CStr, c_char, c_double, c_int, c_void};
13
14use asdf_core::yaml as asdf_yaml;
15
16use crate::error_ffi::LogLevel;
17
18/// Bitmask of parser options, mirroring `asdf_parser_optflags_t`.
19pub type AsdfParserOptFlags = u64;
20
21/// Bitmask of emitter options, mirroring `asdf_emitter_optflags_t`.
22pub type AsdfEmitterOptFlags = u64;
23
24/// Bitmask of log fields, mirroring `asdf_log_fields_t`.
25pub type AsdfLogFields = u64;
26
27/// Parser options. The header defines these as `1 << bit`.
28pub mod parser_opt {
29    /// Emit YAML sub-events from the parser.
30    pub const EMIT_YAML_EVENTS: u64 = 1 << 0;
31    /// Buffer the whole tree while parsing.
32    pub const BUFFER_TREE: u64 = 1 << 1;
33}
34
35/// Emitter options. The header defines these as `1 << bit`.
36pub mod emitter_opt {
37    /// The default, empty set.
38    pub const DEFAULT: u64 = 1 << 0;
39    /// Emit empty containers.
40    pub const EMIT_EMPTY: u64 = 1 << 1;
41    /// Do not write a block checksum.
42    pub const NO_BLOCK_CHECKSUM: u64 = 1 << 2;
43    /// Do not write a block index.
44    pub const NO_BLOCK_INDEX: u64 = 1 << 3;
45    /// Write the tree even when it is empty.
46    pub const EMIT_EMPTY_TREE: u64 = 1 << 4;
47    /// Do not write an empty tree.
48    pub const NO_EMIT_EMPTY_TREE: u64 = 1 << 5;
49    /// Do not write the `asdf_library` metadata.
50    pub const NO_EMIT_ASDF_LIBRARY: u64 = 1 << 6;
51    /// Reserved as the last option.
52    pub const LAST: u64 = 1 << 62;
53}
54
55/// Log field flags. The header defines these as `1 << bit`.
56pub mod log_field {
57    /// The severity.
58    pub const LEVEL: u64 = 1 << 0;
59    /// The originating package.
60    pub const PACKAGE: u64 = 1 << 1;
61    /// The source file.
62    pub const FILE: u64 = 1 << 2;
63    /// The source line.
64    pub const LINE: u64 = 1 << 3;
65    /// The message text.
66    pub const MSG: u64 = 1 << 4;
67    /// Every field.
68    pub const ALL: u64 = LEVEL | PACKAGE | FILE | LINE | MSG;
69}
70
71/// Where an ndarray's data is written, mirroring `asdf_array_storage_t`.
72#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
73#[repr(i32)]
74pub enum AsdfArrayStorage {
75    /// Use the file-level setting.
76    #[default]
77    Default = 0,
78    /// Inline in the tree.
79    Inline = 1,
80    /// In an internal binary block.
81    Internal = 2,
82    /// In an external file; reserved, not yet supported.
83    External = 3,
84}
85
86/// When compressed block data is decompressed, mirroring
87/// `asdf_block_decomp_mode_t`.
88#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
89#[repr(i32)]
90pub enum AsdfBlockDecompMode {
91    /// Choose automatically.
92    #[default]
93    Auto = 0,
94    /// Decompress everything on first access.
95    Eager = 1,
96    /// Decompress on demand where supported, else fall back to eager.
97    Lazy = 2,
98}
99
100/// A `%TAG` directive, mirroring `asdf_yaml_tag_handle_t`.
101#[repr(C)]
102#[derive(Debug)]
103pub struct asdf_yaml_tag_handle_t {
104    /// The shorthand, `!` included.
105    pub handle: *const c_char,
106    /// The prefix it expands to.
107    pub prefix: *const c_char,
108}
109
110/// The low-level parser's event types, mirroring `asdf_event_type_t`.
111///
112/// The order is the one `ASDF_EVENT_TYPES` gives, and the discriminants are
113/// therefore part of the ABI.
114#[derive(Clone, Copy, PartialEq, Eq, Debug)]
115#[repr(i32)]
116pub enum AsdfEventType {
117    None = 0,
118    Begin,
119    AsdfVersion,
120    StandardVersion,
121    Comment,
122    TreeStart,
123    Yaml,
124    TreeEnd,
125    Block,
126    Padding,
127    BlockIndex,
128    End,
129}
130
131impl AsdfEventType {
132    /// The name `asdf_event_type_name` reports, which is the enum member's
133    /// own spelling.
134    pub fn name(self) -> &'static CStr {
135        match self {
136            AsdfEventType::None => c"ASDF_NONE_EVENT",
137            AsdfEventType::Begin => c"ASDF_BEGIN_EVENT",
138            AsdfEventType::AsdfVersion => c"ASDF_ASDF_VERSION_EVENT",
139            AsdfEventType::StandardVersion => c"ASDF_STANDARD_VERSION_EVENT",
140            AsdfEventType::Comment => c"ASDF_COMMENT_EVENT",
141            AsdfEventType::TreeStart => c"ASDF_TREE_START_EVENT",
142            AsdfEventType::Yaml => c"ASDF_YAML_EVENT",
143            AsdfEventType::TreeEnd => c"ASDF_TREE_END_EVENT",
144            AsdfEventType::Block => c"ASDF_BLOCK_EVENT",
145            AsdfEventType::Padding => c"ASDF_PADDING_EVENT",
146            AsdfEventType::BlockIndex => c"ASDF_BLOCK_INDEX_EVENT",
147            AsdfEventType::End => c"ASDF_END_EVENT",
148        }
149    }
150}
151
152impl AsdfEventType {
153    /// The event type for a discriminant, or `None` if it names none.
154    ///
155    /// C callers pass these as plain ints, so a value outside the enum has to
156    /// be rejected rather than transmuted -- holding one in a Rust enum is
157    /// undefined behaviour.
158    pub fn from_i32(value: i32) -> Option<Self> {
159        (0..ASDF_EVENT_TYPE_COUNT)
160            .contains(&value)
161            .then(|| unsafe { core::mem::transmute::<i32, AsdfEventType>(value) })
162    }
163}
164
165/// The number of members of `asdf_event_type_t`, as `ASDF_EVENT_TYPE_COUNT`.
166pub const ASDF_EVENT_TYPE_COUNT: i32 = 12;
167
168/// The YAML sub-event types, mirroring `asdf_yaml_event_type_t`.
169#[derive(Clone, Copy, PartialEq, Eq, Debug)]
170#[repr(i32)]
171pub enum AsdfYamlEventType {
172    None = 0,
173    StreamStart,
174    StreamEnd,
175    DocumentStart,
176    DocumentEnd,
177    MappingStart,
178    MappingEnd,
179    SequenceStart,
180    SequenceEnd,
181    Scalar,
182    Alias,
183}
184
185impl AsdfYamlEventType {
186    /// The text `asdf_yaml_event_type_text` reports.
187    ///
188    /// Upstream forwards this to libfyaml's `fy_event_type_get_text`, which
189    /// gives the YAML test suite's event notation rather than the enum's own
190    /// spelling: `+MAP`, `=VAL`, `-SEQ`. Nothing in a header states these, so
191    /// they are taken from upstream's committed `events` fixtures.
192    pub fn text(self) -> &'static CStr {
193        match self {
194            AsdfYamlEventType::None => c"",
195            AsdfYamlEventType::StreamStart => c"+STR",
196            AsdfYamlEventType::StreamEnd => c"-STR",
197            AsdfYamlEventType::DocumentStart => c"+DOC",
198            AsdfYamlEventType::DocumentEnd => c"-DOC",
199            AsdfYamlEventType::MappingStart => c"+MAP",
200            AsdfYamlEventType::MappingEnd => c"-MAP",
201            AsdfYamlEventType::SequenceStart => c"+SEQ",
202            AsdfYamlEventType::SequenceEnd => c"-SEQ",
203            AsdfYamlEventType::Scalar => c"=VAL",
204            AsdfYamlEventType::Alias => c"=ALI",
205        }
206    }
207}
208
209/// Where a tree sits in the file, mirroring `asdf_tree_info_t`.
210///
211/// Opaque in the public headers, so only its accessors are ABI; the layout
212/// still mirrors upstream's internal one so that code built against those
213/// internal headers sees the same fields.
214#[repr(C)]
215#[derive(Debug)]
216pub struct asdf_tree_info_t {
217    /// Offset of the start of the YAML tree.
218    pub start: usize,
219    /// Offset one past the end of the tree.
220    pub end: usize,
221    /// The tree text, when the parser was asked to buffer it; else `NULL`.
222    pub buf: *const c_char,
223}
224
225/// A block header as it appears in the file, mirroring `asdf_block_header_t`.
226#[repr(C)]
227#[derive(Debug)]
228pub struct asdf_block_header_t {
229    /// Header size excluding the magic and this field.
230    pub header_size: u16,
231    /// Flag bits; bit 0 marks a streamed block.
232    pub flags: u32,
233    /// The compression name, ``-padded.
234    pub compression: [u8; 4],
235    /// Space reserved for the data.
236    pub allocated_size: u64,
237    /// Bytes used on disk.
238    pub used_size: u64,
239    /// Size of the data once decompressed.
240    pub data_size: u64,
241    /// MD5 of the used data; all-zero means "do not verify".
242    pub checksum: [u8; 16],
243}
244
245/// Where a block sits in the file, mirroring `asdf_block_info_t`.
246///
247/// Upstream's struct continues with fields describing its own in-memory
248/// buffer management; those are private to its implementation and are not
249/// reproduced. Everything the public accessors expose is here.
250#[repr(C)]
251#[derive(Debug)]
252pub struct asdf_block_info_t {
253    /// The block's index in the file.
254    pub index: usize,
255    /// Offset of the block's magic.
256    pub header_pos: i64,
257    /// Offset of the block's first data byte.
258    pub data_pos: i64,
259    /// The parsed header.
260    pub header: asdf_block_header_t,
261}
262
263/// Per-file logging configuration, mirroring `asdf_log_cfg_t`.
264///
265/// Any zero field is filled in with a default: the stream is `stderr`, the
266/// level comes from `ASDF_LOG_LEVEL` or `WARN`, and the fields are all of them.
267#[repr(C)]
268#[derive(Debug)]
269pub struct asdf_log_cfg_t {
270    /// Destination stream; `NULL` means `stderr`.
271    pub stream: *mut c_void,
272    /// Minimum severity to emit.
273    pub level: LogLevel,
274    /// Which fields the standard formatter includes.
275    pub fields: AsdfLogFields,
276    /// Suppress colour even where the build supports it.
277    pub no_color: bool,
278}
279
280/// Low-level parser configuration, mirroring `asdf_parser_cfg_t`.
281#[repr(C)]
282#[derive(Debug)]
283pub struct asdf_parser_cfg_t {
284    /// Bitmask of [`parser_opt`] flags.
285    pub flags: AsdfParserOptFlags,
286    /// Optional logging configuration.
287    pub log: *mut asdf_log_cfg_t,
288}
289
290/// Low-level emitter configuration, mirroring `asdf_emitter_cfg_t`.
291#[repr(C)]
292#[derive(Debug)]
293pub struct asdf_emitter_cfg_t {
294    /// Bitmask of [`emitter_opt`] flags.
295    pub flags: AsdfEmitterOptFlags,
296    /// `NULL`-terminated array of tag directives to write.
297    pub tag_handles: *mut asdf_yaml_tag_handle_t,
298    /// Element count above which an inline ndarray logs a warning. Zero
299    /// selects the library default of 1024; `SIZE_MAX` suppresses it.
300    pub inline_ndarray_warning_thresh: usize,
301    /// Override for where all ndarray data is written.
302    pub array_storage: AsdfArrayStorage,
303}
304
305/// Decompression options.
306///
307/// This mirrors the *anonymous* struct that forms `asdf_config_t`'s `decomp`
308/// field, so it must be laid out as though it were declared inline there.
309#[repr(C)]
310#[derive(Debug)]
311pub struct asdf_decomp_cfg_t {
312    /// When to decompress.
313    pub mode: AsdfBlockDecompMode,
314    /// Decompressed size above which to spill to disk.
315    pub max_memory_bytes: usize,
316    /// Fraction of system memory above which to spill to disk.
317    pub max_memory_threshold: c_double,
318    /// Chunk size for lazy decompression; rounded up to a page.
319    pub chunk_size: usize,
320    /// Directory for temporary files when spilling to disk.
321    pub tmp_dir: *const c_char,
322}
323
324/// Extended options for opening a file, mirroring `asdf_config_t`.
325///
326/// The C API copies this on `asdf_open_ex`, so a caller may pass a local and
327/// may leave any field zeroed to accept the default.
328#[repr(C)]
329#[derive(Debug)]
330pub struct asdf_config_t {
331    /// Parser configuration.
332    pub parser: asdf_parser_cfg_t,
333    /// Emitter configuration.
334    pub emitter: asdf_emitter_cfg_t,
335    /// Logging configuration.
336    pub log: asdf_log_cfg_t,
337    /// Decompression configuration.
338    pub decomp: asdf_decomp_cfg_t,
339}
340
341/// The value types, mirroring `asdf_value_type_t`.
342#[derive(Clone, Copy, PartialEq, Eq, Debug)]
343#[repr(i32)]
344pub enum AsdfValueType {
345    /// Unknown, typically only after a parse error.
346    Unknown = 0,
347    /// A sequence.
348    Sequence,
349    /// A mapping.
350    Mapping,
351    /// A scalar not yet narrowed.
352    Scalar,
353    /// A string.
354    String,
355    /// A boolean.
356    Bool,
357    /// A null.
358    Null,
359    /// Signed 8-bit integer.
360    Int8,
361    /// Signed 16-bit integer.
362    Int16,
363    /// Signed 32-bit integer.
364    Int32,
365    /// Signed 64-bit integer.
366    Int64,
367    /// Unsigned 8-bit integer.
368    Uint8,
369    /// Unsigned 16-bit integer.
370    Uint16,
371    /// Unsigned 32-bit integer.
372    Uint32,
373    /// Unsigned 64-bit integer.
374    Uint64,
375    /// 32-bit float.
376    Float,
377    /// 64-bit float.
378    Double,
379    /// A registered extension type.
380    Extension,
381}
382
383impl AsdfValueType {
384    /// The value type for a discriminant, or `None` if it names none.
385    ///
386    /// See [`AsdfEventType::from_i32`] for why C-supplied discriminants are
387    /// checked rather than transmuted blind.
388    pub fn from_i32(value: i32) -> Option<Self> {
389        (AsdfValueType::Unknown as i32..=AsdfValueType::Extension as i32)
390            .contains(&value)
391            .then(|| unsafe { core::mem::transmute::<i32, AsdfValueType>(value) })
392    }
393}
394
395impl From<asdf_yaml::ValueType> for AsdfValueType {
396    fn from(v: asdf_yaml::ValueType) -> Self {
397        use asdf_yaml::ValueType as V;
398        match v {
399            V::Unknown => AsdfValueType::Unknown,
400            V::Sequence => AsdfValueType::Sequence,
401            V::Mapping => AsdfValueType::Mapping,
402            V::Scalar => AsdfValueType::Scalar,
403            V::String => AsdfValueType::String,
404            V::Bool => AsdfValueType::Bool,
405            V::Null => AsdfValueType::Null,
406            V::Int8 => AsdfValueType::Int8,
407            V::Int16 => AsdfValueType::Int16,
408            V::Int32 => AsdfValueType::Int32,
409            V::Int64 => AsdfValueType::Int64,
410            V::Uint8 => AsdfValueType::Uint8,
411            V::Uint16 => AsdfValueType::Uint16,
412            V::Uint32 => AsdfValueType::Uint32,
413            V::Uint64 => AsdfValueType::Uint64,
414            V::Float => AsdfValueType::Float,
415            V::Double => AsdfValueType::Double,
416            V::Extension => AsdfValueType::Extension,
417        }
418    }
419}
420
421/// Return codes for value access, mirroring `asdf_value_err_t`.
422///
423/// Note the negative discriminants: `OK` is zero with errors on both sides,
424/// so a caller testing `err == ASDF_VALUE_OK` is the only correct check.
425#[derive(Clone, Copy, PartialEq, Eq, Debug)]
426#[repr(i32)]
427pub enum AsdfValueErr {
428    /// An unspecified error.
429    Unknown = -2,
430    /// The path does not exist in the tree.
431    NotFound = -1,
432    /// Success.
433    Ok = 0,
434    /// The value is not of the requested type.
435    TypeMismatch = 1,
436    /// A tagged value could not be parsed as its tag claims.
437    ParseFailure = 2,
438    /// A value could not be serialized.
439    EmitFailure = 3,
440    /// A numeric value does not fit the requested C type.
441    Overflow = 4,
442    /// Allocation failed.
443    Oom = 5,
444    /// The file or value is read-only.
445    ReadOnly = 6,
446}
447
448/// Error codes for ndarray access, mirroring `asdf_ndarray_err_t`.
449pub use crate::ndarray_ffi::NdarrayErr as AsdfNdarrayErr;
450
451/// Node style hints, mirroring `asdf_yaml_node_style_t`.
452#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
453#[repr(i32)]
454pub enum AsdfYamlNodeStyle {
455    /// Let the emitter choose.
456    #[default]
457    Auto = 0,
458    /// `{...}` / `[...]`.
459    Flow = 1,
460    /// Indented block notation.
461    Block = 2,
462}
463
464/// The public head of a mapping iterator, mirroring `asdf_mapping_iter_t`.
465///
466/// The implementation casts between this and its own larger struct, so this
467/// must stay at offset 0 of that struct and keep this exact layout.
468#[repr(C)]
469#[derive(Debug)]
470pub struct asdf_mapping_iter_t {
471    /// The current entry's key.
472    pub key: *const c_char,
473    /// The current entry's value.
474    pub value: *mut c_void,
475}
476
477/// The public head of a find iterator, mirroring `asdf_find_iter_t`.
478///
479/// See [`asdf_mapping_iter_t`] for why the layout matters.
480#[repr(C)]
481#[derive(Debug)]
482pub struct asdf_find_iter_t {
483    /// The current matching value, owned by the iterator.
484    pub value: *mut c_void,
485}
486
487/// The public head of a sequence iterator, mirroring `asdf_sequence_iter_t`.
488///
489/// The header puts `value` first and `index` second; the order is the ABI,
490/// and getting it backwards hands C a garbage index and a garbage pointer.
491#[repr(C)]
492#[derive(Debug)]
493pub struct asdf_sequence_iter_t {
494    /// The current item.
495    pub value: *mut c_void,
496    /// The current index.
497    pub index: c_int,
498}
499
500/// The public head of a container iterator, mirroring
501/// `asdf_container_iter_t`.
502#[repr(C)]
503#[derive(Debug)]
504pub struct asdf_container_iter_t {
505    /// The current entry's key, or `NULL` when iterating a sequence.
506    pub key: *const c_char,
507    /// The current index, or `-1` when iterating a mapping.
508    pub index: c_int,
509    /// The current value.
510    pub value: *mut c_void,
511}
512
513#[cfg(test)]
514mod tests {
515    use super::*;
516    use core::mem::{align_of, offset_of, size_of};
517
518    #[test]
519    fn value_err_discriminants_span_zero() {
520        // The C header runs these from -2 through 6, so a caller checking
521        // `err < 0` for "not found" and `err > 0` for a type problem is
522        // relying on the exact values.
523        assert_eq!(AsdfValueErr::Unknown as i32, -2);
524        assert_eq!(AsdfValueErr::NotFound as i32, -1);
525        assert_eq!(AsdfValueErr::Ok as i32, 0);
526        assert_eq!(AsdfValueErr::TypeMismatch as i32, 1);
527        assert_eq!(AsdfValueErr::ReadOnly as i32, 6);
528    }
529
530    #[test]
531    fn value_type_discriminants_are_sequential_from_zero() {
532        assert_eq!(AsdfValueType::Unknown as i32, 0);
533        assert_eq!(AsdfValueType::Sequence as i32, 1);
534        assert_eq!(AsdfValueType::Mapping as i32, 2);
535        assert_eq!(AsdfValueType::Scalar as i32, 3);
536        assert_eq!(AsdfValueType::String as i32, 4);
537        assert_eq!(AsdfValueType::Extension as i32, 17);
538    }
539
540    #[test]
541    fn option_flags_are_bit_positions() {
542        assert_eq!(parser_opt::EMIT_YAML_EVENTS, 1);
543        assert_eq!(parser_opt::BUFFER_TREE, 2);
544        assert_eq!(emitter_opt::DEFAULT, 1);
545        assert_eq!(emitter_opt::EMIT_EMPTY, 2);
546        assert_eq!(emitter_opt::NO_EMIT_ASDF_LIBRARY, 64);
547        assert_eq!(log_field::ALL, 31);
548    }
549
550    #[test]
551    fn storage_and_decomp_modes_match_the_header() {
552        assert_eq!(AsdfArrayStorage::Default as i32, 0);
553        assert_eq!(AsdfArrayStorage::External as i32, 3);
554        assert_eq!(AsdfBlockDecompMode::Auto as i32, 0);
555        assert_eq!(AsdfBlockDecompMode::Lazy as i32, 2);
556    }
557
558    #[test]
559    fn config_nests_its_parts_in_declaration_order() {
560        // asdf_config_t is parser, emitter, log, decomp -- in that order.
561        assert_eq!(offset_of!(asdf_config_t, parser), 0);
562        assert!(offset_of!(asdf_config_t, emitter) >= size_of::<asdf_parser_cfg_t>());
563        assert!(offset_of!(asdf_config_t, log) > offset_of!(asdf_config_t, emitter));
564        assert!(offset_of!(asdf_config_t, decomp) > offset_of!(asdf_config_t, log));
565    }
566
567    #[test]
568    fn iterator_heads_start_with_their_public_fields() {
569        // The implementation casts its own struct to these, so the public
570        // fields must sit at the front.
571        assert_eq!(offset_of!(asdf_mapping_iter_t, key), 0);
572        assert_eq!(offset_of!(asdf_sequence_iter_t, value), 0);
573        assert_eq!(offset_of!(asdf_container_iter_t, key), 0);
574        assert_eq!(offset_of!(asdf_find_iter_t, value), 0);
575        assert_eq!(align_of::<asdf_mapping_iter_t>(), align_of::<*const c_char>());
576    }
577
578    #[test]
579    fn value_types_convert_from_the_engine() {
580        assert_eq!(AsdfValueType::from(asdf_yaml::ValueType::Uint8), AsdfValueType::Uint8);
581        assert_eq!(AsdfValueType::from(asdf_yaml::ValueType::Double), AsdfValueType::Double);
582        assert_eq!(AsdfValueType::from(asdf_yaml::ValueType::Null), AsdfValueType::Null);
583    }
584}