use crate::error::{Result, SecurityError};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvenanceMetadata {
pub entity_uri: String,
pub activity: Option<ActivityMetadata>,
pub derived_from: Vec<String>,
pub attributed_to: Option<String>,
pub generated_at: Option<DateTime<Utc>>,
pub invalidated_at: Option<DateTime<Utc>>,
pub attributes: HashMap<String, String>,
}
impl ProvenanceMetadata {
pub fn new(entity_uri: String) -> Self {
Self {
entity_uri,
activity: None,
derived_from: Vec::new(),
attributed_to: None,
generated_at: Some(Utc::now()),
invalidated_at: None,
attributes: HashMap::new(),
}
}
pub fn with_activity(mut self, activity: ActivityMetadata) -> Self {
self.activity = Some(activity);
self
}
pub fn add_derived_from(mut self, entity_uri: String) -> Self {
self.derived_from.push(entity_uri);
self
}
pub fn with_attribution(mut self, agent_uri: String) -> Self {
self.attributed_to = Some(agent_uri);
self
}
pub fn with_attribute(mut self, key: String, value: String) -> Self {
self.attributes.insert(key, value);
self
}
pub fn to_json(&self) -> Result<String> {
serde_json::to_string(self).map_err(SecurityError::from)
}
pub fn from_json(json: &str) -> Result<Self> {
serde_json::from_str(json).map_err(SecurityError::from)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivityMetadata {
pub uri: String,
pub activity_type: String,
pub started_at: DateTime<Utc>,
pub ended_at: Option<DateTime<Utc>>,
pub associated_with: Option<String>,
pub used: Vec<String>,
pub parameters: HashMap<String, String>,
}
impl ActivityMetadata {
pub fn new(uri: String, activity_type: String) -> Self {
Self {
uri,
activity_type,
started_at: Utc::now(),
ended_at: None,
associated_with: None,
used: Vec::new(),
parameters: HashMap::new(),
}
}
pub fn end(mut self) -> Self {
self.ended_at = Some(Utc::now());
self
}
pub fn add_used(mut self, entity_uri: String) -> Self {
self.used.push(entity_uri);
self
}
pub fn with_agent(mut self, agent_uri: String) -> Self {
self.associated_with = Some(agent_uri);
self
}
pub fn with_parameter(mut self, key: String, value: String) -> Self {
self.parameters.insert(key, value);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentMetadata {
pub uri: String,
pub agent_type: AgentType,
pub name: String,
pub acted_on_behalf_of: Option<String>,
pub attributes: HashMap<String, String>,
}
impl AgentMetadata {
pub fn new(uri: String, agent_type: AgentType, name: String) -> Self {
Self {
uri,
agent_type,
name,
acted_on_behalf_of: None,
attributes: HashMap::new(),
}
}
pub fn with_delegation(mut self, delegator_uri: String) -> Self {
self.acted_on_behalf_of = Some(delegator_uri);
self
}
pub fn with_attribute(mut self, key: String, value: String) -> Self {
self.attributes.insert(key, value);
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AgentType {
Person,
SoftwareAgent,
Organization,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_provenance_metadata() {
let prov = ProvenanceMetadata::new("dataset://123".to_string())
.add_derived_from("dataset://source".to_string())
.with_attribution("agent://user-1".to_string())
.with_attribute("format".to_string(), "GeoTIFF".to_string());
assert_eq!(prov.entity_uri, "dataset://123");
assert_eq!(prov.derived_from.len(), 1);
assert_eq!(prov.attributed_to, Some("agent://user-1".to_string()));
}
#[test]
fn test_activity_metadata() {
let activity = ActivityMetadata::new(
"activity://transform-1".to_string(),
"reproject".to_string(),
)
.add_used("dataset://input".to_string())
.with_agent("agent://user-1".to_string())
.with_parameter("target_crs".to_string(), "EPSG:4326".to_string())
.end();
assert_eq!(activity.activity_type, "reproject");
assert_eq!(activity.used.len(), 1);
assert!(activity.ended_at.is_some());
}
#[test]
fn test_agent_metadata() {
let agent = AgentMetadata::new(
"agent://service-1".to_string(),
AgentType::SoftwareAgent,
"Processing Service".to_string(),
)
.with_delegation("agent://admin".to_string());
assert_eq!(agent.agent_type, AgentType::SoftwareAgent);
assert_eq!(agent.acted_on_behalf_of, Some("agent://admin".to_string()));
}
#[test]
fn test_provenance_serialization() {
let prov = ProvenanceMetadata::new("dataset://123".to_string());
let json = prov.to_json().expect("Serialization failed");
let deserialized = ProvenanceMetadata::from_json(&json).expect("Deserialization failed");
assert_eq!(deserialized.entity_uri, "dataset://123");
}
}