1#![forbid(unsafe_code)]
71#![warn(missing_docs)]
72
73use std::borrow::Cow;
74use std::path::Path;
75
76use asdf_core::core::elements::decode_all;
77use asdf_core::yaml::{
78 self, CompareOptions, Document, NodeData, NodeId, Resolved, ScalarStyle, Schema, Tag,
79};
80use asdf_core::{PendingBlock, Reader, Writer};
81
82pub use asdf_core::ChecksumStatus;
83pub use asdf_core::compression::Compression;
88pub use asdf_core::core::datatype::{ByteOrder, Datatype, Field, ScalarType};
89pub use asdf_core::core::elements::Element;
90pub use asdf_core::core::ndarray::{Mask, Ndarray, Source};
91pub use asdf_core::core::provenance::{ExtensionMetadata, History, HistoryEntry, Meta, Software};
92pub use asdf_core::core::time::{Civil, Location, Time, TimeFormat, TimeScale};
93pub use asdf_core::error::{Error, ErrorCode};
94pub use asdf_core::events::{Event, EventOptions, render_event};
95pub use asdf_core::info::InfoOptions;
96pub use asdf_core::version::Version;
97
98pub type Result<T> = std::result::Result<T, Error>;
100
101pub trait ArrayElement: sealed::Sealed + Copy {
109 const SCALAR: ScalarType;
111
112 fn to_bytes(self) -> Vec<u8>;
114
115 fn from_element(element: &Element) -> Option<Self>;
121
122 #[doc(hidden)]
132 fn decode_native(bytes: &[u8], order: ByteOrder) -> Vec<Self>;
133
134 #[doc(hidden)]
141 fn encode_native(values: &[Self]) -> Vec<u8>;
142}
143
144mod sealed {
145 pub trait Sealed {}
146}
147
148macro_rules! integer_element {
150 ($ty:ty, $scalar:ident) => {
151 impl sealed::Sealed for $ty {}
152 impl ArrayElement for $ty {
153 const SCALAR: ScalarType = ScalarType::$scalar;
154
155 fn to_bytes(self) -> Vec<u8> {
156 self.to_ne_bytes().to_vec()
157 }
158
159 fn from_element(element: &Element) -> Option<Self> {
160 match element {
161 Element::Int(v) => <$ty>::try_from(*v).ok(),
162 Element::Uint(v) => <$ty>::try_from(*v).ok(),
163 Element::Bool(v) => Some(<$ty>::from(*v)),
164 _ => None,
165 }
166 }
167
168 fn decode_native(bytes: &[u8], order: ByteOrder) -> Vec<Self> {
169 let (chunks, _) = bytes.as_chunks::<{ size_of::<$ty>() }>();
170 match order {
171 ByteOrder::Big => chunks.iter().map(|c| <$ty>::from_be_bytes(*c)).collect(),
172 _ => chunks.iter().map(|c| <$ty>::from_le_bytes(*c)).collect(),
173 }
174 }
175
176 fn encode_native(values: &[Self]) -> Vec<u8> {
177 let mut out = Vec::with_capacity(values.len() * size_of::<$ty>());
178 for value in values {
179 out.extend_from_slice(&value.to_ne_bytes());
180 }
181 out
182 }
183 }
184 };
185}
186
187integer_element!(i8, Int8);
188integer_element!(i16, Int16);
189integer_element!(i32, Int32);
190integer_element!(i64, Int64);
191integer_element!(u8, Uint8);
192integer_element!(u16, Uint16);
193integer_element!(u32, Uint32);
194integer_element!(u64, Uint64);
195
196macro_rules! float_element {
198 ($ty:ty, $scalar:ident) => {
199 impl sealed::Sealed for $ty {}
200 impl ArrayElement for $ty {
201 const SCALAR: ScalarType = ScalarType::$scalar;
202
203 fn to_bytes(self) -> Vec<u8> {
204 self.to_ne_bytes().to_vec()
205 }
206
207 fn from_element(element: &Element) -> Option<Self> {
208 match element {
209 Element::Float(v) => Some(*v as $ty),
210 Element::Int(v) => {
213 let converted = *v as $ty;
214 (converted as i64 == *v).then_some(converted)
215 }
216 Element::Uint(v) => {
217 let converted = *v as $ty;
218 (converted as u64 == *v).then_some(converted)
219 }
220 _ => None,
221 }
222 }
223
224 fn decode_native(bytes: &[u8], order: ByteOrder) -> Vec<Self> {
225 let (chunks, _) = bytes.as_chunks::<{ size_of::<$ty>() }>();
226 match order {
227 ByteOrder::Big => chunks.iter().map(|c| <$ty>::from_be_bytes(*c)).collect(),
228 _ => chunks.iter().map(|c| <$ty>::from_le_bytes(*c)).collect(),
229 }
230 }
231
232 fn encode_native(values: &[Self]) -> Vec<u8> {
233 let mut out = Vec::with_capacity(values.len() * size_of::<$ty>());
234 for value in values {
235 out.extend_from_slice(&value.to_ne_bytes());
236 }
237 out
238 }
239 }
240 };
241}
242
243float_element!(f32, Float32);
244float_element!(f64, Float64);
245
246#[derive(Debug)]
248pub struct AsdfFile {
249 reader: Reader,
250}
251
252impl AsdfFile {
253 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
258 Ok(Self { reader: Reader::open(path)? })
259 }
260
261 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
263 Ok(Self { reader: Reader::from_bytes(bytes)? })
264 }
265
266 pub fn format_version(&self) -> &Version {
268 &self.reader.layout().format_version
269 }
270
271 pub fn standard_version(&self) -> Option<&Version> {
273 self.reader.layout().standard_version.as_ref()
274 }
275
276 pub fn tree(&self) -> Result<Option<Tree>> {
281 Ok(self.reader.tree()?.map(|document| Tree { document }))
282 }
283
284 pub fn tree_inlined(&self) -> Result<Option<(Tree, Vec<String>)>> {
290 Ok(self.reader.tree_inlined()?.map(|(document, skipped)| (Tree { document }, skipped)))
291 }
292
293 pub fn block_count(&self) -> usize {
295 self.reader.block_count()
296 }
297
298 pub fn block_data(&self, index: usize) -> Result<Cow<'_, [u8]>> {
302 self.reader.block_data(index)
303 }
304
305 pub fn block_raw(&self, index: usize) -> Result<&[u8]> {
307 self.reader.block_raw(index)
308 }
309
310 pub fn block_compression(&self, index: usize) -> Result<Compression> {
312 self.reader.block_compression(index)
313 }
314
315 pub fn verify_block(&self, index: usize) -> Result<ChecksumStatus> {
320 Ok(self.reader.verify_block_checksum(index)?.0)
321 }
322
323 fn block_for(&self, array: &Ndarray) -> Result<usize> {
325 match &array.source {
326 Source::Block(index) => Ok(*index),
327 Source::LastBlock => self
328 .reader
329 .block_count()
330 .checked_sub(1)
331 .ok_or_else(|| Error::new(ErrorCode::InvalidArgument, "the file has no blocks")),
332 Source::External(uri) => Err(Error::new(
333 ErrorCode::InvalidArgument,
334 format!("array data lives in another file: {uri}"),
335 )),
336 Source::Inline(_) => {
337 Err(Error::new(ErrorCode::InvalidArgument, "array data is inline, not in a block"))
338 }
339 }
340 }
341
342 pub fn read_array(&self, array: &Ndarray) -> Result<Vec<Element>> {
352 if let Source::External(uri) = &array.source {
353 let data = self.reader.external_block(uri)?;
354 let shape = array.resolved_shape(Some(data.len() as u64))?;
355 return decode_all(array, &shape, &data);
356 }
357 let index = self.block_for(array)?;
358 let data = self.block_data(index)?;
359 let shape = array.resolved_shape(Some(data.len() as u64))?;
360 decode_all(array, &shape, &data)
361 }
362
363 pub fn read_array_at(&self, path: &str) -> Result<Vec<Element>> {
370 let tree = self.tree()?.ok_or_else(|| {
371 Error::new(ErrorCode::InvalidArgument, "this file has no tree to look in")
372 })?;
373 let value = tree.get(path).ok_or_else(|| {
374 Error::new(ErrorCode::InvalidArgument, format!("no value at {path:?}"))
375 })?;
376 let array = value.as_ndarray().ok_or_else(|| {
377 Error::new(ErrorCode::InvalidArgument, format!("the value at {path:?} is not an array"))
378 })?;
379 match array.source {
380 Source::Inline(_) => tree.read_array(&array),
381 _ => self.read_array(&array),
382 }
383 }
384
385 pub fn read_array_f64(&self, array: &Ndarray) -> Result<Vec<f64>> {
389 as_f64(self.read_array(array)?)
390 }
391
392 pub fn read_array_i64(&self, array: &Ndarray) -> Result<Vec<i64>> {
397 as_i64(self.read_array(array)?)
398 }
399
400 pub fn read_array_f64_at(&self, path: &str) -> Result<Vec<f64>> {
402 as_f64(self.read_array_at(path)?)
403 }
404
405 pub fn read_array_i64_at(&self, path: &str) -> Result<Vec<i64>> {
407 as_i64(self.read_array_at(path)?)
408 }
409
410 pub fn read_array_of<T: ArrayElement>(&self, path: &str) -> Result<Vec<T>> {
424 let tree = self.tree()?.ok_or_else(|| {
425 Error::new(ErrorCode::InvalidArgument, "this file has no tree to look in")
426 })?;
427 let value = tree.get(path).ok_or_else(|| {
428 Error::new(ErrorCode::InvalidArgument, format!("no value at {path:?}"))
429 })?;
430 let array = value.as_ndarray().ok_or_else(|| {
431 Error::new(ErrorCode::InvalidArgument, format!("the value at {path:?} is not an array"))
432 })?;
433
434 if let Some(bytes) = self.contiguous_bytes_of(&array)?
439 && bulk_readable::<T>(&array)
440 {
441 return Ok(T::decode_native(&bytes, element_order(&array)));
442 }
443
444 match array.source {
445 Source::Inline(_) => as_type(tree.read_array(&array)?),
446 _ => as_type(self.read_array(&array)?),
447 }
448 }
449
450 fn contiguous_bytes_of(&self, array: &Ndarray) -> Result<Option<Cow<'_, [u8]>>> {
455 match &array.source {
456 Source::Inline(_) => Ok(None),
457 Source::External(uri) => Ok(Some(Cow::Owned(self.reader.external_block(uri)?))),
458 _ => {
459 let index = self.block_for(array)?;
460 Ok(Some(self.block_data(index)?))
461 }
462 }
463 }
464
465 pub fn edit(&self) -> Result<AsdfBuilder> {
484 let document = self.reader.tree()?.unwrap_or_else(Document::new_asdf);
485
486 let mut blocks = Vec::with_capacity(self.reader.block_count());
490 for index in 0..self.reader.block_count() {
491 let compression = self.reader.block_compression(index)?;
492 let data = self.reader.block_data(index)?.into_owned();
493 blocks.push(PendingBlock::compressed(data, compression));
494 }
495
496 Ok(AsdfBuilder { document, blocks, compression: Compression::None })
497 }
498
499 pub fn info(&self, options: InfoOptions) -> Result<String> {
504 asdf_core::info::render(&self.reader, options)
505 }
506
507 pub fn events(&self, options: EventOptions) -> Vec<Event> {
515 asdf_core::events::events_from(self.reader.bytes(), self.reader.layout(), options)
516 }
517}
518
519fn as_type<T: ArrayElement>(elements: Vec<Element>) -> Result<Vec<T>> {
521 elements
522 .into_iter()
523 .map(|element| {
524 T::from_element(&element).ok_or_else(|| {
525 Error::new(
526 ErrorCode::InvalidArgument,
527 format!("{element:?} does not fit {}", T::SCALAR.name()),
528 )
529 })
530 })
531 .collect()
532}
533
534fn element_order(array: &Ndarray) -> ByteOrder {
539 match array.datatype.byteorder {
540 ByteOrder::Big | ByteOrder::Little => array.datatype.byteorder,
541 _ => array.byteorder,
542 }
543}
544
545fn bulk_readable<T: ArrayElement>(array: &Ndarray) -> bool {
555 array.datatype.scalar == T::SCALAR
556 && array.datatype.size == size_of::<T>() as u64
557 && array.datatype.fields.is_empty()
558 && array.datatype.shape.is_empty()
559 && array.strides.is_none()
560 && array.offset == 0
561 && array.mask.is_none()
562}
563
564fn as_f64(elements: Vec<Element>) -> Result<Vec<f64>> {
566 elements
567 .into_iter()
568 .map(|element| match element {
569 Element::Float(v) => Ok(v),
570 Element::Int(v) => Ok(v as f64),
571 Element::Uint(v) => Ok(v as f64),
572 Element::Bool(v) => Ok(if v { 1.0 } else { 0.0 }),
573 other => Err(Error::new(
574 ErrorCode::InvalidArgument,
575 format!("{other:?} cannot be read as a number"),
576 )),
577 })
578 .collect()
579}
580
581fn as_i64(elements: Vec<Element>) -> Result<Vec<i64>> {
583 elements
584 .into_iter()
585 .map(|element| match element {
586 Element::Int(v) => Ok(v),
587 Element::Uint(v) => i64::try_from(v).map_err(|_| {
588 Error::new(ErrorCode::InvalidArgument, format!("{v} does not fit an i64"))
589 }),
590 Element::Bool(v) => Ok(i64::from(v)),
591 Element::Float(v) if v.fract() == 0.0 => Ok(v as i64),
592 other => Err(Error::new(
593 ErrorCode::InvalidArgument,
594 format!("{other:?} cannot be read as an integer"),
595 )),
596 })
597 .collect()
598}
599
600#[derive(Clone, Debug)]
602pub struct Tree {
603 document: Document,
604}
605
606impl Tree {
607 pub fn root(&self) -> Option<Value<'_>> {
609 self.document.root().map(|node| Value { document: &self.document, node })
610 }
611
612 pub fn get(&self, path: &str) -> Option<Value<'_>> {
617 self.document.lookup_str(path).map(|node| Value { document: &self.document, node })
618 }
619
620 pub fn read_array(&self, array: &Ndarray) -> Result<Vec<Element>> {
626 let shape = array.resolved_shape(None)?;
627 asdf_core::core::decode_inline(&self.document, array, &shape)
628 }
629
630 pub fn meta(&self) -> Result<Meta> {
635 let root = self
636 .document
637 .root()
638 .ok_or_else(|| Error::new(ErrorCode::InvalidArgument, "the tree has no root"))?;
639 Meta::parse(&self.document, root)
640 }
641
642 pub fn document(&self) -> &Document {
644 &self.document
645 }
646
647 pub fn value_eq(&self, other: &Tree) -> bool {
652 yaml::compare(&self.document, &other.document, CompareOptions::default()).is_equal()
653 }
654
655 pub fn to_yaml(&self) -> Result<String> {
657 yaml::emit(&self.document)
658 .map_err(|e| Error::new(ErrorCode::YamlParseFailed, e.to_string()))
659 }
660}
661
662#[derive(Clone, Copy, Debug)]
664pub struct Value<'a> {
665 document: &'a Document,
666 node: NodeId,
667}
668
669impl<'a> Value<'a> {
670 pub fn tag(&self) -> Option<&'a Tag> {
672 self.document.tag_of(self.node)
673 }
674
675 pub fn has_tag(&self, name: &str) -> bool {
680 self.tag().is_some_and(|t| t.split_version().0 == name)
681 }
682
683 pub fn as_raw_str(&self) -> Option<&'a str> {
685 self.document.resolved(self.node).as_str()
686 }
687
688 pub fn as_str(&self) -> Option<&'a str> {
692 let node = self.document.resolved(self.node);
693 let NodeData::Scalar { value, style } = &node.data else {
694 return None;
695 };
696 matches!(yaml::resolve(value, *style, Schema::Libasdf), Resolved::String)
697 .then_some(value.as_str())
698 }
699
700 pub fn as_i64(&self) -> Option<i64> {
702 match self.resolved()? {
703 Resolved::Int(v, _) => Some(v),
704 Resolved::Uint(v, _) => i64::try_from(v).ok(),
705 _ => None,
706 }
707 }
708
709 pub fn as_u64(&self) -> Option<u64> {
711 match self.resolved()? {
712 Resolved::Uint(v, _) => Some(v),
713 Resolved::Int(v, _) => u64::try_from(v).ok(),
714 _ => None,
715 }
716 }
717
718 pub fn as_f64(&self) -> Option<f64> {
720 match self.resolved()? {
721 Resolved::Double(v) => Some(v),
722 Resolved::Int(v, _) => Some(v as f64),
723 Resolved::Uint(v, _) => Some(v as f64),
724 _ => None,
725 }
726 }
727
728 pub fn as_bool(&self) -> Option<bool> {
730 match self.resolved()? {
731 Resolved::Bool(v) => Some(v),
732 _ => None,
733 }
734 }
735
736 pub fn is_null(&self) -> bool {
738 matches!(self.resolved(), Some(Resolved::Null))
739 }
740
741 fn resolved(&self) -> Option<Resolved> {
742 let node = self.document.resolved(self.node);
743 let NodeData::Scalar { value, style } = &node.data else {
744 return None;
745 };
746 Some(yaml::resolve(value, *style, Schema::Libasdf))
747 }
748
749 pub fn is_mapping(&self) -> bool {
751 self.document.resolved(self.node).is_mapping()
752 }
753
754 pub fn is_sequence(&self) -> bool {
756 self.document.resolved(self.node).is_sequence()
757 }
758
759 pub fn len(&self) -> Option<usize> {
761 self.document.container_len(self.node)
762 }
763
764 pub fn is_empty(&self) -> Option<bool> {
766 self.len().map(|n| n == 0)
767 }
768
769 pub fn get(&self, key: &str) -> Option<Value<'a>> {
771 self.document
772 .mapping_get(self.node, key)
773 .map(|node| Value { document: self.document, node })
774 }
775
776 pub fn at(&self, index: i64) -> Option<Value<'a>> {
778 self.document
779 .sequence_get(self.node, index)
780 .map(|node| Value { document: self.document, node })
781 }
782
783 pub fn path(&self, path: &str) -> Option<Value<'a>> {
785 let parsed = yaml::Path::parse(path).ok()?;
786 self.document
787 .lookup_from(self.node, &parsed)
788 .map(|node| Value { document: self.document, node })
789 }
790
791 pub fn entries(&self) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
793 let document = self.document;
794 let entries = document.mapping_entries(self.node).unwrap_or(&[]);
795 entries.iter().map(move |entry| {
796 let key = document.resolved(entry.key).as_str().unwrap_or_default();
797 (key, Value { document, node: entry.value })
798 })
799 }
800
801 pub fn items(&self) -> impl Iterator<Item = Value<'a>> + 'a {
803 let document = self.document;
804 let items = document.sequence_items(self.node).unwrap_or(&[]);
805 items.iter().map(move |node| Value { document, node: *node })
806 }
807
808 pub fn as_ndarray(&self) -> Option<Ndarray> {
813 Ndarray::parse(self.document, self.node).ok()
814 }
815
816 pub fn as_software(&self) -> Option<Software> {
818 Software::parse(self.document, self.node).ok()
819 }
820
821 pub fn as_history_entry(&self) -> Option<HistoryEntry> {
823 HistoryEntry::parse(self.document, self.node).ok()
824 }
825
826 pub fn as_extension_metadata(&self) -> Option<ExtensionMetadata> {
828 ExtensionMetadata::parse(self.document, self.node).ok()
829 }
830
831 pub fn as_time(&self) -> Option<Time> {
837 Time::parse(self.document, self.node).ok()
838 }
839
840 pub fn is_alias(&self) -> bool {
842 self.document.node(self.node).is_alias()
843 }
844}
845
846#[derive(Debug)]
848pub struct AsdfBuilder {
849 document: Document,
850 blocks: Vec<PendingBlock>,
851 compression: Compression,
852}
853
854impl Default for AsdfBuilder {
855 fn default() -> Self {
856 Self::new()
857 }
858}
859
860impl AsdfBuilder {
861 pub fn new() -> Self {
863 let mut document = Document::new_asdf();
864 let root = document.add(yaml::Node::mapping());
865 document.node_mut(root).tag = Some(Tag::parse("tag:stsci.edu:asdf/core/asdf-1.1.0"));
866 document.set_root(root);
867 Self { document, blocks: Vec::new(), compression: Compression::None }
868 }
869
870 pub fn with_compression(mut self, compression: Compression) -> Self {
876 self.compression = compression;
877 self
878 }
879
880 pub fn recompress(mut self, compression: Compression) -> Self {
886 self.compression = compression;
887 for block in &mut self.blocks {
888 block.compression = compression;
889 }
890 self
891 }
892
893 pub fn document_mut(&mut self) -> &mut Document {
895 &mut self.document
896 }
897
898 fn insert(&mut self, path: &str, node: NodeId) -> Result<()> {
899 self.document
900 .insert_at_str(path, node)
901 .map(|_| ())
902 .map_err(|e| Error::new(ErrorCode::InvalidArgument, e.to_string()))
903 }
904
905 pub fn set_str(&mut self, path: &str, value: &str) -> Result<()> {
907 let style = match yaml::resolve(value, ScalarStyle::Plain, Schema::Libasdf) {
908 Resolved::String => ScalarStyle::Plain,
909 _ => ScalarStyle::SingleQuoted,
910 };
911 let node = self.document.add_scalar_styled(value, style);
912 self.insert(path, node)
913 }
914
915 pub fn set_i64(&mut self, path: &str, value: i64) -> Result<()> {
917 let node = self.document.add_scalar(value.to_string());
918 self.insert(path, node)
919 }
920
921 pub fn set_u64(&mut self, path: &str, value: u64) -> Result<()> {
923 let node = self.document.add_scalar(value.to_string());
924 self.insert(path, node)
925 }
926
927 pub fn set_f64(&mut self, path: &str, value: f64) -> Result<()> {
929 let node = self.document.add_scalar(asdf_core::core::elements::format_float(value));
930 self.insert(path, node)
931 }
932
933 pub fn set_bool(&mut self, path: &str, value: bool) -> Result<()> {
935 let node = self.document.add_scalar(if value { "true" } else { "false" });
936 self.insert(path, node)
937 }
938
939 pub fn set_null(&mut self, path: &str) -> Result<()> {
941 let node = self.document.add_scalar("null");
942 self.insert(path, node)
943 }
944
945 fn set_array_bytes(
947 &mut self,
948 path: &str,
949 bytes: Vec<u8>,
950 shape: &[u64],
951 scalar: ScalarType,
952 ) -> Result<()> {
953 let index = self.blocks.len();
954 self.blocks.push(PendingBlock::compressed(bytes, self.compression));
955
956 let source = self.document.add_scalar(index.to_string());
958 let datatype = self.document.add_scalar(scalar.name());
959 let byteorder = self.document.add_scalar(ByteOrder::native().name());
960
961 let dims: Vec<NodeId> =
962 shape.iter().map(|d| self.document.add_scalar(d.to_string())).collect();
963 let shape_node = self.document.add_sequence(dims);
964 if let NodeData::Sequence { style, .. } = &mut self.document.node_mut(shape_node).data {
965 *style = yaml::CollectionStyle::Flow;
966 }
967
968 let keys: Vec<NodeId> = ["source", "datatype", "byteorder", "shape"]
969 .iter()
970 .map(|k| self.document.add_scalar(*k))
971 .collect();
972 let array = self.document.add_mapping(vec![
973 (keys[0], source),
974 (keys[1], datatype),
975 (keys[2], byteorder),
976 (keys[3], shape_node),
977 ]);
978 self.document.node_mut(array).tag =
979 Some(Tag::parse("tag:stsci.edu:asdf/core/ndarray-1.1.0"));
980
981 self.insert(path, array)
982 }
983
984 pub fn set_array<T: ArrayElement>(&mut self, path: &str, values: &[T]) -> Result<()> {
995 self.set_array_shaped(path, values, &[values.len() as u64])
996 }
997
998 pub fn set_array_shaped<T: ArrayElement>(
1002 &mut self,
1003 path: &str,
1004 values: &[T],
1005 shape: &[u64],
1006 ) -> Result<()> {
1007 let expected: u64 = shape.iter().product();
1008 if expected != values.len() as u64 {
1009 return Err(Error::new(
1010 ErrorCode::InvalidArgument,
1011 format!("shape {shape:?} needs {expected} values, got {}", values.len()),
1012 ));
1013 }
1014 let bytes = T::encode_native(values);
1015 self.set_array_bytes(path, bytes, shape, T::SCALAR)
1016 }
1017
1018 pub fn set_array_u64(&mut self, path: &str, values: &[u64]) -> Result<()> {
1020 self.set_array(path, values)
1021 }
1022
1023 pub fn set_array_i64(&mut self, path: &str, values: &[i64]) -> Result<()> {
1025 self.set_array(path, values)
1026 }
1027
1028 pub fn set_array_f64(&mut self, path: &str, values: &[f64]) -> Result<()> {
1030 self.set_array(path, values)
1031 }
1032
1033 pub fn set_array_f64_shaped(
1037 &mut self,
1038 path: &str,
1039 values: &[f64],
1040 shape: &[u64],
1041 ) -> Result<()> {
1042 self.set_array_shaped(path, values, shape)
1043 }
1044
1045 pub fn add_block(&mut self, data: Vec<u8>) -> usize {
1047 self.blocks.push(PendingBlock::compressed(data, self.compression));
1048 self.blocks.len() - 1
1049 }
1050
1051 fn writer(&self) -> Writer {
1052 let mut writer = Writer::from_document(self.document.clone());
1053 for block in &self.blocks {
1054 writer.add_block(block.clone());
1055 }
1056 writer
1057 }
1058
1059 pub fn to_bytes(&self) -> Result<Vec<u8>> {
1061 self.writer().to_bytes()
1062 }
1063
1064 pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<()> {
1066 self.writer().write_to_path(path)
1067 }
1068
1069 pub fn write_to(&self, sink: &mut impl std::io::Write) -> Result<()> {
1071 self.writer().write_to(sink)
1072 }
1073}
1074
1075pub fn native_byte_order() -> ByteOrder {
1077 ByteOrder::native()
1078}
1079
1080pub fn scalar_datatype(scalar: ScalarType) -> Datatype {
1082 Datatype::scalar(scalar)
1083}
1084
1085#[cfg(test)]
1086mod tests {
1087 use super::*;
1088
1089 fn round_trip(builder: &AsdfBuilder) -> AsdfFile {
1090 AsdfFile::from_bytes(builder.to_bytes().unwrap()).unwrap()
1091 }
1092
1093 #[test]
1094 fn an_inline_array_is_read_from_the_tree() {
1095 let bytes = b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1097%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1098grid: !core/ndarray-1.1.0\n data: [[1, 2, 3], [4, 5, 6]]\n datatype: int32\n shape: [2, 3]\n\
1099...\n"
1100 .to_vec();
1101 let file = AsdfFile::from_bytes(bytes).unwrap();
1102 let tree = file.tree().unwrap().unwrap();
1103 let array = tree.get("grid").unwrap().as_ndarray().unwrap();
1104
1105 let elements = tree.read_array(&array).unwrap();
1106 assert_eq!(elements, (1..=6).map(Element::Int).collect::<Vec<_>>());
1107
1108 assert!(file.read_array(&array).is_err());
1110
1111 assert_eq!(file.read_array_at("grid").unwrap().len(), 6);
1113 }
1114
1115 #[test]
1116 fn read_array_at_covers_a_block_backed_array() {
1117 let values: Vec<i64> = vec![3, 1, 4, 1, 5];
1118 let mut builder = AsdfBuilder::new();
1119 builder.set_array_i64("data", &values).unwrap();
1120 let file = round_trip(&builder);
1121
1122 assert_eq!(
1123 file.read_array_at("data").unwrap(),
1124 values.iter().map(|v| Element::Int(*v)).collect::<Vec<_>>()
1125 );
1126 assert!(file.read_array_at("missing").is_err());
1127 }
1128
1129 #[test]
1130 fn an_external_array_is_followed_to_the_neighbouring_file() {
1131 let dir = std::env::temp_dir().join(format!("asdf-api-exploded-{}", std::process::id()));
1132 std::fs::create_dir_all(&dir).unwrap();
1133
1134 let values: Vec<i64> = vec![10, 20, 30, 40];
1136 let mut holder = AsdfBuilder::new();
1137 holder.set_array_i64("data", &values).unwrap();
1138 holder.write_to_path(dir.join("split0000.asdf")).unwrap();
1139
1140 let referring = format!(
1142 "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1143%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1144data: !core/ndarray-1.1.0\n source: split0000.asdf\n datatype: int64\n \
1145byteorder: little\n shape: [{}]\n...\n",
1146 values.len()
1147 );
1148 let path = dir.join("split.asdf");
1149 std::fs::write(&path, referring).unwrap();
1150
1151 let file = AsdfFile::open(&path).unwrap();
1152 assert_eq!(file.block_count(), 0, "the referring file has no blocks of its own");
1153 assert_eq!(file.read_array_i64_at("data").unwrap(), values);
1154
1155 std::fs::remove_dir_all(&dir).ok();
1156 }
1157
1158 #[test]
1159 fn an_external_array_read_from_memory_is_refused() {
1160 let bytes = b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1164%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1165data: !core/ndarray-1.1.0\n source: elsewhere.asdf\n datatype: int64\n shape: [2]\n\
1166...\n"
1167 .to_vec();
1168 let file = AsdfFile::from_bytes(bytes).unwrap();
1169 let err = file.read_array_at("data").unwrap_err();
1170 assert!(err.message().contains("not read from disk"), "{}", err.message());
1171 }
1172
1173 #[test]
1174 fn an_external_array_may_not_escape_its_directory() {
1175 let dir = std::env::temp_dir().join(format!("asdf-api-escape-{}", std::process::id()));
1176 std::fs::create_dir_all(&dir).unwrap();
1177 let path = dir.join("nosy.asdf");
1178 std::fs::write(
1179 &path,
1180 "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1181%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1182data: !core/ndarray-1.1.0\n source: ../../../etc/passwd\n datatype: int64\n shape: [2]\n\
1183...\n",
1184 )
1185 .unwrap();
1186
1187 let file = AsdfFile::open(&path).unwrap();
1188 let err = file.read_array_at("data").unwrap_err();
1189 assert!(err.message().contains("climbs out"), "{}", err.message());
1190
1191 std::fs::remove_dir_all(&dir).ok();
1192 }
1193
1194 #[test]
1196 fn arrays_of_every_scalar_type_round_trip() {
1197 macro_rules! round_trip {
1198 ($ty:ty, $values:expr) => {{
1199 let values: Vec<$ty> = $values;
1200 let mut builder = AsdfBuilder::new();
1201 builder.set_array("data", &values).unwrap();
1202 let file = round_trip(&builder);
1203
1204 assert_eq!(
1206 file.block_data(0).unwrap().len(),
1207 values.len() * std::mem::size_of::<$ty>(),
1208 "{}",
1209 <$ty as ArrayElement>::SCALAR.name()
1210 );
1211
1212 let back: Vec<$ty> = file.read_array_of("data").unwrap();
1213 assert_eq!(back, values, "{}", <$ty as ArrayElement>::SCALAR.name());
1214 }};
1215 }
1216
1217 round_trip!(i8, vec![i8::MIN, -1, 0, 1, i8::MAX]);
1218 round_trip!(i16, vec![i16::MIN, -1, 0, i16::MAX]);
1219 round_trip!(i32, vec![i32::MIN, -1, 0, i32::MAX]);
1220 round_trip!(i64, vec![i64::MIN, -1, 0, i64::MAX]);
1221 round_trip!(u8, vec![0u8, 1, u8::MAX]);
1222 round_trip!(u16, vec![0u16, 1, u16::MAX]);
1223 round_trip!(u32, vec![0u32, 1, u32::MAX]);
1224 round_trip!(u64, vec![0u64, 1, u64::MAX]);
1225 round_trip!(f32, vec![f32::MIN, -0.5, 0.0, 0.5, f32::MAX]);
1226 round_trip!(f64, vec![f64::MIN, -0.5, 0.0, 0.5, f64::MAX]);
1227 }
1228
1229 #[test]
1232 fn reading_an_array_as_too_narrow_a_type_is_refused() {
1233 let mut builder = AsdfBuilder::new();
1234 builder.set_array("data", &[1i64, 70_000, 3]).unwrap();
1235 let file = round_trip(&builder);
1236
1237 assert_eq!(file.read_array_of::<i64>("data").unwrap(), [1, 70_000, 3]);
1238 assert!(file.read_array_of::<i16>("data").is_err(), "70000 has no i16");
1239 assert_eq!(file.read_array_of::<i32>("data").unwrap(), [1, 70_000, 3]);
1241 }
1242
1243 #[test]
1244 fn a_shaped_array_keeps_its_shape() {
1245 let mut builder = AsdfBuilder::new();
1246 let values: Vec<u8> = (0..6).collect();
1247 builder.set_array_shaped("grid", &values, &[2, 3]).unwrap();
1248
1249 assert!(builder.set_array_shaped("bad", &values, &[2, 4]).is_err());
1251
1252 let file = round_trip(&builder);
1253 let tree = file.tree().unwrap().unwrap();
1254 let array = tree.get("grid").unwrap().as_ndarray().unwrap();
1255 assert_eq!(array.resolved_shape(None).unwrap(), vec![2, 3]);
1256 assert_eq!(file.read_array_of::<u8>("grid").unwrap(), values);
1257 }
1258
1259 #[test]
1262 fn a_file_can_be_opened_edited_and_written_back() {
1263 let mut original = AsdfBuilder::new();
1264 original.set_str("meta/observer", "A. Eddington").unwrap();
1265 original.set_array("data", &[1u16, 2, 3]).unwrap();
1266 let file = round_trip(&original);
1267
1268 let mut edited = file.edit().unwrap();
1269 edited.set_str("meta/observer", "M. Curie").unwrap();
1270 edited.set_i64("meta/exposure", 300).unwrap();
1271 let rewritten = round_trip(&edited);
1272
1273 let tree = rewritten.tree().unwrap().unwrap();
1274 assert_eq!(tree.get("meta/observer").and_then(|v| v.as_str()), Some("M. Curie"));
1275 assert_eq!(tree.get("meta/exposure").and_then(|v| v.as_i64()), Some(300));
1276
1277 assert_eq!(rewritten.block_count(), 1);
1280 assert_eq!(rewritten.read_array_of::<u16>("data").unwrap(), [1, 2, 3]);
1281 }
1282
1283 #[test]
1285 fn editing_preserves_block_indices() {
1286 let mut original = AsdfBuilder::new();
1287 original.set_array("first", &[1u8, 2]).unwrap();
1288 original.set_array("second", &[10u8, 20, 30]).unwrap();
1289 let file = round_trip(&original);
1290
1291 let mut edited = file.edit().unwrap();
1292 edited.set_array("third", &[7u8]).unwrap();
1294 let rewritten = round_trip(&edited);
1295
1296 assert_eq!(rewritten.block_count(), 3);
1297 assert_eq!(rewritten.read_array_of::<u8>("first").unwrap(), [1, 2]);
1298 assert_eq!(rewritten.read_array_of::<u8>("second").unwrap(), [10, 20, 30]);
1299 assert_eq!(rewritten.read_array_of::<u8>("third").unwrap(), [7]);
1300 }
1301
1302 #[test]
1305 fn editing_can_change_a_files_compression() {
1306 let mut original = AsdfBuilder::new();
1307 original.set_array("data", &vec![0u8; 4096]).unwrap();
1308 let file = round_trip(&original);
1309 assert_eq!(file.block_compression(0).unwrap(), Compression::None);
1310
1311 assert_eq!(
1314 round_trip(&file.edit().unwrap().with_compression(Compression::Zlib))
1315 .block_compression(0)
1316 .unwrap(),
1317 Compression::None,
1318 "an existing block keeps its own compression"
1319 );
1320
1321 let edited = file.edit().unwrap().recompress(Compression::Zlib);
1322 let rewritten = round_trip(&edited);
1323
1324 assert_eq!(rewritten.block_compression(0).unwrap(), Compression::Zlib);
1325 assert!(rewritten.block_raw(0).unwrap().len() < 4096, "it should have shrunk");
1326 assert_eq!(rewritten.read_array_of::<u8>("data").unwrap(), vec![0u8; 4096]);
1327 }
1328
1329 #[test]
1330 fn info_and_events_are_reachable_from_rust() {
1331 let mut builder = AsdfBuilder::new();
1332 builder.set_array("data", &[1u8, 2, 3]).unwrap();
1333 let file = round_trip(&builder);
1334
1335 let rendered = file
1336 .info(InfoOptions { print_tree: true, print_blocks: true, verify_checksums: false })
1337 .unwrap();
1338 assert!(rendered.contains("data"), "{rendered}");
1339
1340 let stream = file.events(EventOptions::default());
1341 let names: Vec<&str> = stream.iter().map(Event::type_name).collect();
1342 assert_eq!(names.first(), Some(&"ASDF_ASDF_VERSION_EVENT"));
1343 assert_eq!(names.last(), Some(&"ASDF_END_EVENT"));
1344 assert!(names.contains(&"ASDF_BLOCK_EVENT"));
1345 }
1346
1347 #[test]
1349 fn the_provenance_schemas_read_from_rust() {
1350 let source = "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1351%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1352asdf_library: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n\
1353history:\n \
1354extensions:\n \
1355- !core/extension_metadata-1.0.0\n \
1356extension_class: asdf.extension._manifest.ManifestExtension\n \
1357software: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n \
1358entries:\n \
1359- !core/history_entry-1.0.0\n \
1360description: made this file\n \
1361time: !<tag:stsci.edu:asdf/time/time-1.4.0> '2025-07-23 11:56:15+00:00'\n\
1362...\n";
1363 let file = AsdfFile::from_bytes(source.as_bytes().to_vec()).unwrap();
1364 let tree = file.tree().unwrap().unwrap();
1365
1366 let meta = tree.meta().unwrap();
1368 assert_eq!(meta.asdf_library.as_ref().unwrap().name, "asdf");
1369 assert_eq!(meta.history.extensions.len(), 1);
1370 assert_eq!(meta.history.entries.len(), 1);
1371
1372 let entry = &meta.history.entries[0];
1373 assert_eq!(entry.description.as_deref(), Some("made this file"));
1374 assert_eq!(entry.time.as_ref().unwrap().civil.unwrap().unix_seconds, 1_753_271_775);
1375
1376 let library = tree.get("asdf_library").unwrap().as_software().unwrap();
1378 assert_eq!(library.version, "4.1.0");
1379
1380 let ext = tree.get("history/extensions/0").unwrap().as_extension_metadata().unwrap();
1381 assert_eq!(ext.extension_class, "asdf.extension._manifest.ManifestExtension");
1382 assert!(ext.package.is_none(), "this record names no package");
1383
1384 let time = tree.get("history/entries/0/time").unwrap().as_time().unwrap();
1385 assert_eq!(time.format, TimeFormat::Iso);
1386 assert_eq!(time.scale, TimeScale::Utc);
1387
1388 assert!(tree.get("asdf_library").unwrap().as_time().is_none());
1390 assert!(tree.get("history").unwrap().as_software().is_none());
1391 }
1392
1393 #[test]
1395 fn a_written_files_stamp_reads_back_as_software() {
1396 let file = round_trip(&AsdfBuilder::new());
1397 let tree = file.tree().unwrap().unwrap();
1398
1399 let library = tree.meta().unwrap().asdf_library.expect("asdf_library");
1400 assert_eq!(library, Software::this_library());
1401 }
1402
1403 #[test]
1404 fn writes_and_reads_scalars() {
1405 let mut builder = AsdfBuilder::new();
1406 builder.set_str("name", "Dennis Richie").unwrap();
1407 builder.set_i64("foo", 42).unwrap();
1408 builder.set_u64("big", 5_000_000_000).unwrap();
1409 builder.set_f64("ratio", 1.5).unwrap();
1410 builder.set_bool("flag", true).unwrap();
1411 builder.set_null("nothing").unwrap();
1412
1413 let file = round_trip(&builder);
1414 let tree = file.tree().unwrap().unwrap();
1415
1416 assert_eq!(tree.get("name").unwrap().as_str(), Some("Dennis Richie"));
1417 assert_eq!(tree.get("foo").unwrap().as_i64(), Some(42));
1418 assert_eq!(tree.get("big").unwrap().as_u64(), Some(5_000_000_000));
1419 assert_eq!(tree.get("ratio").unwrap().as_f64(), Some(1.5));
1420 assert_eq!(tree.get("flag").unwrap().as_bool(), Some(true));
1421 assert!(tree.get("nothing").unwrap().is_null());
1422 assert!(tree.get("missing").is_none());
1423 }
1424
1425 #[test]
1426 fn a_numeric_string_stays_a_string() {
1427 let mut builder = AsdfBuilder::new();
1428 builder.set_str("version", "42").unwrap();
1429
1430 let file = round_trip(&builder);
1431 let tree = file.tree().unwrap().unwrap();
1432 let value = tree.get("version").unwrap();
1433 assert_eq!(value.as_str(), Some("42"), "quoting was lost");
1434 assert_eq!(value.as_i64(), None, "a string must not read as an integer");
1435 }
1436
1437 #[test]
1438 fn nested_paths_are_materialised() {
1439 let mut builder = AsdfBuilder::new();
1440 builder.set_i64("meta/observation/exposure", 300).unwrap();
1441
1442 let file = round_trip(&builder);
1443 let tree = file.tree().unwrap().unwrap();
1444 assert_eq!(tree.get("meta/observation/exposure").unwrap().as_i64(), Some(300));
1445 assert!(tree.get("meta").unwrap().is_mapping());
1446 }
1447
1448 #[test]
1449 fn writes_and_reads_arrays() {
1450 let squares: Vec<u64> = (0..100u64).map(|i| i * i).collect();
1451 let mut builder = AsdfBuilder::new();
1452 builder.set_array_u64("powers/squares", &squares).unwrap();
1453
1454 let file = round_trip(&builder);
1455 let tree = file.tree().unwrap().unwrap();
1456
1457 let value = tree.get("powers/squares").unwrap();
1458 assert!(value.has_tag("core/ndarray"));
1459
1460 let array = value.as_ndarray().unwrap();
1461 let read_back = file.read_array_i64(&array).unwrap();
1462 assert_eq!(read_back.len(), 100);
1463 assert_eq!(read_back[10], 100);
1464 assert_eq!(read_back.iter().sum::<i64>(), squares.iter().sum::<u64>() as i64);
1465 }
1466
1467 #[test]
1468 fn writes_and_reads_float_arrays() {
1469 let values: Vec<f64> = (0..50).map(|i| f64::from(i) * 0.25).collect();
1470 let mut builder = AsdfBuilder::new();
1471 builder.set_array_f64("data", &values).unwrap();
1472
1473 let file = round_trip(&builder);
1474 let tree = file.tree().unwrap().unwrap();
1475 let array = tree.get("data").unwrap().as_ndarray().unwrap();
1476 assert_eq!(file.read_array_f64(&array).unwrap(), values);
1477 }
1478
1479 #[test]
1480 fn multi_dimensional_arrays_keep_their_shape() {
1481 let values: Vec<f64> = (0..12).map(f64::from).collect();
1482 let mut builder = AsdfBuilder::new();
1483 builder.set_array_f64_shaped("image", &values, &[3, 4]).unwrap();
1484
1485 let file = round_trip(&builder);
1486 let tree = file.tree().unwrap().unwrap();
1487 let array = tree.get("image").unwrap().as_ndarray().unwrap();
1488
1489 assert_eq!(array.resolved_shape(None).unwrap(), vec![3, 4]);
1490 assert_eq!(file.read_array_f64(&array).unwrap(), values);
1491 }
1492
1493 #[test]
1494 fn a_shape_that_does_not_match_the_data_is_refused() {
1495 let mut builder = AsdfBuilder::new();
1496 let err = builder.set_array_f64_shaped("image", &[1.0, 2.0], &[3, 4]).unwrap_err();
1497 assert_eq!(err.code(), ErrorCode::InvalidArgument);
1498 }
1499
1500 #[test]
1501 fn arrays_can_be_compressed() {
1502 for compression in asdf_core::compression::available() {
1503 let values: Vec<u64> = (0..1000u64).map(|i| i % 7).collect();
1504 let mut builder = AsdfBuilder::new().with_compression(compression);
1505 builder.set_array_u64("data", &values).unwrap();
1506
1507 let file = round_trip(&builder);
1508 assert_eq!(file.block_compression(0).unwrap(), compression);
1509 assert_eq!(file.verify_block(0).unwrap(), ChecksumStatus::Valid);
1510
1511 let tree = file.tree().unwrap().unwrap();
1512 let array = tree.get("data").unwrap().as_ndarray().unwrap();
1513 let read_back = file.read_array_i64(&array).unwrap();
1514 assert_eq!(read_back.len(), values.len(), "{compression:?}");
1515 assert_eq!(read_back[3], 3, "{compression:?}");
1516 }
1517 }
1518
1519 #[test]
1520 fn iterates_mappings_and_sequences() {
1521 let mut builder = AsdfBuilder::new();
1522 builder.set_i64("a", 1).unwrap();
1523 builder.set_i64("b", 2).unwrap();
1524 builder.set_i64("c", 3).unwrap();
1525
1526 let file = round_trip(&builder);
1527 let tree = file.tree().unwrap().unwrap();
1528 let root = tree.root().unwrap();
1529
1530 let keys: Vec<&str> = root.entries().map(|(k, _)| k).collect();
1533 assert_eq!(keys, ["a", "b", "c", "asdf_library"], "insertion order must survive");
1534
1535 let values: Vec<i64> = root.entries().filter_map(|(_, v)| v.as_i64()).collect();
1536 assert_eq!(values, [1, 2, 3]);
1537 }
1538
1539 #[test]
1542 fn written_files_record_what_wrote_them() {
1543 let builder = AsdfBuilder::new();
1544 let file = round_trip(&builder);
1545 let tree = file.tree().unwrap().unwrap();
1546
1547 let library = tree.get("asdf_library").expect("asdf_library");
1548 assert!(library.has_tag("core/software"));
1549 assert_eq!(library.get("name").and_then(|v| v.as_str()), Some("libasdf-rs"));
1550 assert!(library.get("version").and_then(|v| v.as_str()).is_some());
1551 assert!(library.get("homepage").and_then(|v| v.as_str()).is_some());
1552 }
1553
1554 #[test]
1557 fn an_existing_asdf_library_is_left_alone() {
1558 let source = "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1559%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1560asdf_library: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n\
1561x: 1\n...\n";
1562 let original = AsdfFile::from_bytes(source.as_bytes().to_vec()).unwrap();
1563 let tree = original.tree().unwrap().unwrap();
1564
1565 let mut builder = AsdfBuilder::new();
1566 *builder.document_mut() = tree.document().clone();
1567 let rewritten = round_trip(&builder);
1568
1569 let tree = rewritten.tree().unwrap().unwrap();
1570 let library = tree.get("asdf_library").unwrap();
1571 assert_eq!(library.get("name").and_then(|v| v.as_str()), Some("asdf"));
1572 }
1573
1574 #[test]
1575 fn sequences_index_forwards_and_backwards() {
1576 let doc = yaml::parse_document("s: [10, 20, 30]\n").unwrap();
1577 let tree = Tree { document: doc };
1578 let seq = tree.get("s").unwrap();
1579
1580 assert_eq!(seq.len(), Some(3));
1581 assert_eq!(seq.at(0).unwrap().as_i64(), Some(10));
1582 assert_eq!(seq.at(-1).unwrap().as_i64(), Some(30));
1583 assert!(seq.at(3).is_none());
1584
1585 let all: Vec<i64> = seq.items().filter_map(|v| v.as_i64()).collect();
1586 assert_eq!(all, [10, 20, 30]);
1587 }
1588
1589 #[test]
1590 fn aliases_are_visible_and_resolve() {
1591 let doc = yaml::parse_document("shared: &a {x: 1}\nother: *a\n").unwrap();
1592 let tree = Tree { document: doc };
1593
1594 let other = tree.get("other").unwrap();
1595 assert!(other.is_alias());
1596 assert_eq!(other.get("x").unwrap().as_i64(), Some(1));
1598 assert_eq!(tree.get("other/x").unwrap().as_i64(), Some(1));
1599 }
1600
1601 #[test]
1602 fn tags_are_matched_without_their_version() {
1603 let doc = yaml::parse_document(
1604 "%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1605 d: !core/ndarray-1.0.0\n source: 0\n...\n",
1606 )
1607 .unwrap();
1608 let tree = Tree { document: doc };
1609 let value = tree.get("d").unwrap();
1610 assert!(value.has_tag("core/ndarray"));
1611 assert!(!value.has_tag("core/software"));
1612 assert_eq!(value.tag().unwrap().full(), "tag:stsci.edu:asdf/core/ndarray-1.0.0");
1613 }
1614
1615 #[test]
1616 fn trees_render_back_to_yaml() {
1617 let mut builder = AsdfBuilder::new();
1618 builder.set_i64("foo", 42).unwrap();
1619
1620 let file = round_trip(&builder);
1621 let tree = file.tree().unwrap().unwrap();
1622 let text = tree.to_yaml().unwrap();
1623 assert!(text.contains("foo: 42"), "{text}");
1624 assert!(text.starts_with("%YAML 1.1"), "{text}");
1625 }
1626
1627 #[test]
1628 fn value_equality_ignores_presentation() {
1629 let a = Tree { document: yaml::parse_document("a: {x: 1, y: 2}\n").unwrap() };
1630 let b = Tree { document: yaml::parse_document("a:\n x: 1\n y: 2\n").unwrap() };
1631 assert!(a.value_eq(&b));
1632
1633 let c = Tree { document: yaml::parse_document("a: {x: 1, y: 3}\n").unwrap() };
1634 assert!(!a.value_eq(&c));
1635 }
1636
1637 #[test]
1638 fn versions_are_reported() {
1639 let builder = AsdfBuilder::new();
1640 let file = round_trip(&builder);
1641 assert_eq!(file.format_version().triple(), (1, 0, 0));
1642 assert_eq!(file.standard_version().unwrap().triple(), (1, 6, 0));
1643 }
1644
1645 #[test]
1646 fn raw_blocks_round_trip() {
1647 let mut builder = AsdfBuilder::new();
1648 let index = builder.add_block(b"arbitrary bytes".to_vec());
1649 assert_eq!(index, 0);
1650
1651 let file = round_trip(&builder);
1652 assert_eq!(file.block_count(), 1);
1653 assert_eq!(&*file.block_data(0).unwrap(), b"arbitrary bytes");
1654 }
1655}