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}