molframe_core/contract/
provenance.rs1use super::policy::{AnalysisPolicy, Fingerprint, ProfileId};
12use std::collections::BTreeMap;
13use std::fmt;
14use std::path::{Path, PathBuf};
15
16#[derive(Clone, PartialEq, Eq, Debug, Default)]
18#[non_exhaustive]
19pub enum SourceRef {
20 #[default]
22 None,
23 Path(PathBuf),
25 Url(Box<str>),
27 Memory,
29}
30
31impl SourceRef {
32 #[must_use]
34 pub fn path(path: impl Into<PathBuf>) -> Self {
35 Self::Path(path.into())
36 }
37
38 #[must_use]
40 pub fn as_path(&self) -> Option<&Path> {
41 match self {
42 Self::Path(path) => Some(path),
43 _ => None,
44 }
45 }
46}
47
48impl fmt::Display for SourceRef {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::None => f.write_str("(none)"),
52 Self::Path(path) => write!(f, "{}", path.display()),
53 Self::Url(url) => f.write_str(url),
54 Self::Memory => f.write_str("(memory)"),
55 }
56 }
57}
58
59#[derive(Clone, PartialEq, Eq, Debug)]
61pub struct DictionaryVersion(Box<str>);
62
63impl DictionaryVersion {
64 #[must_use]
66 pub fn new(version: impl Into<Box<str>>) -> Self {
67 Self(version.into())
68 }
69
70 #[must_use]
72 pub fn as_str(&self) -> &str {
73 &self.0
74 }
75}
76
77#[derive(Clone, PartialEq, Eq, Debug)]
79pub struct AlgorithmId {
80 name: Box<str>,
81 version: Box<str>,
82}
83
84impl AlgorithmId {
85 #[must_use]
87 pub fn new(name: impl Into<Box<str>>, version: impl Into<Box<str>>) -> Self {
88 Self {
89 name: name.into(),
90 version: version.into(),
91 }
92 }
93
94 #[must_use]
96 pub fn name(&self) -> &str {
97 &self.name
98 }
99
100 #[must_use]
102 pub fn version(&self) -> &str {
103 &self.version
104 }
105}
106
107#[derive(Clone, PartialEq, Eq, Debug)]
109pub enum ParameterValue {
110 Boolean(bool),
112 Integer(i64),
114 Float(u64),
116 Text(Box<str>),
118}
119
120impl ParameterValue {
121 #[must_use]
123 pub fn finite_float(value: f64) -> Option<Self> {
124 value.is_finite().then_some(Self::Float(value.to_bits()))
125 }
126
127 pub(crate) fn serialised(&self) -> String {
128 match self {
129 Self::Boolean(value) => format!("bool:{value}"),
130 Self::Integer(value) => format!("int:{value}"),
131 Self::Float(bits) => format!("float:{bits:016x}"),
132 Self::Text(value) => format!("text:{value}"),
133 }
134 }
135}
136
137pub type AnalysisParameters = BTreeMap<Box<str>, ParameterValue>;
139
140#[derive(Clone, Debug)]
154pub struct Provenance {
155 pub molframe_version: &'static str,
157 pub input_source: SourceRef,
159 pub input_fingerprint: Option<Fingerprint>,
161 pub policy: AnalysisPolicy,
163 pub policy_fingerprint: Fingerprint,
165 pub profile: Option<ProfileId>,
167 pub algorithm: Option<AlgorithmId>,
169 pub parameters: AnalysisParameters,
171 pub schema_version: Option<DictionaryVersion>,
173 pub component_version: Option<DictionaryVersion>,
175 pub timestamp: Option<Box<str>>,
179}
180
181impl Provenance {
182 #[must_use]
184 pub fn new(policy: &AnalysisPolicy) -> Self {
185 Self {
186 molframe_version: env!("CARGO_PKG_VERSION"),
187 input_source: SourceRef::None,
188 input_fingerprint: None,
189 policy: policy.clone(),
190 policy_fingerprint: policy.fingerprint(),
191 profile: policy.profile(),
192 algorithm: None,
193 parameters: BTreeMap::new(),
194 schema_version: None,
195 component_version: None,
196 timestamp: None,
197 }
198 }
199
200 #[must_use]
202 pub fn with_source(mut self, source: SourceRef) -> Self {
203 self.input_source = source;
204 self
205 }
206
207 #[must_use]
209 pub const fn with_input_fingerprint(mut self, fingerprint: Fingerprint) -> Self {
210 self.input_fingerprint = Some(fingerprint);
211 self
212 }
213
214 #[must_use]
216 pub fn with_timestamp(mut self, timestamp: impl Into<Box<str>>) -> Self {
217 self.timestamp = Some(timestamp.into());
218 self
219 }
220
221 #[must_use]
223 pub fn with_algorithm(mut self, algorithm: AlgorithmId) -> Self {
224 self.algorithm = Some(algorithm);
225 self
226 }
227
228 #[must_use]
230 pub fn with_parameter(mut self, name: impl Into<Box<str>>, value: ParameterValue) -> Self {
231 self.parameters.insert(name.into(), value);
232 self
233 }
234
235 #[must_use]
241 pub fn fingerprint(&self) -> Fingerprint {
242 let mut bytes = Vec::with_capacity(64);
243 bytes.extend_from_slice(self.molframe_version.as_bytes());
244 bytes.extend_from_slice(&self.policy_fingerprint.get().to_le_bytes());
245 if let Some(input) = self.input_fingerprint {
246 bytes.extend_from_slice(&input.get().to_le_bytes());
247 }
248 if let Some(version) = &self.schema_version {
249 bytes.extend_from_slice(version.as_str().as_bytes());
250 }
251 if let Some(version) = &self.component_version {
252 bytes.extend_from_slice(version.as_str().as_bytes());
253 }
254 if let Some(algorithm) = &self.algorithm {
255 fingerprint_field(&mut bytes, b"algorithm.name", algorithm.name());
256 fingerprint_field(&mut bytes, b"algorithm.version", algorithm.version());
257 }
258 for (name, value) in &self.parameters {
259 fingerprint_field(&mut bytes, name.as_bytes(), &value.serialised());
260 }
261 Fingerprint::of(&bytes)
262 }
263}
264
265fn fingerprint_field(bytes: &mut Vec<u8>, name: &[u8], value: &str) {
266 bytes.extend_from_slice(&(name.len() as u64).to_le_bytes());
267 bytes.extend_from_slice(name);
268 bytes.extend_from_slice(&(value.len() as u64).to_le_bytes());
269 bytes.extend_from_slice(value.as_bytes());
270}
271
272#[cfg(test)]
273#[path = "provenance_tests.rs"]
274mod tests;