scientific_workflow/
rng_record.rs1use serde::{Deserialize, Serialize};
9use serde_json::{Map, Value};
10use thiserror::Error;
11
12pub const RNG_RECORDS_METADATA_KEY: &str = "rng_records";
14
15#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
24#[serde(deny_unknown_fields)]
25pub struct RngRecord {
26 namespace: String,
27 method: String,
28 version: String,
29 key_encoding: String,
30 key: String,
31 #[serde(default, skip_serializing_if = "Map::is_empty")]
32 parameters: Map<String, Value>,
33}
34
35impl RngRecord {
36 pub fn new(
38 namespace: impl Into<String>,
39 method: impl Into<String>,
40 version: impl Into<String>,
41 key_encoding: impl Into<String>,
42 key: impl Into<String>,
43 parameters: Option<Map<String, Value>>,
44 ) -> Result<Self, RngRecordError> {
45 let record = Self {
46 namespace: namespace.into(),
47 method: method.into(),
48 version: version.into(),
49 key_encoding: key_encoding.into(),
50 key: key.into(),
51 parameters: parameters.unwrap_or_default(),
52 };
53 record.validate()?;
54 Ok(record)
55 }
56
57 pub fn namespace(&self) -> &str {
59 &self.namespace
60 }
61
62 pub fn method(&self) -> &str {
64 &self.method
65 }
66
67 pub fn version(&self) -> &str {
69 &self.version
70 }
71
72 pub fn key_encoding(&self) -> &str {
74 &self.key_encoding
75 }
76
77 pub fn key(&self) -> &str {
79 &self.key
80 }
81
82 pub const fn parameters(&self) -> &Map<String, Value> {
84 &self.parameters
85 }
86
87 pub fn insert_into_metadata(
92 &self,
93 metadata: &mut Map<String, Value>,
94 ) -> Result<(), RngRecordError> {
95 self.validate()?;
96 let records = metadata
97 .entry(RNG_RECORDS_METADATA_KEY.to_owned())
98 .or_insert_with(|| Value::Object(Map::new()))
99 .as_object_mut()
100 .ok_or(RngRecordError::InvalidMetadataShape)?;
101 if records.contains_key(&self.namespace) {
102 return Err(RngRecordError::DuplicateNamespace {
103 namespace: self.namespace.clone(),
104 });
105 }
106 records.insert(
107 self.namespace.clone(),
108 serde_json::to_value(self).expect("RNG records contain only JSON-compatible values"),
109 );
110 Ok(())
111 }
112
113 pub fn from_metadata(
115 metadata: &Map<String, Value>,
116 namespace: &str,
117 ) -> Result<Option<Self>, RngRecordError> {
118 let Some(value) = metadata.get(RNG_RECORDS_METADATA_KEY) else {
119 return Ok(None);
120 };
121 let records = value
122 .as_object()
123 .ok_or(RngRecordError::InvalidMetadataShape)?;
124 let Some(value) = records.get(namespace) else {
125 return Ok(None);
126 };
127 let record: Self = serde_json::from_value(value.clone()).map_err(|source| {
128 RngRecordError::InvalidStoredRecord {
129 namespace: namespace.to_owned(),
130 source,
131 }
132 })?;
133 record.validate()?;
134 if record.namespace != namespace {
135 return Err(RngRecordError::NamespaceMismatch {
136 index: namespace.to_owned(),
137 record: record.namespace,
138 });
139 }
140 Ok(Some(record))
141 }
142
143 fn validate(&self) -> Result<(), RngRecordError> {
144 for (field, value) in [
145 ("namespace", self.namespace.as_str()),
146 ("method", self.method.as_str()),
147 ("version", self.version.as_str()),
148 ("key_encoding", self.key_encoding.as_str()),
149 ("key", self.key.as_str()),
150 ] {
151 if value.trim().is_empty() {
152 return Err(RngRecordError::EmptyField { field });
153 }
154 }
155 Ok(())
156 }
157}
158
159#[derive(Debug, Error)]
161#[non_exhaustive]
162pub enum RngRecordError {
163 #[error("RNG record field `{field}` must not be empty")]
165 EmptyField {
166 field: &'static str,
168 },
169 #[error("RNG namespace `{namespace}` is recorded more than once")]
171 DuplicateNamespace {
172 namespace: String,
174 },
175 #[error("user metadata `{RNG_RECORDS_METADATA_KEY}` entry must be an object")]
177 InvalidMetadataShape,
178 #[error("invalid RNG record for namespace `{namespace}`")]
180 InvalidStoredRecord {
181 namespace: String,
183 #[source]
185 source: serde_json::Error,
186 },
187 #[error("RNG metadata index `{index}` contains record namespace `{record}`")]
189 NamespaceMismatch {
190 index: String,
192 record: String,
194 },
195}