Skip to main content

sigmf/
recording.rs

1//! References from a SigMF collection to its member recordings.
2//!
3//! A SigMF Collection (`.sigmf-collection`, see [`crate::collection`]) lists
4//! the recordings it groups in its `core:streams` array. Each entry is modeled
5//! by [`RecordingRef`]: an object holding the recording's base `name` and the
6//! SHA-512 `hash` of its metadata file. Unknown keys on an entry are preserved as extension fields.
7//!
8//! Older collections encode each entry as a two-element `[name, hash]` array;
9//! such legacy tuples are accepted when a collection is parsed, and the
10//! original form is remembered so that
11//! [`Collection::to_vec_preserving_stream_forms`] can write it back unchanged.
12//!
13//! [`Collection::to_vec_preserving_stream_forms`]: crate::collection::Collection::to_vec_preserving_stream_forms
14
15use serde_derive::{Deserialize, Serialize};
16use serde_json::{Map, Value};
17
18/// One entry of a SigMF collection's `core:streams` array.
19///
20/// Identifies a recording by its base `name` (the metadata file name without
21/// the `.sigmf-meta` extension) and the SHA-512 `hash` of that metadata file,
22/// as a lowercase hexadecimal string.
23#[derive(Debug, Clone, Deserialize, Serialize)]
24pub struct RecordingRef {
25    name: String,
26    hash: String,
27    #[serde(flatten)]
28    extra_fields: Map<String, Value>,
29    #[serde(skip)]
30    legacy_tuple: bool,
31}
32
33impl RecordingRef {
34    /// Creates a reference to the recording `name` whose metadata file has
35    /// SHA-512 digest `hash`.
36    ///
37    /// Neither argument is validated. The reference has no extension fields
38    /// and serializes as an object (not a legacy tuple).
39    ///
40    /// # Examples
41    ///
42    /// ```rust
43    /// use sigmf::recording::RecordingRef;
44    ///
45    /// let reference = RecordingRef::new("example", "0123abcd");
46    /// assert_eq!(reference.get_name(), "example");
47    /// assert_eq!(reference.get_hash(), "0123abcd");
48    ///
49    /// let json = serde_json::to_value(&reference).unwrap();
50    /// assert_eq!(json, serde_json::json!({"name": "example", "hash": "0123abcd"}));
51    /// ```
52    pub fn new(name: &str, hash: &str) -> Self {
53        RecordingRef {
54            name: name.to_string(),
55            hash: hash.to_string(),
56            extra_fields: Map::new(),
57            legacy_tuple: false,
58        }
59    }
60
61    /// Returns the recording's base name (the `name` key).
62    pub fn get_name(&self) -> &str {
63        &self.name
64    }
65
66    /// Returns the SHA-512 digest of the recording's metadata file (the
67    /// `hash` key).
68    pub fn get_hash(&self) -> &str {
69        &self.hash
70    }
71
72    /// Returns the extension fields preserved on this entry, i.e. every key
73    /// other than `name` and `hash`.
74    pub fn extra_fields(&self) -> &Map<String, Value> {
75        &self.extra_fields
76    }
77
78    /// Inserts or replaces an extension field on this entry.
79    ///
80    /// Returns the previous value stored under `key`, if any. Note that a
81    /// reference parsed from a legacy `[name, hash]` tuple is written back as
82    /// a tuple by [`Collection::to_vec_preserving_stream_forms`], which
83    /// drops any extension fields added to it.
84    ///
85    /// # Errors
86    ///
87    /// Returns [`crate::SigmfError::InvalidMetadata`] if `key` is `name` or
88    /// `hash`; use [`RecordingRef::set_name`] or [`RecordingRef::set_hash`]
89    /// instead.
90    ///
91    /// [`Collection::to_vec_preserving_stream_forms`]: crate::collection::Collection::to_vec_preserving_stream_forms
92    pub fn insert_extra(
93        &mut self,
94        key: impl Into<String>,
95        value: Value,
96    ) -> Result<Option<Value>, crate::SigmfError> {
97        let key = key.into();
98        if matches!(key.as_str(), "name" | "hash") {
99            return Err(crate::SigmfError::InvalidMetadata(format!(
100                "cannot insert known field {key:?} as an extension"
101            )));
102        }
103        Ok(self.extra_fields.insert(key, value))
104    }
105
106    /// Sets the recording's base name (the `name` key).
107    pub fn set_name(&mut self, name: String) {
108        self.name = name;
109    }
110
111    /// Sets the SHA-512 digest of the recording's metadata file (the `hash`
112    /// key).
113    pub fn set_hash(&mut self, hash: String) {
114        self.hash = hash;
115    }
116
117    pub(crate) fn from_legacy_tuple(name: &str, hash: &str) -> Self {
118        let mut reference = Self::new(name, hash);
119        reference.legacy_tuple = true;
120        reference
121    }
122
123    pub(crate) fn was_legacy_tuple(&self) -> bool {
124        self.legacy_tuple
125    }
126}