use super::policy::{AnalysisPolicy, Fingerprint, ProfileId};
use std::collections::BTreeMap;
use std::fmt;
use std::path::{Path, PathBuf};
#[derive(Clone, PartialEq, Eq, Debug, Default)]
#[non_exhaustive]
pub enum SourceRef {
#[default]
None,
Path(PathBuf),
Url(Box<str>),
Memory,
}
impl SourceRef {
#[must_use]
pub fn path(path: impl Into<PathBuf>) -> Self {
Self::Path(path.into())
}
#[must_use]
pub fn as_path(&self) -> Option<&Path> {
match self {
Self::Path(path) => Some(path),
_ => None,
}
}
}
impl fmt::Display for SourceRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => f.write_str("(none)"),
Self::Path(path) => write!(f, "{}", path.display()),
Self::Url(url) => f.write_str(url),
Self::Memory => f.write_str("(memory)"),
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct DictionaryVersion(Box<str>);
impl DictionaryVersion {
#[must_use]
pub fn new(version: impl Into<Box<str>>) -> Self {
Self(version.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AlgorithmId {
name: Box<str>,
version: Box<str>,
}
impl AlgorithmId {
#[must_use]
pub fn new(name: impl Into<Box<str>>, version: impl Into<Box<str>>) -> Self {
Self {
name: name.into(),
version: version.into(),
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn version(&self) -> &str {
&self.version
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum ParameterValue {
Boolean(bool),
Integer(i64),
Float(u64),
Text(Box<str>),
}
impl ParameterValue {
#[must_use]
pub fn finite_float(value: f64) -> Option<Self> {
value.is_finite().then_some(Self::Float(value.to_bits()))
}
pub(crate) fn serialised(&self) -> String {
match self {
Self::Boolean(value) => format!("bool:{value}"),
Self::Integer(value) => format!("int:{value}"),
Self::Float(bits) => format!("float:{bits:016x}"),
Self::Text(value) => format!("text:{value}"),
}
}
}
pub type AnalysisParameters = BTreeMap<Box<str>, ParameterValue>;
#[derive(Clone, Debug)]
pub struct Provenance {
pub molframe_version: &'static str,
pub input_source: SourceRef,
pub input_fingerprint: Option<Fingerprint>,
pub policy: AnalysisPolicy,
pub policy_fingerprint: Fingerprint,
pub profile: Option<ProfileId>,
pub algorithm: Option<AlgorithmId>,
pub parameters: AnalysisParameters,
pub schema_version: Option<DictionaryVersion>,
pub component_version: Option<DictionaryVersion>,
pub timestamp: Option<Box<str>>,
}
impl Provenance {
#[must_use]
pub fn new(policy: &AnalysisPolicy) -> Self {
Self {
molframe_version: env!("CARGO_PKG_VERSION"),
input_source: SourceRef::None,
input_fingerprint: None,
policy: policy.clone(),
policy_fingerprint: policy.fingerprint(),
profile: policy.profile(),
algorithm: None,
parameters: BTreeMap::new(),
schema_version: None,
component_version: None,
timestamp: None,
}
}
#[must_use]
pub fn with_source(mut self, source: SourceRef) -> Self {
self.input_source = source;
self
}
#[must_use]
pub const fn with_input_fingerprint(mut self, fingerprint: Fingerprint) -> Self {
self.input_fingerprint = Some(fingerprint);
self
}
#[must_use]
pub fn with_timestamp(mut self, timestamp: impl Into<Box<str>>) -> Self {
self.timestamp = Some(timestamp.into());
self
}
#[must_use]
pub fn with_algorithm(mut self, algorithm: AlgorithmId) -> Self {
self.algorithm = Some(algorithm);
self
}
#[must_use]
pub fn with_parameter(mut self, name: impl Into<Box<str>>, value: ParameterValue) -> Self {
self.parameters.insert(name.into(), value);
self
}
#[must_use]
pub fn fingerprint(&self) -> Fingerprint {
let mut bytes = Vec::with_capacity(64);
bytes.extend_from_slice(self.molframe_version.as_bytes());
bytes.extend_from_slice(&self.policy_fingerprint.get().to_le_bytes());
if let Some(input) = self.input_fingerprint {
bytes.extend_from_slice(&input.get().to_le_bytes());
}
if let Some(version) = &self.schema_version {
bytes.extend_from_slice(version.as_str().as_bytes());
}
if let Some(version) = &self.component_version {
bytes.extend_from_slice(version.as_str().as_bytes());
}
if let Some(algorithm) = &self.algorithm {
fingerprint_field(&mut bytes, b"algorithm.name", algorithm.name());
fingerprint_field(&mut bytes, b"algorithm.version", algorithm.version());
}
for (name, value) in &self.parameters {
fingerprint_field(&mut bytes, name.as_bytes(), &value.serialised());
}
Fingerprint::of(&bytes)
}
}
fn fingerprint_field(bytes: &mut Vec<u8>, name: &[u8], value: &str) {
bytes.extend_from_slice(&(name.len() as u64).to_le_bytes());
bytes.extend_from_slice(name);
bytes.extend_from_slice(&(value.len() as u64).to_le_bytes());
bytes.extend_from_slice(value.as_bytes());
}
#[cfg(test)]
#[path = "provenance_tests.rs"]
mod tests;