use derive_more::derive::{Display, From};
use derive_more::{Deref, Into};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
mod array;
pub type ArrayShape = Vec<u64>;
pub type ChunkShape = Vec<std::num::NonZeroU64>;
pub mod v3;
pub mod v2;
pub use v3::FillValueMetadataV3 as FillValueMetadata;
pub use array::{ChunkKeySeparator, DimensionName, Endianness, IntoDimensionName};
use thiserror::Error;
#[derive(Deserialize, Serialize, Clone, PartialEq, Debug, Display, From)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum ArrayMetadata {
V3(v3::ArrayMetadataV3),
V2(v2::ArrayMetadataV2),
}
impl ArrayMetadata {
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn to_string_pretty(&self) -> String {
serde_json::to_string_pretty(self).expect("array metadata is valid JSON")
}
}
impl TryFrom<&str> for ArrayMetadata {
type Error = serde_json::Error;
fn try_from(metadata_json: &str) -> Result<Self, Self::Error> {
serde_json::from_str::<Self>(metadata_json)
}
}
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Debug, Display, From)]
#[serde(untagged)]
pub enum GroupMetadata {
V3(v3::GroupMetadataV3),
V2(v2::GroupMetadataV2),
}
impl GroupMetadata {
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn to_string_pretty(&self) -> String {
serde_json::to_string_pretty(self).expect("group metadata is valid JSON")
}
}
impl TryFrom<&str> for GroupMetadata {
type Error = serde_json::Error;
fn try_from(metadata_json: &str) -> Result<Self, Self::Error> {
serde_json::from_str::<Self>(metadata_json)
}
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum NodeMetadata {
Array(ArrayMetadata),
Group(GroupMetadata),
}
impl NodeMetadata {
#[allow(clippy::missing_panics_doc)]
#[must_use]
pub fn to_string_pretty(&self) -> String {
serde_json::to_string_pretty(self).expect("node metadata is valid JSON")
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum DataTypeSize {
Fixed(usize),
Variable,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, Deref, From, Into, Eq, PartialEq)]
pub struct Configuration(serde_json::Map<String, serde_json::Value>);
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
pub struct EmptyConfiguration {}
impl Configuration {
pub fn to_typed<TConfiguration: DeserializeOwned>(
&self,
) -> Result<TConfiguration, std::sync::Arc<serde_json::Error>> {
serde_json::from_value(serde_json::Value::Object(self.0.clone()))
.map_err(std::sync::Arc::new)
}
}
impl<T: ConfigurationSerialize> From<T> for Configuration {
fn from(value: T) -> Self {
match serde_json::to_value(value) {
Ok(serde_json::Value::Object(configuration)) => configuration.into(),
_ => {
panic!("the configuration could not be converted to a JSON object")
}
}
}
}
pub trait ConfigurationSerialize: Serialize + DeserializeOwned {
fn try_from_configuration(configuration: Configuration) -> Result<Self, serde_json::Error> {
serde_json::from_value(serde_json::Value::Object(configuration.0))
}
}
#[derive(Clone, Debug, Error, From)]
#[error("{name} is unsupported, configuration: {configuration:?}")]
pub struct ConfigurationError {
name: String,
configuration: Option<Configuration>,
}
impl ConfigurationError {
#[must_use]
pub fn new(name: String, configuration: Option<Configuration>) -> Self {
Self {
name,
configuration,
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub const fn configuration(&self) -> Option<&Configuration> {
self.configuration.as_ref()
}
}
#[cfg(test)]
mod tests {
use v3::{AdditionalFieldV3, AdditionalFieldsV3, MetadataV3};
use super::*;
#[test]
fn metadata() {
let metadata = MetadataV3::try_from(r#""bytes""#);
assert!(metadata.is_ok());
assert_eq!(metadata.unwrap().to_string(), r"bytes");
assert!(MetadataV3::try_from(r#"{ "name": "bytes" }"#).is_ok());
let metadata =
MetadataV3::try_from(r#"{ "name": "bytes", "configuration": { "endian": "little" } }"#);
assert!(metadata.is_ok());
let metadata = metadata.unwrap();
assert_eq!(metadata.to_string(), r#"bytes {"endian":"little"}"#);
assert_eq!(metadata.name(), "bytes");
assert!(metadata.configuration().is_some());
let configuration = metadata.configuration().unwrap();
assert!(configuration.contains_key("endian"));
assert_eq!(
configuration.get("endian").unwrap().as_str().unwrap(),
"little"
);
assert_eq!(
MetadataV3::try_from(r#"{ "name": "bytes", "invalid": { "endian": "little" } }"#)
.unwrap_err()
.to_string(),
r#"Expected metadata "<name>" or {"name":"<name>"} or {"name":"<name>","configuration":{}}"#
);
let metadata =
MetadataV3::try_from(r#"{ "name": "bytes", "configuration": { "endian": "little" } }"#)
.unwrap();
let mut configuration = serde_json::Map::new();
configuration.insert("endian".to_string(), "little".into());
assert_eq!(metadata.configuration(), Some(&configuration.into()));
}
#[test]
fn additional_fields_constructors() {
let additional_field = serde_json::Map::new();
let additional_field: AdditionalFieldV3 = additional_field.into();
assert!(additional_field.must_understand());
assert!(
additional_field.as_value() == &serde_json::Value::Object(serde_json::Map::default())
);
assert!(serde_json::to_string(&additional_field).unwrap() == r#"{"must_understand":true}"#);
let additional_field = AdditionalFieldV3::new("test", true);
assert!(additional_field.must_understand());
assert!(additional_field.as_value() == &serde_json::Value::String("test".to_string()));
assert!(serde_json::to_string(&additional_field).unwrap() == r#""test""#);
let additional_field = AdditionalFieldV3::new(123, false);
assert!(!additional_field.must_understand());
assert!(
additional_field.as_value()
== &serde_json::Value::Number(serde_json::Number::from(123))
);
assert!(serde_json::to_string(&additional_field).unwrap() == "123");
}
#[test]
fn additional_fields_valid() {
let json = r#"{
"unknown_field": {
"key": "value",
"must_understand": false
},
"unsupported_field_1": {
"key": "value",
"must_understand": true
},
"unsupported_field_2": {
"key": "value"
},
"unsupported_field_3": [],
"unsupported_field_4": "test"
}"#;
let additional_fields = serde_json::from_str::<AdditionalFieldsV3>(json).unwrap();
assert!(additional_fields.len() == 5);
assert!(!additional_fields["unknown_field"].must_understand());
assert!(additional_fields["unsupported_field_1"].must_understand());
assert!(additional_fields["unsupported_field_2"].must_understand());
assert!(additional_fields["unsupported_field_3"].must_understand());
assert!(additional_fields["unsupported_field_4"].must_understand());
}
}