Skip to main content

oxigdal_security/lineage/
metadata.rs

1//! Provenance metadata management.
2
3use crate::error::{Result, SecurityError};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8/// PROV-O compliant provenance metadata.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ProvenanceMetadata {
11    /// Entity URI.
12    pub entity_uri: String,
13    /// Activity that generated the entity.
14    pub activity: Option<ActivityMetadata>,
15    /// Entities used to derive this entity.
16    pub derived_from: Vec<String>,
17    /// Agent responsible.
18    pub attributed_to: Option<String>,
19    /// Generation time.
20    pub generated_at: Option<DateTime<Utc>>,
21    /// Invalidation time.
22    pub invalidated_at: Option<DateTime<Utc>>,
23    /// Additional attributes.
24    pub attributes: HashMap<String, String>,
25}
26
27impl ProvenanceMetadata {
28    /// Create new provenance metadata.
29    pub fn new(entity_uri: String) -> Self {
30        Self {
31            entity_uri,
32            activity: None,
33            derived_from: Vec::new(),
34            attributed_to: None,
35            generated_at: Some(Utc::now()),
36            invalidated_at: None,
37            attributes: HashMap::new(),
38        }
39    }
40
41    /// Set activity.
42    pub fn with_activity(mut self, activity: ActivityMetadata) -> Self {
43        self.activity = Some(activity);
44        self
45    }
46
47    /// Add derived from entity.
48    pub fn add_derived_from(mut self, entity_uri: String) -> Self {
49        self.derived_from.push(entity_uri);
50        self
51    }
52
53    /// Set attributed to.
54    pub fn with_attribution(mut self, agent_uri: String) -> Self {
55        self.attributed_to = Some(agent_uri);
56        self
57    }
58
59    /// Add attribute.
60    pub fn with_attribute(mut self, key: String, value: String) -> Self {
61        self.attributes.insert(key, value);
62        self
63    }
64
65    /// Serialize to JSON.
66    pub fn to_json(&self) -> Result<String> {
67        serde_json::to_string(self).map_err(SecurityError::from)
68    }
69
70    /// Deserialize from JSON.
71    pub fn from_json(json: &str) -> Result<Self> {
72        serde_json::from_str(json).map_err(SecurityError::from)
73    }
74}
75
76/// Activity metadata.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct ActivityMetadata {
79    /// Activity URI.
80    pub uri: String,
81    /// Activity type.
82    pub activity_type: String,
83    /// Start time.
84    pub started_at: DateTime<Utc>,
85    /// End time.
86    pub ended_at: Option<DateTime<Utc>>,
87    /// Associated agent.
88    pub associated_with: Option<String>,
89    /// Input entities.
90    pub used: Vec<String>,
91    /// Parameters.
92    pub parameters: HashMap<String, String>,
93}
94
95impl ActivityMetadata {
96    /// Create new activity metadata.
97    pub fn new(uri: String, activity_type: String) -> Self {
98        Self {
99            uri,
100            activity_type,
101            started_at: Utc::now(),
102            ended_at: None,
103            associated_with: None,
104            used: Vec::new(),
105            parameters: HashMap::new(),
106        }
107    }
108
109    /// Mark activity as ended.
110    pub fn end(mut self) -> Self {
111        self.ended_at = Some(Utc::now());
112        self
113    }
114
115    /// Add used entity.
116    pub fn add_used(mut self, entity_uri: String) -> Self {
117        self.used.push(entity_uri);
118        self
119    }
120
121    /// Set associated agent.
122    pub fn with_agent(mut self, agent_uri: String) -> Self {
123        self.associated_with = Some(agent_uri);
124        self
125    }
126
127    /// Add parameter.
128    pub fn with_parameter(mut self, key: String, value: String) -> Self {
129        self.parameters.insert(key, value);
130        self
131    }
132}
133
134/// Agent metadata.
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct AgentMetadata {
137    /// Agent URI.
138    pub uri: String,
139    /// Agent type.
140    pub agent_type: AgentType,
141    /// Agent name.
142    pub name: String,
143    /// On behalf of (for delegation).
144    pub acted_on_behalf_of: Option<String>,
145    /// Additional attributes.
146    pub attributes: HashMap<String, String>,
147}
148
149impl AgentMetadata {
150    /// Create new agent metadata.
151    pub fn new(uri: String, agent_type: AgentType, name: String) -> Self {
152        Self {
153            uri,
154            agent_type,
155            name,
156            acted_on_behalf_of: None,
157            attributes: HashMap::new(),
158        }
159    }
160
161    /// Set delegation.
162    pub fn with_delegation(mut self, delegator_uri: String) -> Self {
163        self.acted_on_behalf_of = Some(delegator_uri);
164        self
165    }
166
167    /// Add attribute.
168    pub fn with_attribute(mut self, key: String, value: String) -> Self {
169        self.attributes.insert(key, value);
170        self
171    }
172}
173
174/// Agent type.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176pub enum AgentType {
177    /// Person.
178    Person,
179    /// Software agent.
180    SoftwareAgent,
181    /// Organization.
182    Organization,
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188
189    #[test]
190    fn test_provenance_metadata() {
191        let prov = ProvenanceMetadata::new("dataset://123".to_string())
192            .add_derived_from("dataset://source".to_string())
193            .with_attribution("agent://user-1".to_string())
194            .with_attribute("format".to_string(), "GeoTIFF".to_string());
195
196        assert_eq!(prov.entity_uri, "dataset://123");
197        assert_eq!(prov.derived_from.len(), 1);
198        assert_eq!(prov.attributed_to, Some("agent://user-1".to_string()));
199    }
200
201    #[test]
202    fn test_activity_metadata() {
203        let activity = ActivityMetadata::new(
204            "activity://transform-1".to_string(),
205            "reproject".to_string(),
206        )
207        .add_used("dataset://input".to_string())
208        .with_agent("agent://user-1".to_string())
209        .with_parameter("target_crs".to_string(), "EPSG:4326".to_string())
210        .end();
211
212        assert_eq!(activity.activity_type, "reproject");
213        assert_eq!(activity.used.len(), 1);
214        assert!(activity.ended_at.is_some());
215    }
216
217    #[test]
218    fn test_agent_metadata() {
219        let agent = AgentMetadata::new(
220            "agent://service-1".to_string(),
221            AgentType::SoftwareAgent,
222            "Processing Service".to_string(),
223        )
224        .with_delegation("agent://admin".to_string());
225
226        assert_eq!(agent.agent_type, AgentType::SoftwareAgent);
227        assert_eq!(agent.acted_on_behalf_of, Some("agent://admin".to_string()));
228    }
229
230    #[test]
231    fn test_provenance_serialization() {
232        let prov = ProvenanceMetadata::new("dataset://123".to_string());
233        let json = prov.to_json().expect("Serialization failed");
234        let deserialized = ProvenanceMetadata::from_json(&json).expect("Deserialization failed");
235
236        assert_eq!(deserialized.entity_uri, "dataset://123");
237    }
238}