Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(metadata): add support for metadata with must_understand (ZEP0009) #148

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions zarrs_metadata/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- Add support for a `must_understand` field to `MetadataV3` (ZEP0009)
- Extensions can now be parsed in more than just the additional fields of array/group metadata (e.g. codecs)

## [0.3.5] - 2025-02-18

### Fixed
Expand Down
105 changes: 99 additions & 6 deletions zarrs_metadata/src/v3/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,47 @@

/// Metadata with a name and optional configuration.
///
/// Represents most fields in Zarr V3 array metadata (see [`ArrayMetadataV3`](crate::v3::ArrayMetadataV3)), which is structured as JSON with a name and optional configuration, or just a string representing the name.
/// Represents most fields in Zarr V3 array metadata (see [`ArrayMetadataV3`](crate::v3::ArrayMetadataV3)) which is either:
/// - a string name / identifier, or
/// - a JSON object with a required `name` field and optional `configuration` and `must_understand` fields.
///
/// Can be deserialised from a JSON string or name/configuration map.
/// For example:
/// `must_understand` is implicitly set to [`true`] if omitted.
/// See [ZEP0009](https://zarr.dev/zeps/draft/ZEP0009.html) for more information on this field and Zarr V3 extensions.
///
/// ### Example Metadata
/// ```json
/// "bytes"
/// ```
/// or
///
/// ```json
/// {
/// "name": "bytes",
/// }
/// ```
/// or
///
/// ```json
/// {
/// "name": "bytes",
/// "configuration": {
/// "endian": "little"
/// }
/// }
/// ```
///
/// ```json
/// {
/// "name": "bytes",
/// "configuration": {
/// "endian": "little"
/// },
/// "must_understand": False
/// }
/// ```
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct MetadataV3 {
name: String,
configuration: Option<MetadataConfiguration>,
must_understand: bool,
}

/// Configuration metadata.
Expand Down Expand Up @@ -67,9 +82,12 @@
s.serialize_entry("name", &self.name)?;
s.end()
} else {
let mut s = s.serialize_map(Some(2))?;
let mut s = s.serialize_map(Some(if self.must_understand { 2 } else { 3 }))?;
s.serialize_entry("name", &self.name)?;
s.serialize_entry("configuration", configuration)?;
if !self.must_understand {
s.serialize_entry("must_understand", &false)?;

Check warning on line 89 in zarrs_metadata/src/v3/metadata.rs

View check run for this annotation

Codecov / codecov/patch

zarrs_metadata/src/v3/metadata.rs#L89

Added line #L89 was not covered by tests
}
s.end()
}
} else {
Expand All @@ -78,6 +96,10 @@
}
}

fn default_must_understand() -> bool {
true
}

impl<'de> serde::Deserialize<'de> for MetadataV3 {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
Expand All @@ -86,6 +108,8 @@
name: String,
#[serde(default)]
configuration: Option<MetadataConfiguration>,
#[serde(default = "default_must_understand")]
must_understand: bool,
}

#[derive(Deserialize)]
Expand All @@ -102,10 +126,12 @@
MetadataIntermediate::Name(name) => Ok(Self {
name,
configuration: None,
must_understand: true,
}),
MetadataIntermediate::NameConfiguration(metadata) => Ok(Self {
name: metadata.name,
configuration: metadata.configuration,
must_understand: metadata.must_understand,
}),
}
}
Expand All @@ -118,6 +144,7 @@
Self {
name: name.into(),
configuration: None,
must_understand: true,
}
}

Expand All @@ -127,9 +154,17 @@
Self {
name: name.into(),
configuration: Some(configuration),
must_understand: true,
}
}

/// Set the value of the `must_understand` field.
#[must_use]
pub fn with_must_understand(mut self, must_understand: bool) -> Self {
self.must_understand = must_understand;
self
}

/// Convert a serializable configuration to [`MetadataV3`].
///
/// # Errors
Expand Down Expand Up @@ -173,6 +208,14 @@
self.configuration.as_ref()
}

/// Return whether the metadata must be understood as indicated by the `must_understand` field.
///
/// The `must_understand` field is implicitly `true` if omitted.
#[must_use]
pub fn must_understand(&self) -> bool {
self.must_understand
}

/// Returns true if the configuration is none or an empty map.
#[must_use]
pub fn configuration_is_none_or_empty(&self) -> bool {
Expand Down Expand Up @@ -346,3 +389,53 @@
/// Additional fields in array or group metadata.
// NOTE: It would be nice if this was just a serde_json::Map, but it only has implementations for `<String, Value>`.
pub type AdditionalFields = std::collections::BTreeMap<String, AdditionalField>;

#[cfg(test)]
mod tests {
use super::MetadataV3;

#[test]
fn metadata_must_understand_implicit_string() {
let metadata = r#""test""#;
let metadata: MetadataV3 = serde_json::from_str(&metadata).unwrap();
assert!(metadata.name() == "test");
assert!(metadata.must_understand());
}

#[test]
fn metadata_must_understand_implicit() {
let metadata = r#"{
"name": "test"
}"#;
let metadata: MetadataV3 = serde_json::from_str(&metadata).unwrap();
assert!(metadata.name() == "test");
assert!(metadata.must_understand());
}

#[test]
fn metadata_must_understand_true() {
let metadata = r#"{
"name": "test",
"must_understand": true
}"#;
let metadata: MetadataV3 = serde_json::from_str(&metadata).unwrap();
assert!(metadata.name() == "test");
assert!(metadata.must_understand());
}

#[test]
fn metadata_must_understand_false() {
let metadata = r#"{
"name": "test",
"must_understand": false
}"#;
let metadata: MetadataV3 = serde_json::from_str(&metadata).unwrap();
assert!(metadata.name() == "test");
assert!(!metadata.must_understand());
assert_ne!(metadata, MetadataV3::new("test"));
assert_eq!(
metadata,
MetadataV3::new("test").with_must_understand(false)
);
}
}
Loading