sigmf/extensions.rs
1//! Typed access to SigMF extension namespaces.
2//!
3//! SigMF lets applications add fields to the `global`, `captures`,
4//! `annotations`, and `collection` objects under their own namespace prefix
5//! (for example `antenna:gain`), declaring each namespace and version in
6//! `core:extensions`. This crate preserves such fields as untyped JSON; this
7//! module adds a typed layer on top:
8//!
9//! - [`SigmfExtension`] describes a Rust type that maps to a set of namespaced
10//! fields in one [`ExtensionScope`]. Implement it by hand, or with
11//! `#[derive(SigmfExtension)]` when the `derive` feature is enabled.
12//! - Typed getters and setters read and write those fields:
13//! [`Metadata::global_extension`](crate::meta::Metadata::global_extension),
14//! [`Capture::extension`](crate::capture::Capture::extension),
15//! [`Annotation::extension`](crate::annotation::Annotation::extension),
16//! [`CollectionMetadata::extension`](crate::collection::CollectionMetadata::extension),
17//! and their `set_*`/`remove_*` counterparts.
18//! - [`ExtensionRegistry`] records which extensions an application understands
19//! so that validation (e.g.
20//! [`Metadata::validate_with_registry`](crate::meta::Metadata::validate_with_registry))
21//! can verify declared extensions and their fields.
22//! - [`CropExtensionHandler`] lets applications adjust extension fields when a
23//! recording is cropped.
24//!
25//! With the `schema` feature, [`extension_schema`] and
26//! [`validate_json_schema`] generate and apply JSON Schemas for extension types.
27//!
28//! Setting extension fields does not modify `core:extensions`; declare the
29//! extension separately, e.g. with [`SigmfExtension::declaration`].
30//!
31//! # Example
32//!
33//! ```rust
34//! use serde::{Deserialize, Serialize};
35//! use sigmf::capture::Capture;
36//! use sigmf::extensions::{ExtensionScope, SigmfExtension};
37//!
38//! #[derive(Debug, PartialEq, Serialize, Deserialize)]
39//! struct Antenna {
40//! #[serde(rename = "antenna:port")]
41//! port: u8,
42//! }
43//!
44//! impl SigmfExtension for Antenna {
45//! const NAMESPACE: &'static str = "antenna";
46//! const VERSION: &'static str = "1.0.0";
47//! const SCOPE: ExtensionScope = ExtensionScope::Capture;
48//! const FIELDS: &'static [&'static str] = &["antenna:port"];
49//! }
50//!
51//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
52//! let mut capture = Capture::new(0);
53//! capture.set_extension(&Antenna { port: 2 })?;
54//! assert_eq!(capture.extra_fields()["antenna:port"], 2);
55//! assert_eq!(capture.extension::<Antenna>()?, Antenna { port: 2 });
56//!
57//! capture.remove_extension::<Antenna>()?;
58//! assert!(capture.extra_fields().get("antenna:port").is_none());
59//! # Ok(())
60//! # }
61//! ```
62
63use crate::SigmfError;
64use crate::global::ExtensionDeclaration;
65use serde::Serialize;
66use serde::de::DeserializeOwned;
67use serde_json::{Map, Value};
68use std::collections::{BTreeMap, BTreeSet};
69
70/// The SigMF object in which an extension's fields may appear.
71#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
72pub enum ExtensionScope {
73 /// The `global` object of a `.sigmf-meta` file.
74 Global,
75 /// An entry of the `captures` array.
76 Capture,
77 /// An entry of the `annotations` array.
78 Annotation,
79 /// The `collection` object of a `.sigmf-collection` file.
80 Collection,
81}
82
83/// Transforms application-defined fields while a recording is cropped.
84pub trait CropExtensionHandler {
85 /// Receives each field map that contains extension data.
86 ///
87 /// `sample_start` and `sample_count` refer to the unmodified capture or
88 /// annotation; both are `None` for global extension fields. The crop interval
89 /// uses absolute, half-open sample indices.
90 fn transform_for_crop(
91 &mut self,
92 scope: ExtensionScope,
93 sample_start: Option<u64>,
94 sample_count: Option<u64>,
95 crop_start: u64,
96 crop_count: u64,
97 fields: &mut Map<String, Value>,
98 ) -> Result<(), SigmfError>;
99}
100
101/// A typed view of one SigMF extension's fields within a single scope.
102///
103/// The type is serialized with Serde to a JSON object whose keys are the
104/// namespaced wire names (e.g. `"antenna:port"`), so fields normally use
105/// `#[serde(rename = "namespace:field")]`. Every key it serializes must be
106/// listed in [`FIELDS`](Self::FIELDS).
107///
108/// With the `derive` feature, `#[derive(sigmf::SigmfExtension)]` together with
109/// `#[sigmf(namespace = "...", version = "...", scope = "...")]` implements
110/// this trait, filling in the associated constants and checking at compile
111/// time that every field's Serde name uses the namespace prefix. See the
112/// [module documentation](self) for a manual implementation.
113pub trait SigmfExtension: Serialize + DeserializeOwned {
114 /// The extension namespace, i.e. the prefix before `:` in field names.
115 ///
116 /// Must be non-empty and must not be `"core"`. The derive macro takes it
117 /// from `#[sigmf(namespace = "...")]` and additionally requires ASCII
118 /// alphanumerics, `_`, or `-`.
119 const NAMESPACE: &'static str;
120 /// The extension version written to `core:extensions`.
121 ///
122 /// Must be non-empty. The derive macro takes it from
123 /// `#[sigmf(version = "...")]`.
124 const VERSION: &'static str;
125 /// The object in which this extension's fields appear.
126 ///
127 /// The derive macro maps `#[sigmf(scope = "...")]` values `"global"`,
128 /// `"capture"`, `"annotation"`, and `"collection"` to the corresponding
129 /// [`ExtensionScope`] variant.
130 const SCOPE: ExtensionScope;
131 /// The complete set of namespaced wire field names this type owns.
132 ///
133 /// Only these keys are read by the typed getters and replaced or removed
134 /// by the typed setters; other fields are left untouched. The derive macro
135 /// fills this with each struct field's Serde wire name.
136 const FIELDS: &'static [&'static str];
137
138 /// Serializes `self` to a JSON value.
139 ///
140 /// # Errors
141 ///
142 /// Returns [`SigmfError::Serde`] if serialization fails.
143 fn to_extension_value(&self) -> Result<Value, SigmfError> {
144 serde_json::to_value(self).map_err(SigmfError::Serde)
145 }
146
147 /// Deserializes an extension from a JSON value.
148 ///
149 /// # Errors
150 ///
151 /// Returns [`SigmfError::InvalidMetadata`] if `value` does not match the
152 /// type, e.g. because a required field is missing.
153 fn from_extension_value(value: Value) -> Result<Self, SigmfError>
154 where
155 Self: Sized,
156 {
157 serde_json::from_value(value)
158 .map_err(|error| SigmfError::InvalidMetadata(error.to_string()))
159 }
160
161 /// Builds the `core:extensions` entry declaring this extension.
162 ///
163 /// `optional` indicates whether readers may ignore the extension.
164 fn declaration(optional: bool) -> ExtensionDeclaration {
165 ExtensionDeclaration {
166 name: Self::NAMESPACE.to_string(),
167 version: Self::VERSION.to_string(),
168 optional,
169 }
170 }
171}
172
173/// The set of extensions, by namespace and version, an application supports.
174///
175/// Pass a registry to
176/// [`Metadata::validate_with_registry`](crate::meta::Metadata::validate_with_registry)
177/// or
178/// [`Collection::validate_with_registry`](crate::collection::Collection::validate_with_registry)
179/// so declared extensions are treated as verified and their fields are
180/// checked against each extension's [`SCOPE`](SigmfExtension::SCOPE) and
181/// [`FIELDS`](SigmfExtension::FIELDS).
182///
183/// # Example
184///
185/// ```rust
186/// use serde::{Deserialize, Serialize};
187/// use sigmf::extensions::{ExtensionRegistry, ExtensionScope, SigmfExtension};
188///
189/// #[derive(Serialize, Deserialize)]
190/// struct Lab {
191/// #[serde(rename = "lab:gain_db")]
192/// gain_db: f64,
193/// }
194///
195/// impl SigmfExtension for Lab {
196/// const NAMESPACE: &'static str = "lab";
197/// const VERSION: &'static str = "1.0.0";
198/// const SCOPE: ExtensionScope = ExtensionScope::Global;
199/// const FIELDS: &'static [&'static str] = &["lab:gain_db"];
200/// }
201///
202/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
203/// let mut registry = ExtensionRegistry::new();
204/// registry.register::<Lab>()?;
205/// assert!(registry.supports("lab", "1.0.0"));
206/// assert!(!registry.supports("lab", "2.0.0"));
207/// assert!(registry.supports_field("lab", "1.0.0", ExtensionScope::Global, "lab:gain_db"));
208/// assert!(!registry.supports_scope("lab", "1.0.0", ExtensionScope::Capture));
209///
210/// // Registering the same namespace and version twice is an error.
211/// assert!(registry.register::<Lab>().is_err());
212/// # Ok(())
213/// # }
214/// ```
215#[derive(Debug, Clone, Default)]
216pub struct ExtensionRegistry {
217 supported: BTreeMap<(String, String), RegisteredExtension>,
218}
219
220#[derive(Debug, Clone)]
221struct RegisteredExtension {
222 scope: ExtensionScope,
223 fields: BTreeSet<String>,
224}
225
226impl ExtensionRegistry {
227 /// Creates an empty registry.
228 pub fn new() -> Self {
229 Self::default()
230 }
231
232 /// Registers support for extension `T` at its namespace and version.
233 ///
234 /// # Errors
235 ///
236 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s namespace is empty or
237 /// `"core"`, its version is empty, or the same namespace and version are
238 /// already registered.
239 pub fn register<T: SigmfExtension>(&mut self) -> Result<(), SigmfError> {
240 let namespace = T::NAMESPACE;
241 if namespace.is_empty() || namespace == "core" || T::VERSION.is_empty() {
242 return Err(SigmfError::InvalidMetadata(
243 "extension namespace and version must be non-empty and namespace cannot be core"
244 .into(),
245 ));
246 }
247 let key = (namespace.to_string(), T::VERSION.to_string());
248 if self.supported.contains_key(&key) {
249 return Err(SigmfError::InvalidMetadata(format!(
250 "extension {namespace:?} version {:?} is already registered",
251 T::VERSION
252 )));
253 }
254 self.supported.insert(
255 key,
256 RegisteredExtension {
257 scope: T::SCOPE,
258 fields: T::FIELDS.iter().map(|field| (*field).to_string()).collect(),
259 },
260 );
261 Ok(())
262 }
263
264 /// Returns `true` if the extension `namespace` at `version` is registered.
265 pub fn supports(&self, namespace: &str, version: &str) -> bool {
266 self.supported
267 .contains_key(&(namespace.to_string(), version.to_string()))
268 }
269
270 /// Returns `true` if the registered extension `namespace` at `version`
271 /// applies to `scope`.
272 ///
273 /// Returns `false` if the extension is not registered.
274 pub fn supports_scope(&self, namespace: &str, version: &str, scope: ExtensionScope) -> bool {
275 self.supported
276 .get(&(namespace.to_string(), version.to_string()))
277 .is_some_and(|extension| extension.scope == scope)
278 }
279
280 /// Returns `true` if the registered extension `namespace` at `version`
281 /// applies to `scope` and defines the namespaced wire name `field`.
282 ///
283 /// Returns `false` if the extension is not registered.
284 pub fn supports_field(
285 &self,
286 namespace: &str,
287 version: &str,
288 scope: ExtensionScope,
289 field: &str,
290 ) -> bool {
291 self.supported
292 .get(&(namespace.to_string(), version.to_string()))
293 .is_some_and(|extension| extension.scope == scope && extension.fields.contains(field))
294 }
295
296 /// Returns a `core:extensions` declaration for each registered extension.
297 ///
298 /// Declarations are sorted by namespace, then version, and are all marked
299 /// as required (`optional: false`).
300 pub fn declarations(&self) -> Vec<ExtensionDeclaration> {
301 self.supported
302 .iter()
303 .map(|((name, version), _)| ExtensionDeclaration {
304 name: name.clone(),
305 version: version.clone(),
306 optional: false,
307 })
308 .collect()
309 }
310}
311
312/// Generates the JSON Schema for extension type `T`.
313///
314/// The schema describes the JSON object `T` serializes to, keyed by its
315/// namespaced wire names.
316///
317/// # Errors
318///
319/// Returns [`SigmfError::Serde`] if the generated schema cannot be converted
320/// to a [`Value`].
321///
322/// # Example
323///
324/// ```rust
325/// # #[cfg(feature = "schema")]
326/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
327/// use serde::{Deserialize, Serialize};
328/// use serde_json::json;
329/// use sigmf::extensions::{
330/// extension_schema, validate_json_schema, ExtensionScope, SigmfExtension,
331/// };
332///
333/// #[derive(Serialize, Deserialize, schemars::JsonSchema)]
334/// struct Lab {
335/// #[serde(rename = "lab:gain_db")]
336/// gain_db: f64,
337/// }
338///
339/// impl SigmfExtension for Lab {
340/// const NAMESPACE: &'static str = "lab";
341/// const VERSION: &'static str = "1.0.0";
342/// const SCOPE: ExtensionScope = ExtensionScope::Global;
343/// const FIELDS: &'static [&'static str] = &["lab:gain_db"];
344/// }
345///
346/// let schema = extension_schema::<Lab>()?;
347/// validate_json_schema(&schema, &json!({ "lab:gain_db": 30.0 }))?;
348/// assert!(validate_json_schema(&schema, &json!({ "lab:gain_db": "high" })).is_err());
349/// # Ok(())
350/// # }
351/// # #[cfg(not(feature = "schema"))]
352/// # fn main() {}
353/// ```
354#[cfg(feature = "schema")]
355pub fn extension_schema<T>() -> Result<Value, SigmfError>
356where
357 T: SigmfExtension + schemars::JsonSchema,
358{
359 serde_json::to_value(schemars::schema_for!(T)).map_err(SigmfError::Serde)
360}
361
362/// Validates `instance` against the JSON Schema `schema`.
363///
364/// For safety, schemas containing `$ref`, `$dynamicRef`, or `$recursiveRef`
365/// values that are not local (`#/...`) references are rejected rather than
366/// fetched.
367///
368/// # Errors
369///
370/// Returns [`SigmfError::InvalidMetadata`] if the schema contains a remote
371/// reference, is not a valid JSON Schema, or `instance` does not conform to
372/// it (the message lists every validation error).
373#[cfg(feature = "schema")]
374pub fn validate_json_schema(schema: &Value, instance: &Value) -> Result<(), SigmfError> {
375 if schema_contains_remote_reference(schema) {
376 return Err(SigmfError::InvalidMetadata(
377 "remote JSON Schema references are disabled".into(),
378 ));
379 }
380 let validator = jsonschema::validator_for(schema)
381 .map_err(|error| SigmfError::InvalidMetadata(format!("invalid JSON Schema: {error}")))?;
382 let errors = validator
383 .iter_errors(instance)
384 .map(|error| error.to_string())
385 .collect::<Vec<_>>();
386 if !errors.is_empty() {
387 return Err(SigmfError::InvalidMetadata(format!(
388 "JSON Schema validation failed: {}",
389 errors.join("; ")
390 )));
391 }
392 Ok(())
393}
394
395#[cfg(feature = "schema")]
396fn schema_contains_remote_reference(value: &Value) -> bool {
397 match value {
398 Value::Object(object) => object.iter().any(|(key, value)| {
399 (matches!(key.as_str(), "$ref" | "$dynamicRef" | "$recursiveRef")
400 && value
401 .as_str()
402 .is_some_and(|reference| !reference.starts_with("#/")))
403 || schema_contains_remote_reference(value)
404 }),
405 Value::Array(values) => values.iter().any(schema_contains_remote_reference),
406 _ => false,
407 }
408}
409
410impl crate::meta::Metadata {
411 /// Reads extension `T` from the fields of the `global` object.
412 ///
413 /// Only the keys listed in [`SigmfExtension::FIELDS`] are considered;
414 /// absent keys are simply omitted before deserialization.
415 ///
416 /// # Errors
417 ///
418 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
419 /// [`ExtensionScope::Global`] or the fields do not deserialize into `T`.
420 pub fn global_extension<T: SigmfExtension>(&self) -> Result<T, SigmfError> {
421 get_extension_fields::<T>(ExtensionScope::Global, self.global().extra_fields())
422 }
423
424 /// Writes extension `T` into the `global` object.
425 ///
426 /// All of `T`'s declared [`FIELDS`](SigmfExtension::FIELDS) are first
427 /// removed, then the serialized fields are inserted, so fields omitted by
428 /// serialization (e.g. `None` with `skip_serializing_if`) are cleared.
429 /// `core:extensions` is not modified.
430 ///
431 /// # Errors
432 ///
433 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
434 /// [`ExtensionScope::Global`], `extension` does not serialize to a JSON
435 /// object, or it serializes a key not listed in `FIELDS`; returns
436 /// [`SigmfError::Serde`] if serialization fails. The metadata is unchanged
437 /// on error.
438 pub fn set_global_extension<T: SigmfExtension>(
439 &mut self,
440 extension: &T,
441 ) -> Result<(), SigmfError> {
442 let fields = extension_fields::<T>(ExtensionScope::Global, extension)?;
443 self.update_global_extension_fields(T::FIELDS, fields);
444 Ok(())
445 }
446
447 /// Removes all of extension `T`'s declared fields from the `global` object.
448 ///
449 /// `core:extensions` is not modified.
450 ///
451 /// # Errors
452 ///
453 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
454 /// [`ExtensionScope::Global`].
455 pub fn remove_global_extension<T: SigmfExtension>(&mut self) -> Result<(), SigmfError> {
456 ensure_scope::<T>(ExtensionScope::Global)?;
457 self.update_global_extension_fields(T::FIELDS, Map::new());
458 Ok(())
459 }
460}
461
462impl crate::capture::Capture {
463 /// Reads extension `T` from this capture's fields.
464 ///
465 /// Only the keys listed in [`SigmfExtension::FIELDS`] are considered;
466 /// absent keys are simply omitted before deserialization.
467 ///
468 /// # Errors
469 ///
470 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
471 /// [`ExtensionScope::Capture`] or the fields do not deserialize into `T`.
472 pub fn extension<T: SigmfExtension>(&self) -> Result<T, SigmfError> {
473 get_extension_fields::<T>(ExtensionScope::Capture, self.extra_fields())
474 }
475
476 /// Writes extension `T` into this capture.
477 ///
478 /// All of `T`'s declared [`FIELDS`](SigmfExtension::FIELDS) are first
479 /// removed, then the serialized fields are inserted, so fields omitted by
480 /// serialization are cleared. `core:extensions` is not modified.
481 ///
482 /// # Errors
483 ///
484 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
485 /// [`ExtensionScope::Capture`], `extension` does not serialize to a JSON
486 /// object, or it serializes a key not listed in `FIELDS`; returns
487 /// [`SigmfError::Serde`] if serialization fails. Nothing is changed on
488 /// error.
489 pub fn set_extension<T: SigmfExtension>(&mut self, extension: &T) -> Result<(), SigmfError> {
490 let fields = extension_fields::<T>(ExtensionScope::Capture, extension)?;
491 replace_extension_fields(self.extension_fields_mut(), T::FIELDS, fields);
492 Ok(())
493 }
494
495 /// Removes all of extension `T`'s declared fields from this capture.
496 ///
497 /// # Errors
498 ///
499 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
500 /// [`ExtensionScope::Capture`].
501 pub fn remove_extension<T: SigmfExtension>(&mut self) -> Result<(), SigmfError> {
502 ensure_scope::<T>(ExtensionScope::Capture)?;
503 replace_extension_fields(self.extension_fields_mut(), T::FIELDS, Map::new());
504 Ok(())
505 }
506}
507
508impl crate::annotation::Annotation {
509 /// Reads extension `T` from this annotation's fields.
510 ///
511 /// Only the keys listed in [`SigmfExtension::FIELDS`] are considered;
512 /// absent keys are simply omitted before deserialization.
513 ///
514 /// # Errors
515 ///
516 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
517 /// [`ExtensionScope::Annotation`] or the fields do not deserialize into `T`.
518 pub fn extension<T: SigmfExtension>(&self) -> Result<T, SigmfError> {
519 get_extension_fields::<T>(ExtensionScope::Annotation, self.extra_fields())
520 }
521
522 /// Writes extension `T` into this annotation.
523 ///
524 /// All of `T`'s declared [`FIELDS`](SigmfExtension::FIELDS) are first
525 /// removed, then the serialized fields are inserted, so fields omitted by
526 /// serialization are cleared. `core:extensions` is not modified.
527 ///
528 /// # Errors
529 ///
530 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
531 /// [`ExtensionScope::Annotation`], `extension` does not serialize to a JSON
532 /// object, or it serializes a key not listed in `FIELDS`; returns
533 /// [`SigmfError::Serde`] if serialization fails. Nothing is changed on
534 /// error.
535 pub fn set_extension<T: SigmfExtension>(&mut self, extension: &T) -> Result<(), SigmfError> {
536 let fields = extension_fields::<T>(ExtensionScope::Annotation, extension)?;
537 replace_extension_fields(self.extension_fields_mut(), T::FIELDS, fields);
538 Ok(())
539 }
540
541 /// Removes all of extension `T`'s declared fields from this annotation.
542 ///
543 /// # Errors
544 ///
545 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
546 /// [`ExtensionScope::Annotation`].
547 pub fn remove_extension<T: SigmfExtension>(&mut self) -> Result<(), SigmfError> {
548 ensure_scope::<T>(ExtensionScope::Annotation)?;
549 replace_extension_fields(self.extension_fields_mut(), T::FIELDS, Map::new());
550 Ok(())
551 }
552}
553
554impl crate::collection::CollectionMetadata {
555 /// Reads extension `T` from this `collection` object's fields.
556 ///
557 /// Only the keys listed in [`SigmfExtension::FIELDS`] are considered;
558 /// absent keys are simply omitted before deserialization.
559 ///
560 /// # Errors
561 ///
562 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
563 /// [`ExtensionScope::Collection`] or the fields do not deserialize into `T`.
564 pub fn extension<T: SigmfExtension>(&self) -> Result<T, SigmfError> {
565 get_extension_fields::<T>(ExtensionScope::Collection, self.extra_fields())
566 }
567
568 /// Writes extension `T` into this `collection` object.
569 ///
570 /// All of `T`'s declared [`FIELDS`](SigmfExtension::FIELDS) are first
571 /// removed, then the serialized fields are inserted, so fields omitted by
572 /// serialization are cleared. `core:extensions` is not modified.
573 ///
574 /// # Errors
575 ///
576 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
577 /// [`ExtensionScope::Collection`], `extension` does not serialize to a JSON
578 /// object, or it serializes a key not listed in `FIELDS`; returns
579 /// [`SigmfError::Serde`] if serialization fails. Nothing is changed on
580 /// error.
581 pub fn set_extension<T: SigmfExtension>(&mut self, extension: &T) -> Result<(), SigmfError> {
582 let fields = extension_fields::<T>(ExtensionScope::Collection, extension)?;
583 replace_extension_fields(self.extension_fields_mut(), T::FIELDS, fields);
584 Ok(())
585 }
586
587 /// Removes all of extension `T`'s declared fields from this `collection` object.
588 ///
589 /// # Errors
590 ///
591 /// Returns [`SigmfError::InvalidMetadata`] if `T`'s scope is not
592 /// [`ExtensionScope::Collection`].
593 pub fn remove_extension<T: SigmfExtension>(&mut self) -> Result<(), SigmfError> {
594 ensure_scope::<T>(ExtensionScope::Collection)?;
595 replace_extension_fields(self.extension_fields_mut(), T::FIELDS, Map::new());
596 Ok(())
597 }
598}
599
600fn get_extension_fields<T: SigmfExtension>(
601 scope: ExtensionScope,
602 source: &Map<String, Value>,
603) -> Result<T, SigmfError> {
604 ensure_scope::<T>(scope)?;
605 let fields = T::FIELDS
606 .iter()
607 .filter_map(|field| {
608 source
609 .get(*field)
610 .map(|value| ((*field).to_string(), value.clone()))
611 })
612 .collect();
613 T::from_extension_value(Value::Object(fields))
614}
615
616fn extension_fields<T: SigmfExtension>(
617 scope: ExtensionScope,
618 extension: &T,
619) -> Result<Map<String, Value>, SigmfError> {
620 ensure_scope::<T>(scope)?;
621 let Value::Object(fields) = extension.to_extension_value()? else {
622 return Err(SigmfError::InvalidMetadata(
623 "extension must serialize as an object".into(),
624 ));
625 };
626 if fields
627 .keys()
628 .any(|field| !T::FIELDS.contains(&field.as_str()))
629 {
630 return Err(SigmfError::InvalidMetadata(
631 "extension serialization produced an undeclared field".into(),
632 ));
633 }
634 Ok(fields)
635}
636
637fn ensure_scope<T: SigmfExtension>(scope: ExtensionScope) -> Result<(), SigmfError> {
638 if T::SCOPE != scope {
639 return Err(SigmfError::InvalidMetadata(
640 "extension scope does not match the metadata object".into(),
641 ));
642 }
643 Ok(())
644}
645
646fn replace_extension_fields(
647 destination: &mut Map<String, Value>,
648 declared_fields: &[&str],
649 replacement: Map<String, Value>,
650) {
651 for field in declared_fields {
652 destination.remove(*field);
653 }
654 destination.extend(replacement);
655}