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