oxigdal_security/lineage/
metadata.rs1use crate::error::{Result, SecurityError};
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct ProvenanceMetadata {
11 pub entity_uri: String,
13 pub activity: Option<ActivityMetadata>,
15 pub derived_from: Vec<String>,
17 pub attributed_to: Option<String>,
19 pub generated_at: Option<DateTime<Utc>>,
21 pub invalidated_at: Option<DateTime<Utc>>,
23 pub attributes: HashMap<String, String>,
25}
26
27impl ProvenanceMetadata {
28 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 pub fn with_activity(mut self, activity: ActivityMetadata) -> Self {
43 self.activity = Some(activity);
44 self
45 }
46
47 pub fn add_derived_from(mut self, entity_uri: String) -> Self {
49 self.derived_from.push(entity_uri);
50 self
51 }
52
53 pub fn with_attribution(mut self, agent_uri: String) -> Self {
55 self.attributed_to = Some(agent_uri);
56 self
57 }
58
59 pub fn with_attribute(mut self, key: String, value: String) -> Self {
61 self.attributes.insert(key, value);
62 self
63 }
64
65 pub fn to_json(&self) -> Result<String> {
67 serde_json::to_string(self).map_err(SecurityError::from)
68 }
69
70 pub fn from_json(json: &str) -> Result<Self> {
72 serde_json::from_str(json).map_err(SecurityError::from)
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct ActivityMetadata {
79 pub uri: String,
81 pub activity_type: String,
83 pub started_at: DateTime<Utc>,
85 pub ended_at: Option<DateTime<Utc>>,
87 pub associated_with: Option<String>,
89 pub used: Vec<String>,
91 pub parameters: HashMap<String, String>,
93}
94
95impl ActivityMetadata {
96 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 pub fn end(mut self) -> Self {
111 self.ended_at = Some(Utc::now());
112 self
113 }
114
115 pub fn add_used(mut self, entity_uri: String) -> Self {
117 self.used.push(entity_uri);
118 self
119 }
120
121 pub fn with_agent(mut self, agent_uri: String) -> Self {
123 self.associated_with = Some(agent_uri);
124 self
125 }
126
127 pub fn with_parameter(mut self, key: String, value: String) -> Self {
129 self.parameters.insert(key, value);
130 self
131 }
132}
133
134#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct AgentMetadata {
137 pub uri: String,
139 pub agent_type: AgentType,
141 pub name: String,
143 pub acted_on_behalf_of: Option<String>,
145 pub attributes: HashMap<String, String>,
147}
148
149impl AgentMetadata {
150 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 pub fn with_delegation(mut self, delegator_uri: String) -> Self {
163 self.acted_on_behalf_of = Some(delegator_uri);
164 self
165 }
166
167 pub fn with_attribute(mut self, key: String, value: String) -> Self {
169 self.attributes.insert(key, value);
170 self
171 }
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
176pub enum AgentType {
177 Person,
179 SoftwareAgent,
181 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}