use std::borrow::Cow;
use std::num::NonZeroU64;
use std::sync::Arc;
use derive_more::From;
use zarrs_plugin::ExtensionName;
use super::chunk_key_encoding::DefaultChunkKeyEncoding;
use super::{
Array, ArrayCreateError, ArrayMetadata, ArrayMetadataV3, ArrayShape, ChunkShape, CodecChain,
DimensionName, StorageTransformerChain,
};
use crate::array::{ArrayMetadataOptions, ChunkGrid};
use crate::config::global_config;
use crate::node::NodePath;
use zarrs_chunk_key_encoding::ChunkKeyEncoding;
use zarrs_codec::{
ArrayToArrayCodecTraits, ArrayToBytesCodecTraits, BytesToBytesCodecTraits, CodecOptions,
};
use zarrs_metadata::v3::{AdditionalFieldsV3, MetadataV3};
use zarrs_metadata::{ChunkKeySeparator, IntoDimensionName};
mod array_builder_chunk_grid_metadata;
pub use array_builder_chunk_grid_metadata::ArrayBuilderChunkGridMetadata;
mod array_builder_data_type;
pub use array_builder_data_type::ArrayBuilderDataType;
mod array_builder_fill_value;
pub use array_builder_fill_value::ArrayBuilderFillValue;
use array_builder_fill_value::ArrayBuilderFillValueImpl;
#[derive(Debug)]
pub struct ArrayBuilder {
data_type: ArrayBuilderDataType,
chunk_grid: ArrayBuilderChunkGridMaybe,
chunk_key_encoding: ChunkKeyEncoding,
fill_value: ArrayBuilderFillValue,
array_to_array_codecs: Vec<Arc<dyn ArrayToArrayCodecTraits>>,
array_to_bytes_codec: Option<Arc<dyn ArrayToBytesCodecTraits>>,
bytes_to_bytes_codecs: Vec<Arc<dyn BytesToBytesCodecTraits>>,
storage_transformers: StorageTransformerChain,
attributes: serde_json::Map<String, serde_json::Value>,
dimension_names: Option<Vec<DimensionName>>,
additional_fields: AdditionalFieldsV3,
#[cfg(feature = "sharding")]
subchunk_shape: Option<ArrayShape>,
codec_options: CodecOptions,
metadata_options: ArrayMetadataOptions,
}
#[derive(Debug, From)]
enum ArrayBuilderChunkGridMaybe {
ChunkGrid(ChunkGrid),
Metadata(ArrayShape, ArrayBuilderChunkGridMetadata),
}
impl ArrayBuilder {
#[must_use]
pub fn new(
shape: impl Into<ArrayShape>,
chunk_grid_metadata: impl Into<ArrayBuilderChunkGridMetadata>,
data_type: impl Into<ArrayBuilderDataType>,
fill_value: impl Into<ArrayBuilderFillValue>,
) -> Self {
let shape = shape.into();
let data_type = data_type.into();
let chunk_grid_metadata: ArrayBuilderChunkGridMetadata = chunk_grid_metadata.into();
let chunk_grid: ArrayBuilderChunkGridMaybe = (shape, chunk_grid_metadata).into();
let fill_value = fill_value.into();
let (codec_options, metadata_options) = {
let config = global_config();
(config.codec_options(), config.array_metadata_options())
};
Self {
data_type,
chunk_grid,
chunk_key_encoding: DefaultChunkKeyEncoding::default().into(),
fill_value,
array_to_array_codecs: Vec::default(),
array_to_bytes_codec: None,
bytes_to_bytes_codecs: Vec::default(),
attributes: serde_json::Map::default(),
storage_transformers: StorageTransformerChain::default(),
dimension_names: None,
additional_fields: AdditionalFieldsV3::default(),
#[cfg(feature = "sharding")]
subchunk_shape: None,
codec_options,
metadata_options,
}
}
pub fn new_with_chunk_grid(
chunk_grid: impl Into<ChunkGrid>,
data_type: impl Into<ArrayBuilderDataType>,
fill_value: impl Into<ArrayBuilderFillValue>,
) -> Self {
let data_type = data_type.into();
let chunk_grid: ChunkGrid = chunk_grid.into();
let chunk_grid: ArrayBuilderChunkGridMaybe = chunk_grid.into();
let fill_value = fill_value.into();
let (codec_options, metadata_options) = {
let config = global_config();
(config.codec_options(), config.array_metadata_options())
};
Self {
data_type,
chunk_grid,
chunk_key_encoding: DefaultChunkKeyEncoding::default().into(),
fill_value,
array_to_array_codecs: Vec::default(),
array_to_bytes_codec: None,
bytes_to_bytes_codecs: Vec::default(),
attributes: serde_json::Map::default(),
storage_transformers: StorageTransformerChain::default(),
dimension_names: None,
additional_fields: AdditionalFieldsV3::default(),
#[cfg(feature = "sharding")]
subchunk_shape: None,
codec_options,
metadata_options,
}
}
#[must_use]
pub fn from_array<T: ?Sized>(array: &Array<T>) -> Self {
let mut builder = Self::new(
array.shape().to_vec(),
array.chunk_grid().metadata(),
array.data_type().clone(),
array.fill_value_metadata(),
);
let additional_fields = match array.metadata() {
ArrayMetadata::V2(_metadata) => AdditionalFieldsV3::default(),
ArrayMetadata::V3(metadata) => metadata.additional_fields.clone(),
};
builder.array_to_array_codecs = array.codecs().array_to_array_codecs().to_vec();
builder.array_to_bytes_codec = Some(array.codecs().array_to_bytes_codec().clone());
builder.bytes_to_bytes_codecs = array.codecs().bytes_to_bytes_codecs().to_vec();
builder
.additional_fields(additional_fields)
.attributes(array.attributes().clone())
.chunk_key_encoding(array.chunk_key_encoding().clone())
.dimension_names(array.dimension_names().clone())
.storage_transformers(array.storage_transformers().clone());
builder
}
pub fn shape(&mut self, shape: impl Into<ArrayShape>) -> &mut Self {
let shape = shape.into();
let chunk_grid_metadata = match &self.chunk_grid {
ArrayBuilderChunkGridMaybe::ChunkGrid(chunk_grid) => {
ArrayBuilderChunkGridMetadata::from(chunk_grid.metadata())
}
ArrayBuilderChunkGridMaybe::Metadata(_array_shape, chunk_grid_metadata) => {
chunk_grid_metadata.clone()
}
};
self.chunk_grid = (shape, chunk_grid_metadata).into();
self
}
pub fn data_type(&mut self, data_type: impl Into<ArrayBuilderDataType>) -> &mut Self {
self.data_type = data_type.into();
self
}
pub fn chunk_grid_metadata(
&mut self,
chunk_grid_metadata: impl Into<ArrayBuilderChunkGridMetadata>,
) -> &mut Self {
let array_shape = match &self.chunk_grid {
ArrayBuilderChunkGridMaybe::ChunkGrid(chunk_grid) => chunk_grid.array_shape(),
ArrayBuilderChunkGridMaybe::Metadata(array_shape, _chunk_grid_metadata) => array_shape,
};
let chunk_grid_metadata = chunk_grid_metadata.into();
self.chunk_grid = (array_shape.to_vec(), chunk_grid_metadata).into();
self
}
pub fn chunk_grid(&mut self, chunk_grid: impl Into<ChunkGrid>) -> &mut Self {
let chunk_grid: ChunkGrid = chunk_grid.into();
self.chunk_grid = chunk_grid.into();
self
}
pub fn fill_value(&mut self, fill_value: impl Into<ArrayBuilderFillValue>) -> &mut Self {
self.fill_value = fill_value.into();
self
}
pub fn chunk_key_encoding(
&mut self,
chunk_key_encoding: impl Into<ChunkKeyEncoding>,
) -> &mut Self {
self.chunk_key_encoding = chunk_key_encoding.into();
self
}
pub fn chunk_key_encoding_default_separator(
&mut self,
separator: ChunkKeySeparator,
) -> &mut Self {
self.chunk_key_encoding = DefaultChunkKeyEncoding::new(separator).into();
self
}
pub fn array_to_array_codecs(
&mut self,
array_to_array_codecs: Vec<Arc<dyn ArrayToArrayCodecTraits>>,
) -> &mut Self {
self.array_to_array_codecs = array_to_array_codecs;
self
}
pub fn array_to_bytes_codec(
&mut self,
array_to_bytes_codec: Arc<dyn ArrayToBytesCodecTraits>,
) -> &mut Self {
self.array_to_bytes_codec = Some(array_to_bytes_codec);
self
}
pub fn bytes_to_bytes_codecs(
&mut self,
bytes_to_bytes_codecs: Vec<Arc<dyn BytesToBytesCodecTraits>>,
) -> &mut Self {
self.bytes_to_bytes_codecs = bytes_to_bytes_codecs;
self
}
#[cfg(feature = "sharding")]
pub fn subchunk_shape(&mut self, subchunk_shape: impl Into<Option<ArrayShape>>) -> &mut Self {
self.subchunk_shape = subchunk_shape.into();
self
}
pub fn attributes(
&mut self,
attributes: serde_json::Map<String, serde_json::Value>,
) -> &mut Self {
self.attributes = attributes;
self
}
pub fn attributes_mut(&mut self) -> &mut serde_json::Map<String, serde_json::Value> {
&mut self.attributes
}
pub fn additional_fields(&mut self, additional_fields: AdditionalFieldsV3) -> &mut Self {
self.additional_fields = additional_fields;
self
}
pub fn dimension_names<I, D>(&mut self, dimension_names: Option<I>) -> &mut Self
where
I: IntoIterator<Item = D>,
D: IntoDimensionName,
{
if let Some(dimension_names) = dimension_names {
self.dimension_names = Some(
dimension_names
.into_iter()
.map(IntoDimensionName::into_dimension_name)
.collect(),
);
} else {
self.dimension_names = None;
}
self
}
pub fn storage_transformers(
&mut self,
storage_transformers: StorageTransformerChain,
) -> &mut Self {
self.storage_transformers = storage_transformers;
self
}
pub fn build_metadata(&self) -> Result<ArrayMetadataV3, ArrayCreateError> {
let chunk_grid = match &self.chunk_grid {
ArrayBuilderChunkGridMaybe::ChunkGrid(chunk_grid) => chunk_grid.clone(),
ArrayBuilderChunkGridMaybe::Metadata(array_shape, metadata) => {
ChunkGrid::from_metadata(&metadata.to_metadata()?, array_shape)
.map_err(ArrayCreateError::ChunkGridCreateError)?
}
};
let data_type = self.data_type.to_data_type()?;
let fill_value = match &self.fill_value.0 {
ArrayBuilderFillValueImpl::FillValue(fill_value) => fill_value.clone(),
ArrayBuilderFillValueImpl::Metadata(fill_value_metadata) => {
data_type.fill_value_v3(fill_value_metadata).map_err(|_| {
ArrayCreateError::InvalidFillValueMetadata {
data_type_name: data_type
.name_v3()
.map_or_else(String::new, Cow::into_owned),
fill_value_metadata: fill_value_metadata.clone(),
}
})?
}
};
if let Some(dimension_names) = &self.dimension_names
&& dimension_names.len() != chunk_grid.dimensionality()
{
return Err(ArrayCreateError::InvalidDimensionNames(
dimension_names.len(),
chunk_grid.dimensionality(),
));
}
let array_to_bytes_codec = self
.array_to_bytes_codec
.clone()
.unwrap_or_else(|| super::codec::default_array_to_bytes_codec(&data_type));
#[cfg(feature = "sharding")]
let codec_chain = if let Some(subchunk_shape) = &self.subchunk_shape {
use super::codec::array_to_bytes::sharding::ShardingCodecBuilder;
let subchunk_shape: ChunkShape = subchunk_shape
.iter()
.copied()
.map(NonZeroU64::try_from)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| ArrayCreateError::InvalidSubchunkShape(subchunk_shape.clone()))?;
let mut sharding_builder = ShardingCodecBuilder::new(subchunk_shape, &data_type);
sharding_builder
.array_to_array_codecs(self.array_to_array_codecs.clone())
.array_to_bytes_codec(array_to_bytes_codec.clone())
.bytes_to_bytes_codecs(self.bytes_to_bytes_codecs.clone());
CodecChain::new(vec![], Arc::new(sharding_builder.build()), vec![])
} else {
CodecChain::new(
self.array_to_array_codecs.clone(),
array_to_bytes_codec,
self.bytes_to_bytes_codecs.clone(),
)
};
#[cfg(not(feature = "sharding"))]
let codec_chain = CodecChain::new(
self.array_to_array_codecs.clone(),
array_to_bytes_codec,
self.bytes_to_bytes_codecs.clone(),
);
let data_type_name = data_type
.name_v3()
.map_or_else(String::new, Cow::into_owned);
let data_type_configuration = data_type.configuration_v3();
let data_type_metadata = if data_type_configuration.is_empty() {
MetadataV3::new(data_type_name.clone())
} else {
MetadataV3::new_with_configuration(data_type_name.clone(), data_type_configuration)
};
Ok(ArrayMetadataV3::new(
chunk_grid.array_shape().to_vec(),
chunk_grid.metadata(),
data_type_metadata,
data_type.metadata_fill_value(&fill_value).map_err(|_| {
ArrayCreateError::InvalidFillValue {
data_type_name,
fill_value,
}
})?,
codec_chain.create_metadatas(self.metadata_options.codec_metadata_options()),
)
.with_attributes(self.attributes.clone())
.with_additional_fields(self.additional_fields.clone())
.with_chunk_key_encoding(self.chunk_key_encoding.metadata())
.with_dimension_names(self.dimension_names.clone())
.with_storage_transformers(self.storage_transformers.create_metadatas()))
}
pub fn build<TStorage: ?Sized>(
&self,
storage: Arc<TStorage>,
path: &str,
) -> Result<Array<TStorage>, ArrayCreateError> {
let path: NodePath = path.try_into()?;
let array_metadata = ArrayMetadata::V3(self.build_metadata()?);
Ok(
Array::new_with_metadata(storage, path.as_str(), array_metadata)?
.with_metadata_options(self.metadata_options)
.with_codec_options(self.codec_options),
)
}
pub fn build_arc<TStorage: ?Sized>(
&self,
storage: Arc<TStorage>,
path: &str,
) -> Result<Arc<Array<TStorage>>, ArrayCreateError> {
Ok(Arc::new(self.build(storage, path)?))
}
}
#[cfg(test)]
mod tests {
use std::num::NonZeroU64;
use zarrs_data_type::FillValue;
use super::*;
use crate::array::chunk_grid::RegularChunkGrid;
use crate::array::chunk_key_encoding::V2ChunkKeyEncoding;
use crate::array::data_type;
use zarrs_metadata::FillValueMetadata;
use zarrs_metadata::v3::MetadataV3;
use zarrs_metadata_ext::chunk_grid::regular::RegularChunkGridConfiguration;
use zarrs_storage::storage_adapter::usage_log::UsageLogStorageAdapter;
use zarrs_storage::store::MemoryStore;
#[test]
fn array_builder() {
let mut builder = ArrayBuilder::new(vec![8, 8], [2, 2], data_type::int8(), 0i8);
builder.shape(vec![8, 8]);
builder.data_type(data_type::int8());
builder.chunk_grid_metadata([2, 2]);
builder.fill_value(0i8);
builder.dimension_names(["y", "x"].into());
let mut attributes = serde_json::Map::new();
attributes.insert("key".to_string(), "value".into());
builder.attributes(attributes.clone());
let mut additional_fields = AdditionalFieldsV3::new();
let additional_field = serde_json::Map::new();
additional_fields.insert("key".to_string(), additional_field.into());
builder.additional_fields(additional_fields.clone());
builder.chunk_key_encoding(V2ChunkKeyEncoding::new_dot());
builder.chunk_key_encoding_default_separator(ChunkKeySeparator::Dot); let log_writer = Arc::new(std::sync::Mutex::new(std::io::stdout()));
let storage = Arc::new(MemoryStore::new());
let storage = Arc::new(UsageLogStorageAdapter::new(storage, log_writer, || {
chrono::Utc::now().format("[%T%.3f] ").to_string()
}));
println!("{:?}", builder.build(storage.clone(), "/"));
let array = builder.build(storage, "/").unwrap();
assert_eq!(array.shape(), &[8, 8]);
assert_eq!(*array.data_type(), data_type::int8());
assert_eq!(array.chunk_grid_shape(), &vec![4, 4]);
assert_eq!(array.fill_value(), &FillValue::from(0i8));
assert_eq!(
array.dimension_names(),
&Some(vec![Some("y".to_string()), Some("x".to_string())])
);
assert_eq!(array.attributes(), &attributes);
if let ArrayMetadata::V3(metadata) = array.metadata() {
assert_eq!(metadata.additional_fields, additional_fields);
}
let builder2 = array.builder();
assert_eq!(builder.data_type, builder2.data_type);
assert_eq!(builder.fill_value, builder2.fill_value);
assert_eq!(builder.attributes, builder2.attributes);
assert_eq!(builder.dimension_names, builder2.dimension_names);
assert_eq!(builder.additional_fields, builder2.additional_fields);
}
#[test]
fn array_builder_invalid() {
let storage = Arc::new(MemoryStore::new());
let builder = ArrayBuilder::new(vec![8, 8], vec![2, 2, 2], data_type::int8(), 0i8);
assert!(builder.build(storage.clone(), "/").is_err());
let builder = ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i16);
assert!(builder.build(storage.clone(), "/").is_ok());
let builder = ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), vec![0, 0]);
assert!(builder.build(storage.clone(), "/").is_err());
let mut builder = ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i8);
builder.dimension_names(["z", "y", "x"].into());
assert!(builder.build(storage.clone(), "/").is_err());
}
#[test]
fn array_builder_invalid_fill_value_metadata_error() {
let storage = Arc::new(MemoryStore::new());
let builder = ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::string(), 123);
let err = builder.build(storage, "/").unwrap_err();
assert_eq!(
err.to_string(),
"invalid fill value metadata for data type `string`: 123"
);
}
#[test]
fn array_builder_variants_array_shape() {
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new([8, 8], vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new([8, 8], vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new([8, 8].as_slice(), vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
}
#[test]
fn array_builder_variants_data_type() {
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], "int8", 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], r#"{"name":"int8"}"#, 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
vec![2, 2],
r#"{"name":"int8"}"#.to_string(),
0i8,
)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
vec![2, 2],
r#"{"name":"int8", "configuration":{},"must_understand":true}"#,
0i8,
)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], MetadataV3::new("int8"), 0i8)
.build_metadata()
.unwrap();
}
#[test]
fn array_builder_variants_chunk_grid() {
assert!(
ArrayBuilder::new(vec![8, 8], vec![0, 0], data_type::int8(), 0i8)
.build_metadata()
.is_err()
);
assert!(
ArrayBuilder::new(vec![8, 8], "regular", data_type::int8(), 0i8)
.build_metadata()
.is_err()
);
assert!(
ArrayBuilder::new(vec![8, 8], "{", data_type::int8(), 0i8)
.build_metadata()
.is_err()
);
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], [2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], [2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], [2, 2].as_slice(), data_type::int8(), 0i8)
.build_metadata()
.unwrap();
let nz2 = NonZeroU64::new(2).unwrap();
ArrayBuilder::new(vec![8, 8], vec![nz2, nz2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], [nz2, nz2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], [nz2, nz2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], [nz2, nz2].as_slice(), data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
r#"{"name":"regular","configuration":{"chunk_shape":[2,2]}}"#,
data_type::int8(),
0i8,
)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
r#"{"name":"regular","configuration":{"chunk_shape":[2,2]}}"#.to_string(),
data_type::int8(),
0i8,
)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
MetadataV3::new_with_configuration(
"regular",
RegularChunkGridConfiguration {
chunk_shape: vec![NonZeroU64::new(2).unwrap(); 2],
},
),
data_type::int8(),
0i8,
)
.build_metadata()
.unwrap();
ArrayBuilder::new_with_chunk_grid(
RegularChunkGrid::new(vec![8, 8], vec![NonZeroU64::new(2).unwrap(); 2]).unwrap(),
data_type::int8(),
0i8,
)
.build_metadata()
.unwrap();
let chunk_grid = Arc::new(
RegularChunkGrid::new(vec![4, 4], vec![NonZeroU64::new(2).unwrap(); 2]).unwrap(),
);
ArrayBuilder::new_with_chunk_grid(chunk_grid, data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new_with_chunk_grid(
RegularChunkGrid::new(vec![8, 8], vec![NonZeroU64::new(2).unwrap(); 2]).unwrap(),
data_type::int8(),
0i8,
)
.build_metadata()
.unwrap();
}
#[test]
fn array_builder_variants_fill_value() {
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i8)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::int8(), 0i16)
.build_metadata()
.unwrap(); ArrayBuilder::new(
vec![8, 8],
vec![2, 2],
data_type::int8(),
FillValue::new(vec![0u8]),
)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
vec![2, 2],
data_type::int8(),
FillValue::from(0u8),
)
.build_metadata()
.unwrap();
ArrayBuilder::new(
vec![8, 8],
vec![2, 2],
data_type::int8(),
FillValueMetadata::Number(serde_json::Number::from(0u8)),
)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::float32(), f32::NAN)
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::float32(), "NaN")
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::float32(), "Infinity")
.build_metadata()
.unwrap();
ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::float32(), "-Infinity")
.build_metadata()
.unwrap();
let ab = ArrayBuilder::new(vec![8, 8], vec![2, 2], data_type::float32(), "0x7fc00000");
assert_eq!(
ab.build_metadata().unwrap().fill_value,
FillValueMetadata::from("NaN")
);
let ab = ArrayBuilder::new(
vec![8, 8],
vec![2, 2],
data_type::float32(),
f32::from_bits(0x7fc00001),
); assert_eq!(
ab.build_metadata().unwrap().fill_value,
FillValueMetadata::from("0x7fc00001")
);
}
}