1#![forbid(unsafe_code)]
71#![warn(missing_docs)]
72
73extern crate alloc;
77
78use alloc::borrow::Cow;
79use std::path::Path;
80
81use asdf_core::core::elements::decode_all;
82use asdf_core::yaml::{
83 self, CompareOptions, Document, NodeData, NodeId, Resolved, ScalarStyle, Schema, Tag,
84};
85use asdf_core::{PendingBlock, Reader, Writer};
86
87pub use asdf_core::ChecksumStatus;
88pub use asdf_core::compression::Compression;
93pub use asdf_core::core::datatype::{ByteOrder, Datatype, Field, ScalarType};
94pub use asdf_core::core::elements::Element;
95pub use asdf_core::core::ndarray::{Mask, Ndarray, Source};
96pub use asdf_core::core::provenance::{ExtensionMetadata, History, HistoryEntry, Meta, Software};
97pub use asdf_core::core::time::{Civil, Location, Time, TimeFormat, TimeScale};
98pub use asdf_core::error::{Error, ErrorCode};
99pub use asdf_core::events::{Event, EventOptions, render_event};
100pub use asdf_core::info::InfoOptions;
101pub use asdf_core::version::Version;
102
103pub type Result<T> = core::result::Result<T, Error>;
105
106pub trait ArrayElement: sealed::Sealed + Copy {
114 const SCALAR: ScalarType;
116
117 fn to_bytes(self) -> Vec<u8>;
119
120 fn from_element(element: &Element) -> Option<Self>;
126
127 #[doc(hidden)]
137 fn decode_native(bytes: &[u8], order: ByteOrder) -> Vec<Self>;
138
139 #[doc(hidden)]
146 fn encode_native(values: &[Self]) -> Vec<u8>;
147}
148
149mod sealed {
150 pub trait Sealed {}
151}
152
153macro_rules! integer_element {
155 ($ty:ty, $scalar:ident) => {
156 impl sealed::Sealed for $ty {}
157 impl ArrayElement for $ty {
158 const SCALAR: ScalarType = ScalarType::$scalar;
159
160 fn to_bytes(self) -> Vec<u8> {
161 self.to_ne_bytes().to_vec()
162 }
163
164 fn from_element(element: &Element) -> Option<Self> {
165 match element {
166 Element::Int(v) => <$ty>::try_from(*v).ok(),
167 Element::Uint(v) => <$ty>::try_from(*v).ok(),
168 Element::Bool(v) => Some(<$ty>::from(*v)),
169 _ => None,
170 }
171 }
172
173 fn decode_native(bytes: &[u8], order: ByteOrder) -> Vec<Self> {
174 let (chunks, _) = bytes.as_chunks::<{ size_of::<$ty>() }>();
175 match order {
176 ByteOrder::Big => chunks.iter().map(|c| <$ty>::from_be_bytes(*c)).collect(),
177 _ => chunks.iter().map(|c| <$ty>::from_le_bytes(*c)).collect(),
178 }
179 }
180
181 fn encode_native(values: &[Self]) -> Vec<u8> {
182 let mut out = Vec::with_capacity(values.len() * size_of::<$ty>());
183 for value in values {
184 out.extend_from_slice(&value.to_ne_bytes());
185 }
186 out
187 }
188 }
189 };
190}
191
192integer_element!(i8, Int8);
193integer_element!(i16, Int16);
194integer_element!(i32, Int32);
195integer_element!(i64, Int64);
196integer_element!(u8, Uint8);
197integer_element!(u16, Uint16);
198integer_element!(u32, Uint32);
199integer_element!(u64, Uint64);
200
201macro_rules! float_element {
203 ($ty:ty, $scalar:ident) => {
204 impl sealed::Sealed for $ty {}
205 impl ArrayElement for $ty {
206 const SCALAR: ScalarType = ScalarType::$scalar;
207
208 fn to_bytes(self) -> Vec<u8> {
209 self.to_ne_bytes().to_vec()
210 }
211
212 fn from_element(element: &Element) -> Option<Self> {
213 match element {
214 Element::Float(v) => Some(*v as $ty),
215 Element::Int(v) => {
218 let converted = *v as $ty;
219 (converted as i64 == *v).then_some(converted)
220 }
221 Element::Uint(v) => {
222 let converted = *v as $ty;
223 (converted as u64 == *v).then_some(converted)
224 }
225 _ => None,
226 }
227 }
228
229 fn decode_native(bytes: &[u8], order: ByteOrder) -> Vec<Self> {
230 let (chunks, _) = bytes.as_chunks::<{ size_of::<$ty>() }>();
231 match order {
232 ByteOrder::Big => chunks.iter().map(|c| <$ty>::from_be_bytes(*c)).collect(),
233 _ => chunks.iter().map(|c| <$ty>::from_le_bytes(*c)).collect(),
234 }
235 }
236
237 fn encode_native(values: &[Self]) -> Vec<u8> {
238 let mut out = Vec::with_capacity(values.len() * size_of::<$ty>());
239 for value in values {
240 out.extend_from_slice(&value.to_ne_bytes());
241 }
242 out
243 }
244 }
245 };
246}
247
248float_element!(f32, Float32);
249float_element!(f64, Float64);
250
251#[derive(Debug)]
253pub struct AsdfFile {
254 reader: Reader,
255}
256
257impl AsdfFile {
258 pub fn open(path: impl AsRef<Path>) -> Result<Self> {
263 Ok(Self { reader: Reader::open(path)? })
264 }
265
266 pub fn from_bytes(bytes: Vec<u8>) -> Result<Self> {
268 Ok(Self { reader: Reader::from_bytes(bytes)? })
269 }
270
271 pub fn format_version(&self) -> &Version {
273 &self.reader.layout().format_version
274 }
275
276 pub fn standard_version(&self) -> Option<&Version> {
278 self.reader.layout().standard_version.as_ref()
279 }
280
281 pub fn tree(&self) -> Result<Option<Tree>> {
286 Ok(self.reader.tree()?.map(|document| Tree { document }))
287 }
288
289 pub fn tree_inlined(&self) -> Result<Option<(Tree, Vec<String>)>> {
295 Ok(self.reader.tree_inlined()?.map(|(document, skipped)| (Tree { document }, skipped)))
296 }
297
298 pub fn block_count(&self) -> usize {
300 self.reader.block_count()
301 }
302
303 pub fn block_data(&self, index: usize) -> Result<Cow<'_, [u8]>> {
307 self.reader.block_data(index)
308 }
309
310 pub fn block_raw(&self, index: usize) -> Result<&[u8]> {
312 self.reader.block_raw(index)
313 }
314
315 pub fn block_compression(&self, index: usize) -> Result<Compression> {
317 self.reader.block_compression(index)
318 }
319
320 pub fn verify_block(&self, index: usize) -> Result<ChecksumStatus> {
325 Ok(self.reader.verify_block_checksum(index)?.0)
326 }
327
328 fn block_for(&self, array: &Ndarray) -> Result<usize> {
330 match &array.source {
331 Source::Block(index) => Ok(*index),
332 Source::LastBlock => self
333 .reader
334 .block_count()
335 .checked_sub(1)
336 .ok_or_else(|| Error::new(ErrorCode::InvalidArgument, "the file has no blocks")),
337 Source::External(uri) => Err(Error::new(
338 ErrorCode::InvalidArgument,
339 format!("array data lives in another file: {uri}"),
340 )),
341 Source::Inline(_) => {
342 Err(Error::new(ErrorCode::InvalidArgument, "array data is inline, not in a block"))
343 }
344 }
345 }
346
347 pub fn read_array(&self, array: &Ndarray) -> Result<Vec<Element>> {
357 if let Source::External(uri) = &array.source {
358 let data = self.reader.external_block(uri)?;
359 let shape = array.resolved_shape(Some(data.len() as u64))?;
360 return decode_all(array, &shape, &data);
361 }
362 let index = self.block_for(array)?;
363 let data = self.block_data(index)?;
364 let shape = array.resolved_shape(Some(data.len() as u64))?;
365 decode_all(array, &shape, &data)
366 }
367
368 pub fn read_array_at(&self, path: &str) -> Result<Vec<Element>> {
375 let tree = self.tree()?.ok_or_else(|| {
376 Error::new(ErrorCode::InvalidArgument, "this file has no tree to look in")
377 })?;
378 let value = tree.get(path).ok_or_else(|| {
379 Error::new(ErrorCode::InvalidArgument, format!("no value at {path:?}"))
380 })?;
381 let array = value.as_ndarray().ok_or_else(|| {
382 Error::new(ErrorCode::InvalidArgument, format!("the value at {path:?} is not an array"))
383 })?;
384 match array.source {
385 Source::Inline(_) => tree.read_array(&array),
386 _ => self.read_array(&array),
387 }
388 }
389
390 pub fn read_array_f64(&self, array: &Ndarray) -> Result<Vec<f64>> {
394 as_f64(self.read_array(array)?)
395 }
396
397 pub fn read_array_i64(&self, array: &Ndarray) -> Result<Vec<i64>> {
402 as_i64(self.read_array(array)?)
403 }
404
405 pub fn read_array_f64_at(&self, path: &str) -> Result<Vec<f64>> {
407 as_f64(self.read_array_at(path)?)
408 }
409
410 pub fn read_array_i64_at(&self, path: &str) -> Result<Vec<i64>> {
412 as_i64(self.read_array_at(path)?)
413 }
414
415 pub fn read_array_of<T: ArrayElement>(&self, path: &str) -> Result<Vec<T>> {
429 let tree = self.tree()?.ok_or_else(|| {
430 Error::new(ErrorCode::InvalidArgument, "this file has no tree to look in")
431 })?;
432 let value = tree.get(path).ok_or_else(|| {
433 Error::new(ErrorCode::InvalidArgument, format!("no value at {path:?}"))
434 })?;
435 let array = value.as_ndarray().ok_or_else(|| {
436 Error::new(ErrorCode::InvalidArgument, format!("the value at {path:?} is not an array"))
437 })?;
438
439 if let Some(bytes) = self.contiguous_bytes_of(&array)?
444 && bulk_readable::<T>(&array)
445 {
446 return Ok(T::decode_native(&bytes, element_order(&array)));
447 }
448
449 match array.source {
450 Source::Inline(_) => as_type(tree.read_array(&array)?),
451 _ => as_type(self.read_array(&array)?),
452 }
453 }
454
455 fn contiguous_bytes_of(&self, array: &Ndarray) -> Result<Option<Cow<'_, [u8]>>> {
460 match &array.source {
461 Source::Inline(_) => Ok(None),
462 Source::External(uri) => Ok(Some(Cow::Owned(self.reader.external_block(uri)?))),
463 _ => {
464 let index = self.block_for(array)?;
465 Ok(Some(self.block_data(index)?))
466 }
467 }
468 }
469
470 pub fn edit(&self) -> Result<AsdfBuilder> {
489 let document = self.reader.tree()?.unwrap_or_else(Document::new_asdf);
490
491 let mut blocks = Vec::with_capacity(self.reader.block_count());
495 for index in 0..self.reader.block_count() {
496 let compression = self.reader.block_compression(index)?;
497 let data = self.reader.block_data(index)?.into_owned();
498 blocks.push(PendingBlock::compressed(data, compression));
499 }
500
501 Ok(AsdfBuilder { document, blocks, compression: Compression::None })
502 }
503
504 pub fn info(&self, options: InfoOptions) -> Result<String> {
509 asdf_core::info::render(&self.reader, options)
510 }
511
512 pub fn events(&self, options: EventOptions) -> Vec<Event> {
520 asdf_core::events::events_from(self.reader.bytes(), self.reader.layout(), options)
521 }
522}
523
524fn as_type<T: ArrayElement>(elements: Vec<Element>) -> Result<Vec<T>> {
526 elements
527 .into_iter()
528 .map(|element| {
529 T::from_element(&element).ok_or_else(|| {
530 Error::new(
531 ErrorCode::InvalidArgument,
532 format!("{element:?} does not fit {}", T::SCALAR.name()),
533 )
534 })
535 })
536 .collect()
537}
538
539fn element_order(array: &Ndarray) -> ByteOrder {
544 match array.datatype.byteorder {
545 ByteOrder::Big | ByteOrder::Little => array.datatype.byteorder,
546 _ => array.byteorder,
547 }
548}
549
550fn bulk_readable<T: ArrayElement>(array: &Ndarray) -> bool {
560 array.datatype.scalar == T::SCALAR
561 && array.datatype.size == size_of::<T>() as u64
562 && array.datatype.fields.is_empty()
563 && array.datatype.shape.is_empty()
564 && array.strides.is_none()
565 && array.offset == 0
566 && array.mask.is_none()
567}
568
569fn as_f64(elements: Vec<Element>) -> Result<Vec<f64>> {
571 elements
572 .into_iter()
573 .map(|element| match element {
574 Element::Float(v) => Ok(v),
575 Element::Int(v) => Ok(v as f64),
576 Element::Uint(v) => Ok(v as f64),
577 Element::Bool(v) => Ok(if v { 1.0 } else { 0.0 }),
578 other => Err(Error::new(
579 ErrorCode::InvalidArgument,
580 format!("{other:?} cannot be read as a number"),
581 )),
582 })
583 .collect()
584}
585
586fn as_i64(elements: Vec<Element>) -> Result<Vec<i64>> {
588 elements
589 .into_iter()
590 .map(|element| match element {
591 Element::Int(v) => Ok(v),
592 Element::Uint(v) => i64::try_from(v).map_err(|_| {
593 Error::new(ErrorCode::InvalidArgument, format!("{v} does not fit an i64"))
594 }),
595 Element::Bool(v) => Ok(i64::from(v)),
596 Element::Float(v) if v.fract() == 0.0 => Ok(v as i64),
597 other => Err(Error::new(
598 ErrorCode::InvalidArgument,
599 format!("{other:?} cannot be read as an integer"),
600 )),
601 })
602 .collect()
603}
604
605#[derive(Clone, Debug)]
607pub struct Tree {
608 document: Document,
609}
610
611impl Tree {
612 pub fn root(&self) -> Option<Value<'_>> {
614 self.document.root().map(|node| Value { document: &self.document, node })
615 }
616
617 pub fn get(&self, path: &str) -> Option<Value<'_>> {
622 self.document.lookup_str(path).map(|node| Value { document: &self.document, node })
623 }
624
625 pub fn read_array(&self, array: &Ndarray) -> Result<Vec<Element>> {
631 let shape = array.resolved_shape(None)?;
632 asdf_core::core::decode_inline(&self.document, array, &shape)
633 }
634
635 pub fn meta(&self) -> Result<Meta> {
640 let root = self
641 .document
642 .root()
643 .ok_or_else(|| Error::new(ErrorCode::InvalidArgument, "the tree has no root"))?;
644 Meta::parse(&self.document, root)
645 }
646
647 pub fn document(&self) -> &Document {
649 &self.document
650 }
651
652 pub fn value_eq(&self, other: &Tree) -> bool {
657 yaml::compare(&self.document, &other.document, CompareOptions::default()).is_equal()
658 }
659
660 pub fn to_yaml(&self) -> Result<String> {
662 yaml::emit(&self.document)
663 .map_err(|e| Error::new(ErrorCode::YamlParseFailed, e.to_string()))
664 }
665}
666
667#[derive(Clone, Copy, Debug)]
669pub struct Value<'a> {
670 document: &'a Document,
671 node: NodeId,
672}
673
674impl<'a> Value<'a> {
675 pub fn tag(&self) -> Option<&'a Tag> {
677 self.document.tag_of(self.node)
678 }
679
680 pub fn has_tag(&self, name: &str) -> bool {
685 self.tag().is_some_and(|t| t.split_version().0 == name)
686 }
687
688 pub fn as_raw_str(&self) -> Option<&'a str> {
690 self.document.resolved(self.node).as_str()
691 }
692
693 pub fn as_str(&self) -> Option<&'a str> {
697 let node = self.document.resolved(self.node);
698 let NodeData::Scalar { value, style } = &node.data else {
699 return None;
700 };
701 matches!(yaml::resolve(value, *style, Schema::Libasdf), Resolved::String)
702 .then_some(value.as_str())
703 }
704
705 pub fn as_i64(&self) -> Option<i64> {
707 match self.resolved()? {
708 Resolved::Int(v, _) => Some(v),
709 Resolved::Uint(v, _) => i64::try_from(v).ok(),
710 _ => None,
711 }
712 }
713
714 pub fn as_u64(&self) -> Option<u64> {
716 match self.resolved()? {
717 Resolved::Uint(v, _) => Some(v),
718 Resolved::Int(v, _) => u64::try_from(v).ok(),
719 _ => None,
720 }
721 }
722
723 pub fn as_f64(&self) -> Option<f64> {
725 match self.resolved()? {
726 Resolved::Double(v) => Some(v),
727 Resolved::Int(v, _) => Some(v as f64),
728 Resolved::Uint(v, _) => Some(v as f64),
729 _ => None,
730 }
731 }
732
733 pub fn as_bool(&self) -> Option<bool> {
735 match self.resolved()? {
736 Resolved::Bool(v) => Some(v),
737 _ => None,
738 }
739 }
740
741 pub fn is_null(&self) -> bool {
743 matches!(self.resolved(), Some(Resolved::Null))
744 }
745
746 fn resolved(&self) -> Option<Resolved> {
747 let node = self.document.resolved(self.node);
748 let NodeData::Scalar { value, style } = &node.data else {
749 return None;
750 };
751 Some(yaml::resolve(value, *style, Schema::Libasdf))
752 }
753
754 pub fn is_mapping(&self) -> bool {
756 self.document.resolved(self.node).is_mapping()
757 }
758
759 pub fn is_sequence(&self) -> bool {
761 self.document.resolved(self.node).is_sequence()
762 }
763
764 pub fn len(&self) -> Option<usize> {
766 self.document.container_len(self.node)
767 }
768
769 pub fn is_empty(&self) -> Option<bool> {
771 self.len().map(|n| n == 0)
772 }
773
774 pub fn get(&self, key: &str) -> Option<Value<'a>> {
776 self.document
777 .mapping_get(self.node, key)
778 .map(|node| Value { document: self.document, node })
779 }
780
781 pub fn at(&self, index: i64) -> Option<Value<'a>> {
783 self.document
784 .sequence_get(self.node, index)
785 .map(|node| Value { document: self.document, node })
786 }
787
788 pub fn path(&self, path: &str) -> Option<Value<'a>> {
790 let parsed = yaml::Path::parse(path).ok()?;
791 self.document
792 .lookup_from(self.node, &parsed)
793 .map(|node| Value { document: self.document, node })
794 }
795
796 pub fn entries(&self) -> impl Iterator<Item = (&'a str, Value<'a>)> + 'a {
798 let document = self.document;
799 let entries = document.mapping_entries(self.node).unwrap_or(&[]);
800 entries.iter().map(move |entry| {
801 let key = document.resolved(entry.key).as_str().unwrap_or_default();
802 (key, Value { document, node: entry.value })
803 })
804 }
805
806 pub fn items(&self) -> impl Iterator<Item = Value<'a>> + 'a {
808 let document = self.document;
809 let items = document.sequence_items(self.node).unwrap_or(&[]);
810 items.iter().map(move |node| Value { document, node: *node })
811 }
812
813 pub fn as_ndarray(&self) -> Option<Ndarray> {
818 Ndarray::parse(self.document, self.node).ok()
819 }
820
821 pub fn as_software(&self) -> Option<Software> {
823 Software::parse(self.document, self.node).ok()
824 }
825
826 pub fn as_history_entry(&self) -> Option<HistoryEntry> {
828 HistoryEntry::parse(self.document, self.node).ok()
829 }
830
831 pub fn as_extension_metadata(&self) -> Option<ExtensionMetadata> {
833 ExtensionMetadata::parse(self.document, self.node).ok()
834 }
835
836 pub fn as_time(&self) -> Option<Time> {
842 Time::parse(self.document, self.node).ok()
843 }
844
845 pub fn is_alias(&self) -> bool {
847 self.document.node(self.node).is_alias()
848 }
849}
850
851#[derive(Debug)]
853pub struct AsdfBuilder {
854 document: Document,
855 blocks: Vec<PendingBlock>,
856 compression: Compression,
857}
858
859impl Default for AsdfBuilder {
860 fn default() -> Self {
861 Self::new()
862 }
863}
864
865impl AsdfBuilder {
866 pub fn new() -> Self {
868 let mut document = Document::new_asdf();
869 let root = document.add(yaml::Node::mapping());
870 document.node_mut(root).tag = Some(Tag::parse("tag:stsci.edu:asdf/core/asdf-1.1.0"));
871 document.set_root(root);
872 Self { document, blocks: Vec::new(), compression: Compression::None }
873 }
874
875 pub fn with_compression(mut self, compression: Compression) -> Self {
881 self.compression = compression;
882 self
883 }
884
885 pub fn recompress(mut self, compression: Compression) -> Self {
891 self.compression = compression;
892 for block in &mut self.blocks {
893 block.compression = compression;
894 }
895 self
896 }
897
898 pub fn document_mut(&mut self) -> &mut Document {
900 &mut self.document
901 }
902
903 fn insert(&mut self, path: &str, node: NodeId) -> Result<()> {
904 self.document
905 .insert_at_str(path, node)
906 .map(|_| ())
907 .map_err(|e| Error::new(ErrorCode::InvalidArgument, e.to_string()))
908 }
909
910 pub fn set_str(&mut self, path: &str, value: &str) -> Result<()> {
912 let style = match yaml::resolve(value, ScalarStyle::Plain, Schema::Libasdf) {
913 Resolved::String => ScalarStyle::Plain,
914 _ => ScalarStyle::SingleQuoted,
915 };
916 let node = self.document.add_scalar_styled(value, style);
917 self.insert(path, node)
918 }
919
920 pub fn set_i64(&mut self, path: &str, value: i64) -> Result<()> {
922 let node = self.document.add_scalar(value.to_string());
923 self.insert(path, node)
924 }
925
926 pub fn set_u64(&mut self, path: &str, value: u64) -> Result<()> {
928 let node = self.document.add_scalar(value.to_string());
929 self.insert(path, node)
930 }
931
932 pub fn set_f64(&mut self, path: &str, value: f64) -> Result<()> {
934 let node = self.document.add_scalar(asdf_core::core::elements::format_float(value));
935 self.insert(path, node)
936 }
937
938 pub fn set_bool(&mut self, path: &str, value: bool) -> Result<()> {
940 let node = self.document.add_scalar(if value { "true" } else { "false" });
941 self.insert(path, node)
942 }
943
944 pub fn set_null(&mut self, path: &str) -> Result<()> {
946 let node = self.document.add_scalar("null");
947 self.insert(path, node)
948 }
949
950 fn set_array_bytes(
952 &mut self,
953 path: &str,
954 bytes: Vec<u8>,
955 shape: &[u64],
956 scalar: ScalarType,
957 ) -> Result<()> {
958 let index = self.blocks.len();
959 self.blocks.push(PendingBlock::compressed(bytes, self.compression));
960
961 let source = self.document.add_scalar(index.to_string());
963 let datatype = self.document.add_scalar(scalar.name());
964 let byteorder = self.document.add_scalar(ByteOrder::native().name());
965
966 let dims: Vec<NodeId> =
967 shape.iter().map(|d| self.document.add_scalar(d.to_string())).collect();
968 let shape_node = self.document.add_sequence(dims);
969 if let NodeData::Sequence { style, .. } = &mut self.document.node_mut(shape_node).data {
970 *style = yaml::CollectionStyle::Flow;
971 }
972
973 let keys: Vec<NodeId> = ["source", "datatype", "byteorder", "shape"]
974 .iter()
975 .map(|k| self.document.add_scalar(*k))
976 .collect();
977 let array = self.document.add_mapping(vec![
978 (keys[0], source),
979 (keys[1], datatype),
980 (keys[2], byteorder),
981 (keys[3], shape_node),
982 ]);
983 self.document.node_mut(array).tag =
984 Some(Tag::parse("tag:stsci.edu:asdf/core/ndarray-1.1.0"));
985
986 self.insert(path, array)
987 }
988
989 pub fn set_array<T: ArrayElement>(&mut self, path: &str, values: &[T]) -> Result<()> {
1000 self.set_array_shaped(path, values, &[values.len() as u64])
1001 }
1002
1003 pub fn set_array_shaped<T: ArrayElement>(
1007 &mut self,
1008 path: &str,
1009 values: &[T],
1010 shape: &[u64],
1011 ) -> Result<()> {
1012 let expected =
1013 shape.iter().try_fold(1u64, |acc, d| acc.checked_mul(*d)).ok_or_else(|| {
1014 Error::new(
1015 ErrorCode::OverLimit,
1016 format!("shape {shape:?} has more elements than 64 bits hold"),
1017 )
1018 })?;
1019 if expected != values.len() as u64 {
1020 return Err(Error::new(
1021 ErrorCode::InvalidArgument,
1022 format!("shape {shape:?} needs {expected} values, got {}", values.len()),
1023 ));
1024 }
1025 let bytes = T::encode_native(values);
1026 self.set_array_bytes(path, bytes, shape, T::SCALAR)
1027 }
1028
1029 pub fn set_array_u64(&mut self, path: &str, values: &[u64]) -> Result<()> {
1031 self.set_array(path, values)
1032 }
1033
1034 pub fn set_array_i64(&mut self, path: &str, values: &[i64]) -> Result<()> {
1036 self.set_array(path, values)
1037 }
1038
1039 pub fn set_array_f64(&mut self, path: &str, values: &[f64]) -> Result<()> {
1041 self.set_array(path, values)
1042 }
1043
1044 pub fn set_array_f64_shaped(
1048 &mut self,
1049 path: &str,
1050 values: &[f64],
1051 shape: &[u64],
1052 ) -> Result<()> {
1053 self.set_array_shaped(path, values, shape)
1054 }
1055
1056 pub fn add_block(&mut self, data: Vec<u8>) -> usize {
1058 self.blocks.push(PendingBlock::compressed(data, self.compression));
1059 self.blocks.len() - 1
1060 }
1061
1062 fn writer(&self) -> Writer {
1063 let mut writer = Writer::from_document(self.document.clone());
1064 for block in &self.blocks {
1065 writer.add_block(block.clone());
1066 }
1067 writer
1068 }
1069
1070 pub fn to_bytes(&self) -> Result<Vec<u8>> {
1072 self.writer().to_bytes()
1073 }
1074
1075 pub fn write_to_path(&self, path: impl AsRef<Path>) -> Result<()> {
1077 self.writer().write_to_path(path)
1078 }
1079
1080 pub fn write_to(&self, sink: &mut impl std::io::Write) -> Result<()> {
1082 self.writer().write_to(sink)
1083 }
1084}
1085
1086pub fn native_byte_order() -> ByteOrder {
1088 ByteOrder::native()
1089}
1090
1091pub fn scalar_datatype(scalar: ScalarType) -> Datatype {
1093 Datatype::scalar(scalar)
1094}
1095
1096#[cfg(test)]
1097mod tests {
1098 use super::*;
1099
1100 fn round_trip(builder: &AsdfBuilder) -> AsdfFile {
1101 AsdfFile::from_bytes(builder.to_bytes().unwrap()).unwrap()
1102 }
1103
1104 #[test]
1105 fn an_inline_array_is_read_from_the_tree() {
1106 let bytes = b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1108%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1109grid: !core/ndarray-1.1.0\n data: [[1, 2, 3], [4, 5, 6]]\n datatype: int32\n shape: [2, 3]\n\
1110...\n"
1111 .to_vec();
1112 let file = AsdfFile::from_bytes(bytes).unwrap();
1113 let tree = file.tree().unwrap().unwrap();
1114 let array = tree.get("grid").unwrap().as_ndarray().unwrap();
1115
1116 let elements = tree.read_array(&array).unwrap();
1117 assert_eq!(elements, (1..=6).map(Element::Int).collect::<Vec<_>>());
1118
1119 assert!(file.read_array(&array).is_err());
1121
1122 assert_eq!(file.read_array_at("grid").unwrap().len(), 6);
1124 }
1125
1126 #[test]
1127 fn read_array_at_covers_a_block_backed_array() {
1128 let values: Vec<i64> = vec![3, 1, 4, 1, 5];
1129 let mut builder = AsdfBuilder::new();
1130 builder.set_array_i64("data", &values).unwrap();
1131 let file = round_trip(&builder);
1132
1133 assert_eq!(
1134 file.read_array_at("data").unwrap(),
1135 values.iter().map(|v| Element::Int(*v)).collect::<Vec<_>>()
1136 );
1137 assert!(file.read_array_at("missing").is_err());
1138 }
1139
1140 #[test]
1141 fn an_external_array_is_followed_to_the_neighbouring_file() {
1142 let dir = std::env::temp_dir().join(format!("asdf-api-exploded-{}", std::process::id()));
1143 std::fs::create_dir_all(&dir).unwrap();
1144
1145 let values: Vec<i64> = vec![10, 20, 30, 40];
1147 let mut holder = AsdfBuilder::new();
1148 holder.set_array_i64("data", &values).unwrap();
1149 holder.write_to_path(dir.join("split0000.asdf")).unwrap();
1150
1151 let referring = format!(
1153 "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1154%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1155data: !core/ndarray-1.1.0\n source: split0000.asdf\n datatype: int64\n \
1156byteorder: little\n shape: [{}]\n...\n",
1157 values.len()
1158 );
1159 let path = dir.join("split.asdf");
1160 std::fs::write(&path, referring).unwrap();
1161
1162 let file = AsdfFile::open(&path).unwrap();
1163 assert_eq!(file.block_count(), 0, "the referring file has no blocks of its own");
1164 assert_eq!(file.read_array_i64_at("data").unwrap(), values);
1165
1166 std::fs::remove_dir_all(&dir).ok();
1167 }
1168
1169 #[test]
1170 fn an_external_array_read_from_memory_is_refused() {
1171 let bytes = b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1175%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1176data: !core/ndarray-1.1.0\n source: elsewhere.asdf\n datatype: int64\n shape: [2]\n\
1177...\n"
1178 .to_vec();
1179 let file = AsdfFile::from_bytes(bytes).unwrap();
1180 let err = file.read_array_at("data").unwrap_err();
1181 assert!(err.message().contains("not read from disk"), "{}", err.message());
1182 }
1183
1184 #[test]
1185 fn an_external_array_may_not_escape_its_directory() {
1186 let dir = std::env::temp_dir().join(format!("asdf-api-escape-{}", std::process::id()));
1187 std::fs::create_dir_all(&dir).unwrap();
1188 let path = dir.join("nosy.asdf");
1189 std::fs::write(
1190 &path,
1191 "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1192%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1193data: !core/ndarray-1.1.0\n source: ../../../etc/passwd\n datatype: int64\n shape: [2]\n\
1194...\n",
1195 )
1196 .unwrap();
1197
1198 let file = AsdfFile::open(&path).unwrap();
1199 let err = file.read_array_at("data").unwrap_err();
1200 assert!(err.message().contains("climbs out"), "{}", err.message());
1201
1202 std::fs::remove_dir_all(&dir).ok();
1203 }
1204
1205 #[test]
1207 fn arrays_of_every_scalar_type_round_trip() {
1208 macro_rules! round_trip {
1209 ($ty:ty, $values:expr) => {{
1210 let values: Vec<$ty> = $values;
1211 let mut builder = AsdfBuilder::new();
1212 builder.set_array("data", &values).unwrap();
1213 let file = round_trip(&builder);
1214
1215 assert_eq!(
1217 file.block_data(0).unwrap().len(),
1218 values.len() * core::mem::size_of::<$ty>(),
1219 "{}",
1220 <$ty as ArrayElement>::SCALAR.name()
1221 );
1222
1223 let back: Vec<$ty> = file.read_array_of("data").unwrap();
1224 assert_eq!(back, values, "{}", <$ty as ArrayElement>::SCALAR.name());
1225 }};
1226 }
1227
1228 round_trip!(i8, vec![i8::MIN, -1, 0, 1, i8::MAX]);
1229 round_trip!(i16, vec![i16::MIN, -1, 0, i16::MAX]);
1230 round_trip!(i32, vec![i32::MIN, -1, 0, i32::MAX]);
1231 round_trip!(i64, vec![i64::MIN, -1, 0, i64::MAX]);
1232 round_trip!(u8, vec![0u8, 1, u8::MAX]);
1233 round_trip!(u16, vec![0u16, 1, u16::MAX]);
1234 round_trip!(u32, vec![0u32, 1, u32::MAX]);
1235 round_trip!(u64, vec![0u64, 1, u64::MAX]);
1236 round_trip!(f32, vec![f32::MIN, -0.5, 0.0, 0.5, f32::MAX]);
1237 round_trip!(f64, vec![f64::MIN, -0.5, 0.0, 0.5, f64::MAX]);
1238 }
1239
1240 #[test]
1243 fn reading_an_array_as_too_narrow_a_type_is_refused() {
1244 let mut builder = AsdfBuilder::new();
1245 builder.set_array("data", &[1i64, 70_000, 3]).unwrap();
1246 let file = round_trip(&builder);
1247
1248 assert_eq!(file.read_array_of::<i64>("data").unwrap(), [1, 70_000, 3]);
1249 assert!(file.read_array_of::<i16>("data").is_err(), "70000 has no i16");
1250 assert_eq!(file.read_array_of::<i32>("data").unwrap(), [1, 70_000, 3]);
1252 }
1253
1254 #[test]
1255 fn a_shaped_array_keeps_its_shape() {
1256 let mut builder = AsdfBuilder::new();
1257 let values: Vec<u8> = (0..6).collect();
1258 builder.set_array_shaped("grid", &values, &[2, 3]).unwrap();
1259
1260 assert!(builder.set_array_shaped("bad", &values, &[2, 4]).is_err());
1262
1263 let file = round_trip(&builder);
1264 let tree = file.tree().unwrap().unwrap();
1265 let array = tree.get("grid").unwrap().as_ndarray().unwrap();
1266 assert_eq!(array.resolved_shape(None).unwrap(), vec![2, 3]);
1267 assert_eq!(file.read_array_of::<u8>("grid").unwrap(), values);
1268 }
1269
1270 #[test]
1273 fn a_file_can_be_opened_edited_and_written_back() {
1274 let mut original = AsdfBuilder::new();
1275 original.set_str("meta/observer", "A. Eddington").unwrap();
1276 original.set_array("data", &[1u16, 2, 3]).unwrap();
1277 let file = round_trip(&original);
1278
1279 let mut edited = file.edit().unwrap();
1280 edited.set_str("meta/observer", "M. Curie").unwrap();
1281 edited.set_i64("meta/exposure", 300).unwrap();
1282 let rewritten = round_trip(&edited);
1283
1284 let tree = rewritten.tree().unwrap().unwrap();
1285 assert_eq!(tree.get("meta/observer").and_then(|v| v.as_str()), Some("M. Curie"));
1286 assert_eq!(tree.get("meta/exposure").and_then(|v| v.as_i64()), Some(300));
1287
1288 assert_eq!(rewritten.block_count(), 1);
1291 assert_eq!(rewritten.read_array_of::<u16>("data").unwrap(), [1, 2, 3]);
1292 }
1293
1294 #[test]
1296 fn editing_preserves_block_indices() {
1297 let mut original = AsdfBuilder::new();
1298 original.set_array("first", &[1u8, 2]).unwrap();
1299 original.set_array("second", &[10u8, 20, 30]).unwrap();
1300 let file = round_trip(&original);
1301
1302 let mut edited = file.edit().unwrap();
1303 edited.set_array("third", &[7u8]).unwrap();
1305 let rewritten = round_trip(&edited);
1306
1307 assert_eq!(rewritten.block_count(), 3);
1308 assert_eq!(rewritten.read_array_of::<u8>("first").unwrap(), [1, 2]);
1309 assert_eq!(rewritten.read_array_of::<u8>("second").unwrap(), [10, 20, 30]);
1310 assert_eq!(rewritten.read_array_of::<u8>("third").unwrap(), [7]);
1311 }
1312
1313 #[test]
1316 fn editing_can_change_a_files_compression() {
1317 let mut original = AsdfBuilder::new();
1318 original.set_array("data", &vec![0u8; 4096]).unwrap();
1319 let file = round_trip(&original);
1320 assert_eq!(file.block_compression(0).unwrap(), Compression::None);
1321
1322 assert_eq!(
1325 round_trip(&file.edit().unwrap().with_compression(Compression::Zlib))
1326 .block_compression(0)
1327 .unwrap(),
1328 Compression::None,
1329 "an existing block keeps its own compression"
1330 );
1331
1332 let edited = file.edit().unwrap().recompress(Compression::Zlib);
1333 let rewritten = round_trip(&edited);
1334
1335 assert_eq!(rewritten.block_compression(0).unwrap(), Compression::Zlib);
1336 assert!(rewritten.block_raw(0).unwrap().len() < 4096, "it should have shrunk");
1337 assert_eq!(rewritten.read_array_of::<u8>("data").unwrap(), vec![0u8; 4096]);
1338 }
1339
1340 #[test]
1341 fn info_and_events_are_reachable_from_rust() {
1342 let mut builder = AsdfBuilder::new();
1343 builder.set_array("data", &[1u8, 2, 3]).unwrap();
1344 let file = round_trip(&builder);
1345
1346 let rendered = file
1347 .info(InfoOptions { print_tree: true, print_blocks: true, verify_checksums: false })
1348 .unwrap();
1349 assert!(rendered.contains("data"), "{rendered}");
1350
1351 let stream = file.events(EventOptions::default());
1352 let names: Vec<&str> = stream.iter().map(Event::type_name).collect();
1353 assert_eq!(names.first(), Some(&"ASDF_ASDF_VERSION_EVENT"));
1354 assert_eq!(names.last(), Some(&"ASDF_END_EVENT"));
1355 assert!(names.contains(&"ASDF_BLOCK_EVENT"));
1356 }
1357
1358 #[test]
1360 fn the_provenance_schemas_read_from_rust() {
1361 let source = "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1362%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1363asdf_library: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n\
1364history:\n \
1365extensions:\n \
1366- !core/extension_metadata-1.0.0\n \
1367extension_class: asdf.extension._manifest.ManifestExtension\n \
1368software: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n \
1369entries:\n \
1370- !core/history_entry-1.0.0\n \
1371description: made this file\n \
1372time: !<tag:stsci.edu:asdf/time/time-1.4.0> '2025-07-23 11:56:15+00:00'\n\
1373...\n";
1374 let file = AsdfFile::from_bytes(source.as_bytes().to_vec()).unwrap();
1375 let tree = file.tree().unwrap().unwrap();
1376
1377 let meta = tree.meta().unwrap();
1379 assert_eq!(meta.asdf_library.as_ref().unwrap().name, "asdf");
1380 assert_eq!(meta.history.extensions.len(), 1);
1381 assert_eq!(meta.history.entries.len(), 1);
1382
1383 let entry = &meta.history.entries[0];
1384 assert_eq!(entry.description.as_deref(), Some("made this file"));
1385 assert_eq!(entry.time.as_ref().unwrap().civil.unwrap().unix_seconds, 1_753_271_775);
1386
1387 let library = tree.get("asdf_library").unwrap().as_software().unwrap();
1389 assert_eq!(library.version, "4.1.0");
1390
1391 let ext = tree.get("history/extensions/0").unwrap().as_extension_metadata().unwrap();
1392 assert_eq!(ext.extension_class, "asdf.extension._manifest.ManifestExtension");
1393 assert!(ext.package.is_none(), "this record names no package");
1394
1395 let time = tree.get("history/entries/0/time").unwrap().as_time().unwrap();
1396 assert_eq!(time.format, TimeFormat::Iso);
1397 assert_eq!(time.scale, TimeScale::Utc);
1398
1399 assert!(tree.get("asdf_library").unwrap().as_time().is_none());
1401 assert!(tree.get("history").unwrap().as_software().is_none());
1402 }
1403
1404 #[test]
1406 fn a_written_files_stamp_reads_back_as_software() {
1407 let file = round_trip(&AsdfBuilder::new());
1408 let tree = file.tree().unwrap().unwrap();
1409
1410 let library = tree.meta().unwrap().asdf_library.expect("asdf_library");
1411 assert_eq!(library, Software::this_library());
1412 }
1413
1414 #[test]
1415 fn writes_and_reads_scalars() {
1416 let mut builder = AsdfBuilder::new();
1417 builder.set_str("name", "Dennis Richie").unwrap();
1418 builder.set_i64("foo", 42).unwrap();
1419 builder.set_u64("big", 5_000_000_000).unwrap();
1420 builder.set_f64("ratio", 1.5).unwrap();
1421 builder.set_bool("flag", true).unwrap();
1422 builder.set_null("nothing").unwrap();
1423
1424 let file = round_trip(&builder);
1425 let tree = file.tree().unwrap().unwrap();
1426
1427 assert_eq!(tree.get("name").unwrap().as_str(), Some("Dennis Richie"));
1428 assert_eq!(tree.get("foo").unwrap().as_i64(), Some(42));
1429 assert_eq!(tree.get("big").unwrap().as_u64(), Some(5_000_000_000));
1430 assert_eq!(tree.get("ratio").unwrap().as_f64(), Some(1.5));
1431 assert_eq!(tree.get("flag").unwrap().as_bool(), Some(true));
1432 assert!(tree.get("nothing").unwrap().is_null());
1433 assert!(tree.get("missing").is_none());
1434 }
1435
1436 #[test]
1437 fn a_numeric_string_stays_a_string() {
1438 let mut builder = AsdfBuilder::new();
1439 builder.set_str("version", "42").unwrap();
1440
1441 let file = round_trip(&builder);
1442 let tree = file.tree().unwrap().unwrap();
1443 let value = tree.get("version").unwrap();
1444 assert_eq!(value.as_str(), Some("42"), "quoting was lost");
1445 assert_eq!(value.as_i64(), None, "a string must not read as an integer");
1446 }
1447
1448 #[test]
1449 fn nested_paths_are_materialised() {
1450 let mut builder = AsdfBuilder::new();
1451 builder.set_i64("meta/observation/exposure", 300).unwrap();
1452
1453 let file = round_trip(&builder);
1454 let tree = file.tree().unwrap().unwrap();
1455 assert_eq!(tree.get("meta/observation/exposure").unwrap().as_i64(), Some(300));
1456 assert!(tree.get("meta").unwrap().is_mapping());
1457 }
1458
1459 #[test]
1460 fn writes_and_reads_arrays() {
1461 let squares: Vec<u64> = (0..100u64).map(|i| i * i).collect();
1462 let mut builder = AsdfBuilder::new();
1463 builder.set_array_u64("powers/squares", &squares).unwrap();
1464
1465 let file = round_trip(&builder);
1466 let tree = file.tree().unwrap().unwrap();
1467
1468 let value = tree.get("powers/squares").unwrap();
1469 assert!(value.has_tag("core/ndarray"));
1470
1471 let array = value.as_ndarray().unwrap();
1472 let read_back = file.read_array_i64(&array).unwrap();
1473 assert_eq!(read_back.len(), 100);
1474 assert_eq!(read_back[10], 100);
1475 assert_eq!(read_back.iter().sum::<i64>(), squares.iter().sum::<u64>() as i64);
1476 }
1477
1478 #[test]
1479 fn writes_and_reads_float_arrays() {
1480 let values: Vec<f64> = (0..50).map(|i| f64::from(i) * 0.25).collect();
1481 let mut builder = AsdfBuilder::new();
1482 builder.set_array_f64("data", &values).unwrap();
1483
1484 let file = round_trip(&builder);
1485 let tree = file.tree().unwrap().unwrap();
1486 let array = tree.get("data").unwrap().as_ndarray().unwrap();
1487 assert_eq!(file.read_array_f64(&array).unwrap(), values);
1488 }
1489
1490 #[test]
1491 fn multi_dimensional_arrays_keep_their_shape() {
1492 let values: Vec<f64> = (0..12).map(f64::from).collect();
1493 let mut builder = AsdfBuilder::new();
1494 builder.set_array_f64_shaped("image", &values, &[3, 4]).unwrap();
1495
1496 let file = round_trip(&builder);
1497 let tree = file.tree().unwrap().unwrap();
1498 let array = tree.get("image").unwrap().as_ndarray().unwrap();
1499
1500 assert_eq!(array.resolved_shape(None).unwrap(), vec![3, 4]);
1501 assert_eq!(file.read_array_f64(&array).unwrap(), values);
1502 }
1503
1504 #[test]
1505 fn a_shape_that_does_not_match_the_data_is_refused() {
1506 let mut builder = AsdfBuilder::new();
1507 let err = builder.set_array_f64_shaped("image", &[1.0, 2.0], &[3, 4]).unwrap_err();
1508 assert_eq!(err.code(), ErrorCode::InvalidArgument);
1509 }
1510
1511 #[test]
1512 fn arrays_can_be_compressed() {
1513 for compression in asdf_core::compression::available() {
1514 let values: Vec<u64> = (0..1000u64).map(|i| i % 7).collect();
1515 let mut builder = AsdfBuilder::new().with_compression(compression);
1516 builder.set_array_u64("data", &values).unwrap();
1517
1518 let file = round_trip(&builder);
1519 assert_eq!(file.block_compression(0).unwrap(), compression);
1520 assert_eq!(file.verify_block(0).unwrap(), ChecksumStatus::Valid);
1521
1522 let tree = file.tree().unwrap().unwrap();
1523 let array = tree.get("data").unwrap().as_ndarray().unwrap();
1524 let read_back = file.read_array_i64(&array).unwrap();
1525 assert_eq!(read_back.len(), values.len(), "{compression:?}");
1526 assert_eq!(read_back[3], 3, "{compression:?}");
1527 }
1528 }
1529
1530 #[test]
1531 fn iterates_mappings_and_sequences() {
1532 let mut builder = AsdfBuilder::new();
1533 builder.set_i64("a", 1).unwrap();
1534 builder.set_i64("b", 2).unwrap();
1535 builder.set_i64("c", 3).unwrap();
1536
1537 let file = round_trip(&builder);
1538 let tree = file.tree().unwrap().unwrap();
1539 let root = tree.root().unwrap();
1540
1541 let keys: Vec<&str> = root.entries().map(|(k, _)| k).collect();
1544 assert_eq!(keys, ["a", "b", "c", "asdf_library"], "insertion order must survive");
1545
1546 let values: Vec<i64> = root.entries().filter_map(|(_, v)| v.as_i64()).collect();
1547 assert_eq!(values, [1, 2, 3]);
1548 }
1549
1550 #[test]
1553 fn written_files_record_what_wrote_them() {
1554 let builder = AsdfBuilder::new();
1555 let file = round_trip(&builder);
1556 let tree = file.tree().unwrap().unwrap();
1557
1558 let library = tree.get("asdf_library").expect("asdf_library");
1559 assert!(library.has_tag("core/software"));
1560 assert_eq!(library.get("name").and_then(|v| v.as_str()), Some("libasdf-rs"));
1561 assert!(library.get("version").and_then(|v| v.as_str()).is_some());
1562 assert!(library.get("homepage").and_then(|v| v.as_str()).is_some());
1563 }
1564
1565 #[test]
1568 fn an_existing_asdf_library_is_left_alone() {
1569 let source = "#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n\
1570%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1571asdf_library: !core/software-1.0.0 {name: asdf, version: 4.1.0}\n\
1572x: 1\n...\n";
1573 let original = AsdfFile::from_bytes(source.as_bytes().to_vec()).unwrap();
1574 let tree = original.tree().unwrap().unwrap();
1575
1576 let mut builder = AsdfBuilder::new();
1577 *builder.document_mut() = tree.document().clone();
1578 let rewritten = round_trip(&builder);
1579
1580 let tree = rewritten.tree().unwrap().unwrap();
1581 let library = tree.get("asdf_library").unwrap();
1582 assert_eq!(library.get("name").and_then(|v| v.as_str()), Some("asdf"));
1583 }
1584
1585 #[test]
1586 fn sequences_index_forwards_and_backwards() {
1587 let doc = yaml::parse_document("s: [10, 20, 30]\n").unwrap();
1588 let tree = Tree { document: doc };
1589 let seq = tree.get("s").unwrap();
1590
1591 assert_eq!(seq.len(), Some(3));
1592 assert_eq!(seq.at(0).unwrap().as_i64(), Some(10));
1593 assert_eq!(seq.at(-1).unwrap().as_i64(), Some(30));
1594 assert!(seq.at(3).is_none());
1595
1596 let all: Vec<i64> = seq.items().filter_map(|v| v.as_i64()).collect();
1597 assert_eq!(all, [10, 20, 30]);
1598 }
1599
1600 #[test]
1601 fn aliases_are_visible_and_resolve() {
1602 let doc = yaml::parse_document("shared: &a {x: 1}\nother: *a\n").unwrap();
1603 let tree = Tree { document: doc };
1604
1605 let other = tree.get("other").unwrap();
1606 assert!(other.is_alias());
1607 assert_eq!(other.get("x").unwrap().as_i64(), Some(1));
1609 assert_eq!(tree.get("other/x").unwrap().as_i64(), Some(1));
1610 }
1611
1612 #[test]
1613 fn tags_are_matched_without_their_version() {
1614 let doc = yaml::parse_document(
1615 "%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n\
1616 d: !core/ndarray-1.0.0\n source: 0\n...\n",
1617 )
1618 .unwrap();
1619 let tree = Tree { document: doc };
1620 let value = tree.get("d").unwrap();
1621 assert!(value.has_tag("core/ndarray"));
1622 assert!(!value.has_tag("core/software"));
1623 assert_eq!(value.tag().unwrap().full(), "tag:stsci.edu:asdf/core/ndarray-1.0.0");
1624 }
1625
1626 #[test]
1627 fn trees_render_back_to_yaml() {
1628 let mut builder = AsdfBuilder::new();
1629 builder.set_i64("foo", 42).unwrap();
1630
1631 let file = round_trip(&builder);
1632 let tree = file.tree().unwrap().unwrap();
1633 let text = tree.to_yaml().unwrap();
1634 assert!(text.contains("foo: 42"), "{text}");
1635 assert!(text.starts_with("%YAML 1.1"), "{text}");
1636 }
1637
1638 #[test]
1639 fn value_equality_ignores_presentation() {
1640 let a = Tree { document: yaml::parse_document("a: {x: 1, y: 2}\n").unwrap() };
1641 let b = Tree { document: yaml::parse_document("a:\n x: 1\n y: 2\n").unwrap() };
1642 assert!(a.value_eq(&b));
1643
1644 let c = Tree { document: yaml::parse_document("a: {x: 1, y: 3}\n").unwrap() };
1645 assert!(!a.value_eq(&c));
1646 }
1647
1648 #[test]
1649 fn versions_are_reported() {
1650 let builder = AsdfBuilder::new();
1651 let file = round_trip(&builder);
1652 assert_eq!(file.format_version().triple(), (1, 0, 0));
1653 assert_eq!(file.standard_version().unwrap().triple(), (1, 6, 0));
1654 }
1655
1656 #[test]
1657 fn raw_blocks_round_trip() {
1658 let mut builder = AsdfBuilder::new();
1659 let index = builder.add_block(b"arbitrary bytes".to_vec());
1660 assert_eq!(index, 0);
1661
1662 let file = round_trip(&builder);
1663 assert_eq!(file.block_count(), 1);
1664 assert_eq!(&*file.block_data(0).unwrap(), b"arbitrary bytes");
1665 }
1666}