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}