1#[cfg(not(feature = "std"))]
6use alloc::{boxed::Box, string::String, string::ToString, vec, vec::Vec};
7
8use crate::attribute::AttributeMessage;
9use crate::chunked_write::{ChunkMeta, ChunkOptions, ChunkProvider};
10use crate::compound::CompoundType;
11use crate::convert::TryToUsize;
12use crate::dataspace::{Dataspace, DataspaceType};
13use crate::datatype::{
14 CharacterSet, CompoundMember, Datatype, DatatypeByteOrder, EnumMember, StringPadding,
15};
16use crate::scaleoffset::ScaleOffset;
17
18pub fn make_f64_type() -> Datatype {
21 Datatype::FloatingPoint {
22 size: 8,
23 byte_order: DatatypeByteOrder::LittleEndian,
24 bit_offset: 0,
25 bit_precision: 64,
26 exponent_location: 52,
27 exponent_size: 11,
28 mantissa_location: 0,
29 mantissa_size: 52,
30 exponent_bias: 1023,
31 }
32}
33
34pub fn make_f32_type() -> Datatype {
35 Datatype::FloatingPoint {
36 size: 4,
37 byte_order: DatatypeByteOrder::LittleEndian,
38 bit_offset: 0,
39 bit_precision: 32,
40 exponent_location: 23,
41 exponent_size: 8,
42 mantissa_location: 0,
43 mantissa_size: 23,
44 exponent_bias: 127,
45 }
46}
47
48pub fn make_i32_type() -> Datatype {
49 Datatype::FixedPoint {
50 size: 4,
51 byte_order: DatatypeByteOrder::LittleEndian,
52 signed: true,
53 bit_offset: 0,
54 bit_precision: 32,
55 }
56}
57
58pub fn make_i64_type() -> Datatype {
59 Datatype::FixedPoint {
60 size: 8,
61 byte_order: DatatypeByteOrder::LittleEndian,
62 signed: true,
63 bit_offset: 0,
64 bit_precision: 64,
65 }
66}
67
68pub fn make_u8_type() -> Datatype {
69 Datatype::FixedPoint {
70 size: 1,
71 byte_order: DatatypeByteOrder::LittleEndian,
72 signed: false,
73 bit_offset: 0,
74 bit_precision: 8,
75 }
76}
77
78pub fn make_i8_type() -> Datatype {
79 Datatype::FixedPoint {
80 size: 1,
81 byte_order: DatatypeByteOrder::LittleEndian,
82 signed: true,
83 bit_offset: 0,
84 bit_precision: 8,
85 }
86}
87
88pub fn make_i16_type() -> Datatype {
89 Datatype::FixedPoint {
90 size: 2,
91 byte_order: DatatypeByteOrder::LittleEndian,
92 signed: true,
93 bit_offset: 0,
94 bit_precision: 16,
95 }
96}
97
98pub fn make_u16_type() -> Datatype {
99 Datatype::FixedPoint {
100 size: 2,
101 byte_order: DatatypeByteOrder::LittleEndian,
102 signed: false,
103 bit_offset: 0,
104 bit_precision: 16,
105 }
106}
107
108pub fn make_u32_type() -> Datatype {
109 Datatype::FixedPoint {
110 size: 4,
111 byte_order: DatatypeByteOrder::LittleEndian,
112 signed: false,
113 bit_offset: 0,
114 bit_precision: 32,
115 }
116}
117
118pub fn make_u64_type() -> Datatype {
119 Datatype::FixedPoint {
120 size: 8,
121 byte_order: DatatypeByteOrder::LittleEndian,
122 signed: false,
123 bit_offset: 0,
124 bit_precision: 64,
125 }
126}
127
128pub fn make_object_reference_type() -> Datatype {
129 Datatype::Reference {
130 size: 8,
131 ref_type: crate::datatype::ReferenceType::Object,
132 }
133}
134
135pub fn make_vlen_string_type(charset: CharacterSet) -> Datatype {
146 Datatype::VariableLength {
147 is_string: true,
148 padding: Some(StringPadding::NullTerminate),
149 charset: Some(charset),
150 base_type: Box::new(make_u8_type()),
151 }
152}
153
154pub struct CompoundTypeBuilder {
158 fields: Vec<(String, Datatype)>,
159}
160
161impl CompoundTypeBuilder {
162 pub fn new() -> Self {
163 Self { fields: Vec::new() }
164 }
165
166 pub fn field(mut self, name: &str, datatype: Datatype) -> Self {
168 self.fields.push((name.to_string(), datatype));
169 self
170 }
171
172 pub fn f64_field(self, name: &str) -> Self {
174 self.field(name, make_f64_type())
175 }
176 pub fn f32_field(self, name: &str) -> Self {
178 self.field(name, make_f32_type())
179 }
180 pub fn i32_field(self, name: &str) -> Self {
182 self.field(name, make_i32_type())
183 }
184 pub fn i64_field(self, name: &str) -> Self {
186 self.field(name, make_i64_type())
187 }
188 pub fn u8_field(self, name: &str) -> Self {
190 self.field(name, make_u8_type())
191 }
192 pub fn i8_field(self, name: &str) -> Self {
194 self.field(name, make_i8_type())
195 }
196 pub fn i16_field(self, name: &str) -> Self {
198 self.field(name, make_i16_type())
199 }
200 pub fn u16_field(self, name: &str) -> Self {
202 self.field(name, make_u16_type())
203 }
204 pub fn u32_field(self, name: &str) -> Self {
206 self.field(name, make_u32_type())
207 }
208 pub fn u64_field(self, name: &str) -> Self {
210 self.field(name, make_u64_type())
211 }
212
213 pub fn build(self) -> Datatype {
215 let mut offset = 0u64;
216 let mut members = Vec::with_capacity(self.fields.len());
217 for (name, dt) in self.fields {
218 let sz = dt.type_size();
219 members.push(CompoundMember {
220 name,
221 byte_offset: offset,
222 datatype: dt,
223 });
224 offset += sz as u64;
225 }
226 Datatype::Compound {
227 #[expect(
228 clippy::cast_possible_truncation,
229 reason = "accumulated compound size is stored in the 4-byte datatype size field"
230 )]
231 size: offset as u32,
232 members,
233 }
234 }
235}
236
237impl Default for CompoundTypeBuilder {
238 fn default() -> Self {
239 Self::new()
240 }
241}
242
243pub struct ExplicitCompoundTypeBuilder {
249 size: u32,
250 fields: Vec<CompoundMember>,
251}
252
253impl ExplicitCompoundTypeBuilder {
254 pub fn field(mut self, name: &str, byte_offset: u64, datatype: Datatype) -> Self {
256 self.fields.push(CompoundMember {
257 name: name.to_string(),
258 byte_offset,
259 datatype,
260 });
261 self
262 }
263
264 pub fn f64_field(self, name: &str, byte_offset: u64) -> Self {
266 self.field(name, byte_offset, make_f64_type())
267 }
268
269 pub fn f32_field(self, name: &str, byte_offset: u64) -> Self {
271 self.field(name, byte_offset, make_f32_type())
272 }
273
274 pub fn i32_field(self, name: &str, byte_offset: u64) -> Self {
276 self.field(name, byte_offset, make_i32_type())
277 }
278
279 pub fn i64_field(self, name: &str, byte_offset: u64) -> Self {
281 self.field(name, byte_offset, make_i64_type())
282 }
283
284 pub fn u8_field(self, name: &str, byte_offset: u64) -> Self {
286 self.field(name, byte_offset, make_u8_type())
287 }
288
289 pub fn i8_field(self, name: &str, byte_offset: u64) -> Self {
291 self.field(name, byte_offset, make_i8_type())
292 }
293
294 pub fn i16_field(self, name: &str, byte_offset: u64) -> Self {
296 self.field(name, byte_offset, make_i16_type())
297 }
298
299 pub fn u16_field(self, name: &str, byte_offset: u64) -> Self {
301 self.field(name, byte_offset, make_u16_type())
302 }
303
304 pub fn u32_field(self, name: &str, byte_offset: u64) -> Self {
306 self.field(name, byte_offset, make_u32_type())
307 }
308
309 pub fn u64_field(self, name: &str, byte_offset: u64) -> Self {
311 self.field(name, byte_offset, make_u64_type())
312 }
313
314 pub fn build(mut self) -> Result<Datatype, crate::error::FormatError> {
316 use crate::error::FormatError;
317
318 if self.size == 0 {
319 return Err(FormatError::InvalidCompoundSize);
320 }
321 if self.fields.is_empty() {
322 return Err(FormatError::EmptyCompoundType);
323 }
324
325 for (index, field) in self.fields.iter().enumerate() {
326 if self.fields[..index]
327 .iter()
328 .any(|earlier| earlier.name == field.name)
329 {
330 return Err(FormatError::DuplicateCompoundField(field.name.clone()));
331 }
332 let field_size = field.datatype.type_size();
333 let end = field.byte_offset.checked_add(u64::from(field_size));
334 if field_size == 0 || end.is_none_or(|end| end > u64::from(self.size)) {
335 return Err(FormatError::CompoundFieldOutOfBounds {
336 name: field.name.clone(),
337 offset: field.byte_offset,
338 field_size,
339 compound_size: self.size,
340 });
341 }
342 }
343
344 self.fields.sort_by_key(|field| field.byte_offset);
345 for fields in self.fields.windows(2) {
346 let first_end = fields[0].byte_offset + u64::from(fields[0].datatype.type_size());
347 if first_end > fields[1].byte_offset {
348 return Err(FormatError::CompoundFieldOverlap {
349 first: fields[0].name.clone(),
350 second: fields[1].name.clone(),
351 });
352 }
353 }
354
355 Ok(Datatype::Compound {
356 size: self.size,
357 members: self.fields,
358 })
359 }
360}
361
362impl CompoundTypeBuilder {
363 pub fn with_size(size: u32) -> ExplicitCompoundTypeBuilder {
365 ExplicitCompoundTypeBuilder {
366 size,
367 fields: Vec::new(),
368 }
369 }
370}
371
372pub struct EnumTypeBuilder {
374 base_type: Datatype,
375 members: Vec<EnumMember>,
376}
377
378impl EnumTypeBuilder {
379 pub fn i32_based() -> Self {
381 Self {
382 base_type: make_i32_type(),
383 members: Vec::new(),
384 }
385 }
386
387 pub fn u8_based() -> Self {
389 Self {
390 base_type: make_u8_type(),
391 members: Vec::new(),
392 }
393 }
394
395 pub fn value(mut self, name: &str, val: i32) -> Self {
397 self.members.push(EnumMember {
398 name: name.to_string(),
399 value: val.to_le_bytes().to_vec(),
400 });
401 self
402 }
403
404 pub fn u8_value(mut self, name: &str, val: u8) -> Self {
406 self.members.push(EnumMember {
407 name: name.to_string(),
408 value: vec![val],
409 });
410 self
411 }
412
413 pub fn build(self) -> Datatype {
415 let size = self.base_type.type_size();
416 Datatype::Enumeration {
417 size,
418 base_type: Box::new(self.base_type),
419 members: self.members,
420 }
421 }
422}
423
424pub(crate) fn build_attr_message(name: &str, value: &AttrValue) -> AttributeMessage {
427 match value {
428 AttrValue::F64(v) => AttributeMessage {
429 name: name.to_string(),
430 datatype: make_f64_type(),
431 dataspace: scalar_ds(),
432 raw_data: v.to_le_bytes().to_vec(),
433 },
434 AttrValue::F64Array(arr) => {
435 let mut raw = Vec::with_capacity(arr.len() * 8);
436 for v in arr {
437 raw.extend_from_slice(&v.to_le_bytes());
438 }
439 AttributeMessage {
440 name: name.to_string(),
441 datatype: make_f64_type(),
442 dataspace: simple_1d(arr.len() as u64),
443 raw_data: raw,
444 }
445 }
446 AttrValue::I64(v) => AttributeMessage {
447 name: name.to_string(),
448 datatype: make_i64_type(),
449 dataspace: scalar_ds(),
450 raw_data: v.to_le_bytes().to_vec(),
451 },
452 AttrValue::I64Array(arr) => {
453 let mut raw = Vec::with_capacity(arr.len() * 8);
454 for v in arr {
455 raw.extend_from_slice(&v.to_le_bytes());
456 }
457 AttributeMessage {
458 name: name.to_string(),
459 datatype: make_i64_type(),
460 dataspace: simple_1d(arr.len() as u64),
461 raw_data: raw,
462 }
463 }
464 AttrValue::I32(v) => AttributeMessage {
465 name: name.to_string(),
466 datatype: make_i32_type(),
467 dataspace: scalar_ds(),
468 raw_data: v.to_le_bytes().to_vec(),
469 },
470 AttrValue::U32(v) => AttributeMessage {
471 name: name.to_string(),
472 datatype: make_u32_type(),
473 dataspace: scalar_ds(),
474 raw_data: v.to_le_bytes().to_vec(),
475 },
476 AttrValue::U64(v) => AttributeMessage {
477 name: name.to_string(),
478 datatype: make_u64_type(),
479 dataspace: scalar_ds(),
480 raw_data: v.to_le_bytes().to_vec(),
481 },
482 AttrValue::String(s) => {
483 let bytes = s.as_bytes();
484 AttributeMessage {
485 name: name.to_string(),
486 datatype: Datatype::String {
487 #[expect(
488 clippy::cast_possible_truncation,
489 reason = "string byte length is stored in the 4-byte fixed-string datatype size field"
490 )]
491 size: bytes.len() as u32,
492 padding: StringPadding::NullPad,
493 charset: CharacterSet::Utf8,
494 },
495 dataspace: scalar_ds(),
496 raw_data: bytes.to_vec(),
497 }
498 }
499 AttrValue::StringArray(arr) => {
500 let max_len = arr.iter().map(|s| s.len()).max().unwrap_or(0);
501 let mut raw = Vec::new();
502 for s in arr {
503 let mut b = s.as_bytes().to_vec();
504 b.resize(max_len, 0);
505 raw.extend_from_slice(&b);
506 }
507 AttributeMessage {
508 name: name.to_string(),
509 datatype: Datatype::String {
510 #[expect(
511 clippy::cast_possible_truncation,
512 reason = "max string byte length is stored in the 4-byte fixed-string datatype size field"
513 )]
514 size: max_len as u32,
515 padding: StringPadding::NullPad,
516 charset: CharacterSet::Utf8,
517 },
518 dataspace: simple_1d(arr.len() as u64),
519 raw_data: raw,
520 }
521 }
522 AttrValue::AsciiString(s) => {
523 let bytes = s.as_bytes();
524 AttributeMessage {
525 name: name.to_string(),
526 datatype: Datatype::String {
527 #[expect(
528 clippy::cast_possible_truncation,
529 reason = "string byte length is stored in the 4-byte fixed-string datatype size field"
530 )]
531 size: bytes.len() as u32,
532 padding: StringPadding::NullPad,
533 charset: CharacterSet::Ascii,
534 },
535 dataspace: scalar_ds(),
536 raw_data: bytes.to_vec(),
537 }
538 }
539 AttrValue::AsciiStringArray(arr) => {
540 let max_len = arr.iter().map(|s| s.len()).max().unwrap_or(0);
541 let mut raw = Vec::new();
542 for s in arr {
543 let mut b = s.as_bytes().to_vec();
544 b.resize(max_len, 0);
545 raw.extend_from_slice(&b);
546 }
547 AttributeMessage {
548 name: name.to_string(),
549 datatype: Datatype::String {
550 #[expect(
551 clippy::cast_possible_truncation,
552 reason = "max string byte length is stored in the 4-byte fixed-string datatype size field"
553 )]
554 size: max_len as u32,
555 padding: StringPadding::NullPad,
556 charset: CharacterSet::Ascii,
557 },
558 dataspace: simple_1d(arr.len() as u64),
559 raw_data: raw,
560 }
561 }
562 AttrValue::VarLenAsciiArray(strings) => {
563 let vl_ref_size = 16usize; let mut raw = Vec::with_capacity(strings.len() * vl_ref_size);
573 for (i, s) in strings.iter().enumerate() {
574 #[expect(
575 clippy::cast_possible_truncation,
576 reason = "VLEN string length is written into the 4-byte length prefix of the variable-length reference"
577 )]
578 raw.extend_from_slice(&(s.len() as u32).to_le_bytes());
579 raw.extend_from_slice(&0u64.to_le_bytes()); #[expect(
581 clippy::cast_possible_truncation,
582 reason = "1-based heap object index is written into the 4-byte object-index field of the variable-length reference"
583 )]
584 raw.extend_from_slice(&((i + 1) as u32).to_le_bytes());
585 }
586 AttributeMessage {
587 name: name.to_string(),
588 datatype: Datatype::VariableLength {
589 is_string: false,
590 padding: None,
591 charset: None,
592 base_type: Box::new(Datatype::String {
593 size: 1,
594 padding: StringPadding::NullTerminate,
595 charset: CharacterSet::Ascii,
596 }),
597 },
598 dataspace: simple_1d(strings.len() as u64),
599 raw_data: raw,
600 }
601 }
602 }
603}
604
605pub(crate) fn build_global_heap_collection(strings: &[&str]) -> Vec<u8> {
608 let objects: Vec<&[u8]> = strings.iter().map(|s| s.as_bytes()).collect();
609 build_global_heap_collection_bytes(&objects)
610}
611
612pub(crate) fn build_global_heap_collection_bytes(objects: &[&[u8]]) -> Vec<u8> {
617 let length_size = 8usize;
618 let header_size = 8 + length_size; let mut obj_size_total = 0usize;
622 for obj in objects {
623 let obj_header = 8 + length_size; let padded_data_len = (obj.len() + 7) & !7; obj_size_total += obj_header + padded_data_len;
626 }
627 obj_size_total += 8 + length_size; let collection_size = header_size + obj_size_total;
629 let min_collection_size = 4096;
631 let padded_collection = ((collection_size.max(min_collection_size)) + 7) & !7;
632
633 let mut buf = Vec::with_capacity(padded_collection);
634 buf.extend_from_slice(b"GCOL");
636 buf.push(1); buf.extend_from_slice(&[0u8; 3]); buf.extend_from_slice(&(padded_collection as u64).to_le_bytes());
639
640 for (i, obj) in objects.iter().enumerate() {
642 #[expect(
643 clippy::cast_possible_truncation,
644 reason = "1-based heap object index is written into the 2-byte heap-object index field"
645 )]
646 let index = (i + 1) as u16;
647 buf.extend_from_slice(&index.to_le_bytes());
648 buf.extend_from_slice(&1u16.to_le_bytes()); buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&(obj.len() as u64).to_le_bytes());
651 buf.extend_from_slice(obj);
652 let padded = (obj.len() + 7) & !7;
654 for _ in obj.len()..padded {
655 buf.push(0);
656 }
657 }
658
659 let free_total_size = padded_collection - buf.len();
663 buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&0u16.to_le_bytes()); buf.extend_from_slice(&[0u8; 4]); buf.extend_from_slice(&(free_total_size as u64).to_le_bytes()); buf.resize(padded_collection, 0);
670
671 buf
672}
673
674pub(crate) fn patch_vl_refs(raw_data: &mut [u8], collection_address: u64) {
677 let vl_ref_size = 16; let count = raw_data.len() / vl_ref_size;
679 for i in 0..count {
680 let addr_offset = i * vl_ref_size + 4; raw_data[addr_offset..addr_offset + 8].copy_from_slice(&collection_address.to_le_bytes());
682 }
683}
684
685#[derive(Debug, Clone, PartialEq, Eq)]
693pub(crate) enum VlStringElement {
694 Null,
696 Bytes(Vec<u8>),
698}
699
700pub(crate) const VL_REF_SIZE: usize = 16;
703
704pub(crate) struct VlStringStaging {
713 pub refs: Vec<u8>,
715 pub collection_bytes: Vec<u8>,
717 pub patch_mask: Vec<bool>,
720}
721
722pub(crate) fn stage_vl_elements(
731 elements: &[VlStringElement],
732 element_size: usize,
733) -> VlStringStaging {
734 let element_size = element_size.max(1);
735 let mut objects: Vec<&[u8]> = Vec::new();
738 let mut refs = Vec::with_capacity(elements.len() * VL_REF_SIZE);
739 let mut patch_mask = Vec::with_capacity(elements.len());
740 for element in elements {
741 match element {
742 VlStringElement::Null => {
743 refs.extend_from_slice(&0u32.to_le_bytes()); refs.extend_from_slice(&0u64.to_le_bytes()); refs.extend_from_slice(&0u32.to_le_bytes()); patch_mask.push(false);
751 }
752 VlStringElement::Bytes(bytes) => {
753 #[expect(
754 clippy::cast_possible_truncation,
755 reason = "VL element length (element count) is written into the 4-byte \
756 length prefix of the variable-length reference"
757 )]
758 refs.extend_from_slice(&((bytes.len() / element_size) as u32).to_le_bytes());
759 refs.extend_from_slice(&0u64.to_le_bytes()); let index = objects.len() + 1; #[expect(
762 clippy::cast_possible_truncation,
763 reason = "1-based heap object index is written into the 4-byte object-index \
764 field of the variable-length reference"
765 )]
766 refs.extend_from_slice(&(index as u32).to_le_bytes());
767 objects.push(bytes);
768 patch_mask.push(true);
769 }
770 }
771 }
772 let collection_bytes = if objects.is_empty() {
776 Vec::new()
777 } else {
778 build_global_heap_collection_bytes(&objects)
779 };
780 VlStringStaging {
781 refs,
782 collection_bytes,
783 patch_mask,
784 }
785}
786
787pub(crate) fn patch_vl_refs_masked(
791 raw_data: &mut [u8],
792 patch_mask: &[bool],
793 collection_address: u64,
794) {
795 for (i, &patch) in patch_mask.iter().enumerate() {
796 if !patch {
797 continue;
798 }
799 let addr_offset = i * VL_REF_SIZE + 4; raw_data[addr_offset..addr_offset + 8].copy_from_slice(&collection_address.to_le_bytes());
801 }
802}
803
804pub(crate) fn scalar_ds() -> Dataspace {
805 Dataspace {
806 space_type: DataspaceType::Scalar,
807 rank: 0,
808 dimensions: vec![],
809 max_dimensions: None,
810 }
811}
812
813pub(crate) fn simple_1d(n: u64) -> Dataspace {
814 Dataspace {
815 space_type: DataspaceType::Simple,
816 rank: 1,
817 dimensions: vec![n],
818 max_dimensions: None,
819 }
820}
821
822#[derive(Debug, Clone, PartialEq)]
826pub enum AttrValue {
827 F64(f64),
828 F64Array(Vec<f64>),
829 I32(i32),
830 I64(i64),
831 I64Array(Vec<i64>),
832 U32(u32),
833 U64(u64),
834 String(String),
836 StringArray(Vec<String>),
837 AsciiString(String),
839 AsciiStringArray(Vec<String>),
842 VarLenAsciiArray(Vec<String>),
846}
847
848#[cfg(feature = "provenance")]
852#[derive(Debug, Clone)]
853pub struct ProvenanceConfig {
854 pub creator: String,
855 pub timestamp: String,
856 pub source: Option<String>,
857}
858
859pub(crate) struct RawChunkPayload {
866 pub(crate) chunk_dims: Vec<u64>,
868 pub(crate) element_size: usize,
870 pub(crate) raw_size: u64,
873 pub(crate) pipeline_message: Option<Vec<u8>>,
875 pub(crate) meta: Vec<ChunkMeta>,
877 pub(crate) provider: core::panic::AssertUnwindSafe<Box<dyn ChunkProvider>>,
888}
889
890#[derive(Debug, Clone, PartialEq, Eq)]
897pub(crate) enum ObjectRefTarget {
898 Path(String),
900 Raw(u64),
903}
904
905pub struct DatasetBuilder {
907 pub(crate) name: String,
908 pub(crate) datatype: Option<Datatype>,
909 pub(crate) shape: Option<Vec<u64>>,
910 pub(crate) maxshape: Option<Vec<u64>>,
911 pub(crate) data: Option<Vec<u8>>,
912 pub(crate) attrs: Vec<(String, AttrValue)>,
913 pub(crate) chunk_options: ChunkOptions,
914 pub(crate) raw_chunks: Option<RawChunkPayload>,
919 pub(crate) reference_targets: Option<Vec<ObjectRefTarget>>,
923 pub(crate) vl_string_staging: Option<VlStringStaging>,
928 #[cfg(feature = "provenance")]
929 pub(crate) provenance: Option<ProvenanceConfig>,
930}
931
932impl DatasetBuilder {
933 pub(crate) fn new(name: &str) -> Self {
934 Self {
935 name: name.to_string(),
936 datatype: None,
937 shape: None,
938 maxshape: None,
939 data: None,
940 attrs: Vec::new(),
941 chunk_options: ChunkOptions::default(),
942 raw_chunks: None,
943 reference_targets: None,
944 vl_string_staging: None,
945 #[cfg(feature = "provenance")]
946 provenance: None,
947 }
948 }
949
950 pub fn with_f64_data(&mut self, data: &[f64]) -> &mut Self {
951 self.datatype = Some(make_f64_type());
952 let mut b = Vec::with_capacity(data.len() * 8);
953 for &v in data {
954 b.extend_from_slice(&v.to_le_bytes());
955 }
956 self.data = Some(b);
957 if self.shape.is_none() {
958 self.shape = Some(vec![data.len() as u64]);
959 }
960 self
961 }
962
963 pub fn with_f32_data(&mut self, data: &[f32]) -> &mut Self {
964 self.datatype = Some(make_f32_type());
965 let mut b = Vec::with_capacity(data.len() * 4);
966 for &v in data {
967 b.extend_from_slice(&v.to_le_bytes());
968 }
969 self.data = Some(b);
970 if self.shape.is_none() {
971 self.shape = Some(vec![data.len() as u64]);
972 }
973 self
974 }
975
976 pub fn with_i32_data(&mut self, data: &[i32]) -> &mut Self {
977 self.datatype = Some(make_i32_type());
978 let mut b = Vec::with_capacity(data.len() * 4);
979 for &v in data {
980 b.extend_from_slice(&v.to_le_bytes());
981 }
982 self.data = Some(b);
983 if self.shape.is_none() {
984 self.shape = Some(vec![data.len() as u64]);
985 }
986 self
987 }
988
989 pub fn with_i64_data(&mut self, data: &[i64]) -> &mut Self {
990 self.datatype = Some(make_i64_type());
991 let mut b = Vec::with_capacity(data.len() * 8);
992 for &v in data {
993 b.extend_from_slice(&v.to_le_bytes());
994 }
995 self.data = Some(b);
996 if self.shape.is_none() {
997 self.shape = Some(vec![data.len() as u64]);
998 }
999 self
1000 }
1001
1002 pub fn with_u8_data(&mut self, data: &[u8]) -> &mut Self {
1003 self.datatype = Some(make_u8_type());
1004 self.data = Some(data.to_vec());
1005 if self.shape.is_none() {
1006 self.shape = Some(vec![data.len() as u64]);
1007 }
1008 self
1009 }
1010
1011 pub fn with_i8_data(&mut self, data: &[i8]) -> &mut Self {
1012 self.datatype = Some(make_i8_type());
1013 let mut b = Vec::with_capacity(data.len());
1014 for &v in data {
1015 b.push(v as u8);
1016 }
1017 self.data = Some(b);
1018 if self.shape.is_none() {
1019 self.shape = Some(vec![data.len() as u64]);
1020 }
1021 self
1022 }
1023
1024 pub fn with_i16_data(&mut self, data: &[i16]) -> &mut Self {
1025 self.datatype = Some(make_i16_type());
1026 let mut b = Vec::with_capacity(data.len() * 2);
1027 for &v in data {
1028 b.extend_from_slice(&v.to_le_bytes());
1029 }
1030 self.data = Some(b);
1031 if self.shape.is_none() {
1032 self.shape = Some(vec![data.len() as u64]);
1033 }
1034 self
1035 }
1036
1037 pub fn with_u16_data(&mut self, data: &[u16]) -> &mut Self {
1038 self.datatype = Some(make_u16_type());
1039 let mut b = Vec::with_capacity(data.len() * 2);
1040 for &v in data {
1041 b.extend_from_slice(&v.to_le_bytes());
1042 }
1043 self.data = Some(b);
1044 if self.shape.is_none() {
1045 self.shape = Some(vec![data.len() as u64]);
1046 }
1047 self
1048 }
1049
1050 pub fn with_u32_data(&mut self, data: &[u32]) -> &mut Self {
1051 self.datatype = Some(make_u32_type());
1052 let mut b = Vec::with_capacity(data.len() * 4);
1053 for &v in data {
1054 b.extend_from_slice(&v.to_le_bytes());
1055 }
1056 self.data = Some(b);
1057 if self.shape.is_none() {
1058 self.shape = Some(vec![data.len() as u64]);
1059 }
1060 self
1061 }
1062
1063 pub fn with_u64_data(&mut self, data: &[u64]) -> &mut Self {
1064 self.datatype = Some(make_u64_type());
1065 let mut b = Vec::with_capacity(data.len() * 8);
1066 for &v in data {
1067 b.extend_from_slice(&v.to_le_bytes());
1068 }
1069 self.data = Some(b);
1070 if self.shape.is_none() {
1071 self.shape = Some(vec![data.len() as u64]);
1072 }
1073 self
1074 }
1075
1076 pub fn with_reference_data(&mut self, addresses: &[u64]) -> &mut Self {
1079 self.datatype = Some(make_object_reference_type());
1080 let mut b = Vec::with_capacity(addresses.len() * 8);
1081 for &addr in addresses {
1082 b.extend_from_slice(&addr.to_le_bytes());
1083 }
1084 self.data = Some(b);
1085 if self.shape.is_none() {
1086 self.shape = Some(vec![addresses.len() as u64]);
1087 }
1088 self
1089 }
1090
1091 pub fn with_path_references(&mut self, paths: &[&str]) -> &mut Self {
1095 let targets = paths
1096 .iter()
1097 .map(|s| ObjectRefTarget::Path(s.to_string()))
1098 .collect();
1099 self.with_object_references(targets)
1100 }
1101
1102 pub(crate) fn with_object_references(&mut self, targets: Vec<ObjectRefTarget>) -> &mut Self {
1110 self.datatype = Some(make_object_reference_type());
1111 self.data = Some(vec![0u8; targets.len() * 8]);
1113 if self.shape.is_none() {
1114 self.shape = Some(vec![targets.len() as u64]);
1115 }
1116 self.reference_targets = Some(targets);
1117 self
1118 }
1119
1120 pub fn with_complex32_data(&mut self, data: &[(f32, f32)]) -> &mut Self {
1122 let ct = CompoundTypeBuilder::new()
1123 .f32_field("real")
1124 .f32_field("imag")
1125 .build();
1126 let mut raw = Vec::with_capacity(data.len() * 8);
1127 for &(r, i) in data {
1128 raw.extend_from_slice(&r.to_le_bytes());
1129 raw.extend_from_slice(&i.to_le_bytes());
1130 }
1131 self.with_compound_data(ct, raw, data.len() as u64)
1132 }
1133
1134 pub fn with_complex64_data(&mut self, data: &[(f64, f64)]) -> &mut Self {
1136 let ct = CompoundTypeBuilder::new()
1137 .f64_field("real")
1138 .f64_field("imag")
1139 .build();
1140 let mut raw = Vec::with_capacity(data.len() * 16);
1141 for &(r, i) in data {
1142 raw.extend_from_slice(&r.to_le_bytes());
1143 raw.extend_from_slice(&i.to_le_bytes());
1144 }
1145 self.with_compound_data(ct, raw, data.len() as u64)
1146 }
1147
1148 pub fn with_compound_data(
1150 &mut self,
1151 datatype: Datatype,
1152 raw_data: Vec<u8>,
1153 num_elements: u64,
1154 ) -> &mut Self {
1155 self.with_raw_data(datatype, raw_data, num_elements)
1156 }
1157
1158 pub fn with_raw_data(
1169 &mut self,
1170 datatype: Datatype,
1171 raw_data: Vec<u8>,
1172 num_elements: u64,
1173 ) -> &mut Self {
1174 self.datatype = Some(datatype);
1175 self.data = Some(raw_data);
1176 if self.shape.is_none() {
1177 self.shape = Some(vec![num_elements]);
1178 }
1179 self
1180 }
1181
1182 #[allow(clippy::too_many_arguments)]
1198 pub(crate) fn with_raw_chunks_lazy(
1199 &mut self,
1200 datatype: Datatype,
1201 dims: &[u64],
1202 maxshape: Option<&[u64]>,
1203 chunk_dims: &[u64],
1204 element_size: usize,
1205 pipeline_message: Option<Vec<u8>>,
1206 meta: Vec<ChunkMeta>,
1207 provider: Box<dyn ChunkProvider>,
1208 ) -> &mut Self {
1209 let raw_size: u64 = chunk_dims
1214 .iter()
1215 .copied()
1216 .product::<u64>()
1217 .saturating_mul(element_size as u64);
1218 self.datatype = Some(datatype);
1219 if self.shape.is_none() {
1220 self.shape = Some(dims.to_vec());
1221 }
1222 if let Some(ms) = maxshape {
1223 self.maxshape = Some(ms.to_vec());
1224 }
1225 self.chunk_options.chunk_dims = Some(chunk_dims.to_vec());
1226 self.raw_chunks = Some(RawChunkPayload {
1227 chunk_dims: chunk_dims.to_vec(),
1228 element_size,
1229 raw_size,
1230 pipeline_message,
1231 meta,
1232 provider: core::panic::AssertUnwindSafe(provider),
1233 });
1234 self
1235 }
1236
1237 pub fn with_compound_values<T: CompoundType>(
1242 &mut self,
1243 values: &[T],
1244 ) -> Result<&mut Self, crate::error::FormatError> {
1245 let datatype = T::datatype()?;
1246 if !matches!(datatype, Datatype::Compound { .. }) {
1247 return Err(crate::error::FormatError::TypeMismatch {
1248 expected: "Compound",
1249 actual: "non-Compound",
1250 });
1251 }
1252 let element_size = datatype.type_size().to_usize()?;
1253 if element_size == 0 {
1254 return Err(crate::error::FormatError::InvalidCompoundSize);
1255 }
1256 let mut raw = Vec::with_capacity(values.len().saturating_mul(element_size));
1257 for value in values {
1258 let start = raw.len();
1259 value.encode(&mut raw);
1260 let actual = raw.len() - start;
1261 if actual != element_size {
1262 return Err(crate::error::FormatError::DataSizeMismatch {
1263 expected: element_size,
1264 actual,
1265 });
1266 }
1267 }
1268 Ok(self.with_compound_data(datatype, raw, values.len() as u64))
1269 }
1270
1271 pub fn with_enum_i32_data(&mut self, datatype: Datatype, values: &[i32]) -> &mut Self {
1273 self.datatype = Some(datatype);
1274 let mut raw = Vec::with_capacity(values.len() * 4);
1275 for &v in values {
1276 raw.extend_from_slice(&v.to_le_bytes());
1277 }
1278 self.data = Some(raw);
1279 if self.shape.is_none() {
1280 self.shape = Some(vec![values.len() as u64]);
1281 }
1282 self
1283 }
1284
1285 pub fn with_enum_u8_data(&mut self, datatype: Datatype, values: &[u8]) -> &mut Self {
1287 self.datatype = Some(datatype);
1288 self.data = Some(values.to_vec());
1289 if self.shape.is_none() {
1290 self.shape = Some(vec![values.len() as u64]);
1291 }
1292 self
1293 }
1294
1295 pub fn with_vlen_strings(&mut self, values: &[&str]) -> &mut Self {
1308 let datatype = make_vlen_string_type(CharacterSet::Utf8);
1309 let elements: Vec<VlStringElement> = values
1310 .iter()
1311 .map(|s| VlStringElement::Bytes(s.as_bytes().to_vec()))
1312 .collect();
1313 self.stage_vlen_strings(datatype, &elements);
1314 self
1315 }
1316
1317 pub(crate) fn with_vlen_string_elements(
1329 &mut self,
1330 datatype: Datatype,
1331 elements: &[VlStringElement],
1332 ) -> Result<&mut Self, crate::error::FormatError> {
1333 if !crate::vl_data::is_vlen_string_datatype(&datatype) {
1334 return Err(crate::error::FormatError::TypeMismatch {
1335 expected: "VariableLength string",
1336 actual: "non-VariableLength string",
1337 });
1338 }
1339 self.stage_vlen_strings(datatype, elements);
1340 Ok(self)
1341 }
1342
1343 fn stage_vlen_strings(&mut self, datatype: Datatype, elements: &[VlStringElement]) {
1346 self.stage_vlen_elements(datatype, elements, 1);
1349 }
1350
1351 pub(crate) fn with_vlen_sequence_elements(
1364 &mut self,
1365 datatype: Datatype,
1366 elements: &[VlStringElement],
1367 ) -> Result<&mut Self, crate::error::FormatError> {
1368 let Datatype::VariableLength { base_type, .. } = &datatype else {
1369 return Err(crate::error::FormatError::TypeMismatch {
1370 expected: "non-string VariableLength",
1371 actual: "non-VariableLength",
1372 });
1373 };
1374 if crate::vl_data::is_vlen_string_datatype(&datatype) {
1375 return Err(crate::error::FormatError::TypeMismatch {
1376 expected: "non-string VariableLength",
1377 actual: "VariableLength string",
1378 });
1379 }
1380 let element_size = base_type.type_size() as usize;
1381 if element_size == 0 {
1382 return Err(crate::error::FormatError::VlDataError(
1383 "non-string VL base type has zero size".into(),
1384 ));
1385 }
1386 self.stage_vlen_elements(datatype, elements, element_size);
1387 Ok(self)
1388 }
1389
1390 fn stage_vlen_elements(
1393 &mut self,
1394 datatype: Datatype,
1395 elements: &[VlStringElement],
1396 element_size: usize,
1397 ) {
1398 let n = elements.len() as u64;
1399 let staging = stage_vl_elements(elements, element_size);
1400 self.datatype = Some(datatype);
1401 self.data = Some(staging.refs.clone());
1402 self.vl_string_staging = Some(staging);
1403 if self.shape.is_none() {
1404 self.shape = Some(vec![n]);
1405 }
1406 }
1407
1408 pub fn with_array_data(
1410 &mut self,
1411 base_type: Datatype,
1412 array_dims: &[u32],
1413 raw_data: Vec<u8>,
1414 num_elements: u64,
1415 ) -> &mut Self {
1416 self.datatype = Some(Datatype::Array {
1417 base_type: Box::new(base_type),
1418 dimensions: array_dims.to_vec(),
1419 });
1420 self.data = Some(raw_data);
1421 if self.shape.is_none() {
1422 self.shape = Some(vec![num_elements]);
1423 }
1424 self
1425 }
1426
1427 pub fn with_shape(&mut self, shape: &[u64]) -> &mut Self {
1428 self.shape = Some(shape.to_vec());
1429 self
1430 }
1431
1432 pub fn with_dtype(&mut self, dt: Datatype) -> &mut Self {
1435 self.datatype = Some(dt);
1436 self
1437 }
1438
1439 pub fn with_maxshape(&mut self, maxshape: &[u64]) -> &mut Self {
1442 self.maxshape = Some(maxshape.to_vec());
1443 self
1444 }
1445
1446 pub fn set_attr(&mut self, name: &str, value: AttrValue) -> &mut Self {
1447 self.attrs.push((name.to_string(), value));
1448 self
1449 }
1450
1451 pub fn with_chunks(&mut self, chunk_dims: &[u64]) -> &mut Self {
1453 self.chunk_options.chunk_dims = Some(chunk_dims.to_vec());
1454 self
1455 }
1456
1457 pub fn with_deflate(&mut self, level: u32) -> &mut Self {
1459 self.chunk_options.deflate_level = Some(level);
1460 self
1461 }
1462
1463 pub fn with_shuffle(&mut self) -> &mut Self {
1465 self.chunk_options.shuffle = true;
1466 self
1467 }
1468
1469 pub fn with_fletcher32(&mut self) -> &mut Self {
1471 self.chunk_options.fletcher32 = true;
1472 self
1473 }
1474
1475 pub fn with_scale_offset(&mut self, mode: ScaleOffset) -> &mut Self {
1495 self.chunk_options.scale_offset = Some(mode);
1496 self
1497 }
1498
1499 #[cfg(feature = "zfp")]
1516 pub fn with_zfp(&mut self, rate: f64) -> &mut Self {
1517 self.chunk_options.zfp_rate = Some(rate);
1518 self
1519 }
1520
1521 #[cfg(feature = "provenance")]
1526 pub fn with_provenance(
1527 &mut self,
1528 creator: &str,
1529 timestamp: &str,
1530 source: Option<&str>,
1531 ) -> &mut Self {
1532 self.provenance = Some(ProvenanceConfig {
1533 creator: creator.to_string(),
1534 timestamp: timestamp.to_string(),
1535 source: source.map(|s| s.to_string()),
1536 });
1537 self
1538 }
1539}
1540
1541pub struct GroupBuilder {
1566 pub(crate) name: String,
1567 pub(crate) datasets: Vec<DatasetBuilder>,
1568 pub(crate) sub_groups: Vec<FinishedGroup>,
1569 pub(crate) attrs: Vec<(String, AttrValue)>,
1570}
1571
1572impl GroupBuilder {
1573 pub(crate) fn new(name: &str) -> Self {
1574 Self {
1575 name: name.to_string(),
1576 datasets: Vec::new(),
1577 sub_groups: Vec::new(),
1578 attrs: Vec::new(),
1579 }
1580 }
1581
1582 pub fn create_dataset(&mut self, name: &str) -> &mut DatasetBuilder {
1583 self.datasets.push(DatasetBuilder::new(name));
1584 self.datasets.last_mut().unwrap()
1585 }
1586
1587 pub fn create_group(&mut self, name: &str) -> GroupBuilder {
1590 GroupBuilder::new(name)
1591 }
1592
1593 pub fn add_group(&mut self, group: FinishedGroup) {
1595 self.sub_groups.push(group);
1596 }
1597
1598 pub fn set_attr(&mut self, name: &str, value: AttrValue) {
1599 self.attrs.push((name.to_string(), value));
1600 }
1601
1602 pub fn finish(self) -> FinishedGroup {
1604 FinishedGroup {
1605 name: self.name,
1606 datasets: self.datasets,
1607 sub_groups: self.sub_groups,
1608 attrs: self.attrs,
1609 }
1610 }
1611}
1612
1613pub struct FinishedGroup {
1615 pub(crate) name: String,
1616 pub(crate) datasets: Vec<DatasetBuilder>,
1617 pub(crate) sub_groups: Vec<FinishedGroup>,
1618 pub(crate) attrs: Vec<(String, AttrValue)>,
1619}