1#![allow(dead_code)]
7#![allow(missing_docs)]
8
9use crate::error::{IoError, Result};
10use chrono::{DateTime, Utc};
11use indexmap::{indexmap, IndexMap};
12use serde::{Deserialize, Serialize};
13use sha2::{Digest, Sha256};
14use std::collections::{HashMap, HashSet};
15use std::fmt;
16use std::path::Path;
17use std::sync::{Arc, RwLock};
18
19pub mod standard_keys {
21 pub const TITLE: &str = "title";
22 pub const AUTHOR: &str = "author";
23 pub const DESCRIPTION: &str = "description";
24 pub const CREATION_DATE: &str = "creation_date";
25 pub const MODIFICATION_DATE: &str = "modification_date";
26 pub const VERSION: &str = "version";
27 pub const LICENSE: &str = "license";
28 pub const KEYWORDS: &str = "keywords";
29 pub const UNITS: &str = "units";
30 pub const DIMENSIONS: &str = "dimensions";
31 pub const COORDINATE_SYSTEM: &str = "coordinate_system";
32 pub const INSTRUMENT: &str = "instrument";
33 pub const EXPERIMENT: &str = "experiment";
34 pub const PROCESSING_HISTORY: &str = "processing_history";
35 pub const REFERENCES: &str = "references";
36 pub const PROVENANCE: &str = "provenance";
37}
38
39#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum MetadataValue {
43 String(String),
45 Integer(i64),
47 Float(f64),
49 Boolean(bool),
51 DateTime(DateTime<Utc>),
53 Array(Vec<MetadataValue>),
55 Object(IndexMap<String, MetadataValue>),
57 Binary(Vec<u8>),
59}
60
61impl fmt::Display for MetadataValue {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 Self::String(s) => write!(f, "{}", s),
65 Self::Integer(i) => write!(f, "{}", i),
66 Self::Float(fl) => write!(f, "{}", fl),
67 Self::Boolean(b) => write!(f, "{}", b),
68 Self::DateTime(dt) => write!(f, "{}", dt.to_rfc3339()),
69 Self::Array(arr) => write!(
70 f,
71 "[{}]",
72 arr.iter()
73 .map(|v| v.to_string())
74 .collect::<Vec<_>>()
75 .join(", ")
76 ),
77 Self::Object(_) => write!(f, "[object]"),
78 Self::Binary(b) => write!(f, "[binary: {} bytes]", b.len()),
79 }
80 }
81}
82
83#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct Metadata {
86 data: IndexMap<String, MetadataValue>,
88 extensions: HashMap<String, IndexMap<String, MetadataValue>>,
90 schema_version: String,
92}
93
94impl Metadata {
95 pub fn new() -> Self {
97 Self {
98 data: IndexMap::new(),
99 extensions: HashMap::new(),
100 schema_version: "1.0".to_string(),
101 }
102 }
103
104 pub fn with_schema(version: &str) -> Self {
106 Self {
107 data: IndexMap::new(),
108 extensions: HashMap::new(),
109 schema_version: version.to_string(),
110 }
111 }
112
113 pub fn set(&mut self, key: impl Into<String>, value: impl Into<MetadataValue>) {
115 self.data.insert(key.into(), value.into());
116 }
117
118 pub fn get(&self, key: &str) -> Option<&MetadataValue> {
120 self.data.get(key)
121 }
122
123 pub fn get_string(&self, key: &str) -> Option<&str> {
125 match self.get(key)? {
126 MetadataValue::String(s) => Some(s),
127 _ => None,
128 }
129 }
130
131 pub fn get_integer(&self, key: &str) -> Option<i64> {
133 match self.get(key)? {
134 MetadataValue::Integer(i) => Some(*i),
135 _ => None,
136 }
137 }
138
139 pub fn get_float(&self, key: &str) -> Option<f64> {
141 match self.get(key)? {
142 MetadataValue::Float(f) => Some(*f),
143 MetadataValue::Integer(i) => Some(*i as f64),
144 _ => None,
145 }
146 }
147
148 pub fn set_extension(
150 &mut self,
151 format: &str,
152 key: impl Into<String>,
153 value: impl Into<MetadataValue>,
154 ) {
155 self.extensions
156 .entry(format.to_string())
157 .or_default()
158 .insert(key.into(), value.into());
159 }
160
161 pub fn get_extension(&self, format: &str) -> Option<&IndexMap<String, MetadataValue>> {
163 self.extensions.get(format)
164 }
165
166 pub fn merge(&mut self, other: &Metadata) {
168 for (key, value) in &other.data {
169 self.data.insert(key.clone(), value.clone());
170 }
171 for (format, ext_data) in &other.extensions {
172 let ext = self.extensions.entry(format.clone()).or_default();
173 for (key, value) in ext_data {
174 ext.insert(key.clone(), value.clone());
175 }
176 }
177 }
178
179 pub fn validate(&self, schema: &MetadataSchema) -> Result<()> {
181 schema.validate(self)
182 }
183
184 pub fn to_format(&self, format: MetadataFormat) -> Result<String> {
186 match format {
187 MetadataFormat::Json => serde_json::to_string_pretty(self)
188 .map_err(|e| IoError::SerializationError(e.to_string())),
189 MetadataFormat::Yaml => {
190 serde_yaml::to_string(self).map_err(|e| IoError::SerializationError(e.to_string()))
191 }
192 MetadataFormat::Toml => {
193 toml::to_string_pretty(self).map_err(|e| IoError::SerializationError(e.to_string()))
194 }
195 }
196 }
197
198 pub fn from_file(path: impl AsRef<Path>) -> Result<Self> {
200 let _path = path.as_ref();
201 let content = std::fs::read_to_string(_path).map_err(IoError::Io)?;
202
203 let extension = _path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
204
205 match extension {
206 "json" => serde_json::from_str(&content)
207 .map_err(|e| IoError::SerializationError(e.to_string())),
208 "yaml" | "yml" => serde_yaml::from_str(&content)
209 .map_err(|e| IoError::SerializationError(e.to_string())),
210 "toml" => {
211 toml::from_str(&content).map_err(|e| IoError::SerializationError(e.to_string()))
212 }
213 _ => Err(IoError::UnsupportedFormat(format!(
214 "Unknown metadata format: {}",
215 extension
216 ))),
217 }
218 }
219
220 pub fn to_file(&self, path: impl AsRef<Path>) -> Result<()> {
222 let path = path.as_ref();
223 let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
224
225 let format = match extension {
226 "json" => MetadataFormat::Json,
227 "yaml" | "yml" => MetadataFormat::Yaml,
228 "toml" => MetadataFormat::Toml,
229 _ => {
230 return Err(IoError::UnsupportedFormat(format!(
231 "Unknown metadata format: {}",
232 extension
233 )))
234 }
235 };
236
237 let content = self.to_format(format)?;
238 std::fs::write(path, content).map_err(IoError::Io)
239 }
240
241 pub fn add_processing_history(&mut self, entry: ProcessingHistoryEntry) -> Result<()> {
243 let history = match self.data.get_mut(standard_keys::PROCESSING_HISTORY) {
244 Some(MetadataValue::Array(arr)) => arr,
245 _ => {
246 self.data.insert(
247 standard_keys::PROCESSING_HISTORY.to_string(),
248 MetadataValue::Array(Vec::new()),
249 );
250 match self.data.get_mut(standard_keys::PROCESSING_HISTORY) {
251 Some(MetadataValue::Array(arr)) => arr,
252 _ => {
253 return Err(IoError::Other(
254 "Failed to create processing history array".to_string(),
255 ))
256 }
257 }
258 }
259 };
260
261 let entry_obj = indexmap! {
262 "timestamp".to_string() => MetadataValue::DateTime(entry.timestamp),
263 "operation".to_string() => MetadataValue::String(entry.operation),
264 "parameters".to_string() => MetadataValue::Object(entry.parameters),
265 "user".to_string() => MetadataValue::String(entry.user.unwrap_or_else(|| "unknown".to_string())),
266 };
267
268 history.push(MetadataValue::Object(entry_obj));
269 Ok(())
270 }
271
272 pub fn update_modification_date(&mut self) {
274 self.set(
275 standard_keys::MODIFICATION_DATE,
276 MetadataValue::DateTime(Utc::now()),
277 );
278 }
279}
280
281#[derive(Debug, Clone)]
283pub struct ProcessingHistoryEntry {
284 pub timestamp: DateTime<Utc>,
285 pub operation: String,
286 pub parameters: IndexMap<String, MetadataValue>,
287 pub user: Option<String>,
288}
289
290impl ProcessingHistoryEntry {
291 pub fn new(operation: impl Into<String>) -> Self {
292 Self {
293 timestamp: Utc::now(),
294 operation: operation.into(),
295 parameters: IndexMap::new(),
296 user: std::env::var("USER").ok(),
297 }
298 }
299
300 pub fn with_parameter(
301 mut self,
302 key: impl Into<String>,
303 value: impl Into<MetadataValue>,
304 ) -> Self {
305 self.parameters.insert(key.into(), value.into());
306 self
307 }
308}
309
310#[derive(Debug, Clone, Copy)]
312pub enum MetadataFormat {
313 Json,
314 Yaml,
315 Toml,
316}
317
318#[derive(Debug, Clone)]
320pub struct MetadataSchema {
321 required_fields: Vec<String>,
322 field_types: HashMap<String, MetadataFieldType>,
323 constraints: Vec<MetadataConstraint>,
324}
325
326#[derive(Debug, Clone)]
327pub enum MetadataFieldType {
328 String,
329 Integer,
330 Float,
331 Boolean,
332 DateTime,
333 Array(Box<MetadataFieldType>),
334 Object,
335}
336
337#[derive(Debug, Clone)]
338pub enum MetadataConstraint {
339 MinValue(String, f64),
340 MaxValue(String, f64),
341 Pattern(String, String),
342 OneOf(String, Vec<MetadataValue>),
343}
344
345impl Default for MetadataSchema {
346 fn default() -> Self {
347 Self::new()
348 }
349}
350
351impl MetadataSchema {
352 pub fn new() -> Self {
353 Self {
354 required_fields: Vec::new(),
355 field_types: HashMap::new(),
356 constraints: Vec::new(),
357 }
358 }
359
360 pub fn require(mut self, field: impl Into<String>) -> Self {
361 self.required_fields.push(field.into());
362 self
363 }
364
365 pub fn field_type(mut self, field: impl Into<String>, fieldtype: MetadataFieldType) -> Self {
366 self.field_types.insert(field.into(), fieldtype);
367 self
368 }
369
370 pub fn constraint(mut self, constraint: MetadataConstraint) -> Self {
371 self.constraints.push(constraint);
372 self
373 }
374
375 pub fn validate(&self, metadata: &Metadata) -> Result<()> {
376 for field in &self.required_fields {
378 if metadata.get(field).is_none() {
379 return Err(IoError::ValidationError(format!(
380 "Required field '{}' is missing",
381 field
382 )));
383 }
384 }
385
386 for (field, expected_type) in &self.field_types {
388 if let Some(value) = metadata.get(field) {
389 if !self.validate_type(value, expected_type) {
390 return Err(IoError::ValidationError(format!(
391 "Field '{}' has incorrect type",
392 field
393 )));
394 }
395 }
396 }
397
398 for constraint in &self.constraints {
400 self.apply_constraint(metadata, constraint)?;
401 }
402
403 Ok(())
404 }
405
406 #[allow(clippy::only_used_in_recursion)]
407 fn validate_type(&self, value: &MetadataValue, expected: &MetadataFieldType) -> bool {
408 match (value, expected) {
409 (MetadataValue::String(_), MetadataFieldType::String) => true,
410 (MetadataValue::Integer(_), MetadataFieldType::Integer) => true,
411 (MetadataValue::Float(_), MetadataFieldType::Float) => true,
412 (MetadataValue::Boolean(_), MetadataFieldType::Boolean) => true,
413 (MetadataValue::DateTime(_), MetadataFieldType::DateTime) => true,
414 (MetadataValue::Array(arr), MetadataFieldType::Array(elem_type)) => {
415 arr.iter().all(|v| self.validate_type(v, elem_type))
416 }
417 (MetadataValue::Object(_), MetadataFieldType::Object) => true,
418 _ => false,
419 }
420 }
421
422 fn apply_constraint(&self, metadata: &Metadata, constraint: &MetadataConstraint) -> Result<()> {
423 match constraint {
424 MetadataConstraint::MinValue(field, min) => {
425 if let Some(val) = metadata.get_float(field) {
426 if val < *min {
427 return Err(IoError::ValidationError(format!(
428 "Field '{}' value {} is less than minimum {}",
429 field, val, min
430 )));
431 }
432 }
433 }
434 MetadataConstraint::MaxValue(field, max) => {
435 if let Some(val) = metadata.get_float(field) {
436 if val > *max {
437 return Err(IoError::ValidationError(format!(
438 "Field '{}' value {} is greater than maximum {}",
439 field, val, max
440 )));
441 }
442 }
443 }
444 MetadataConstraint::Pattern(field, pattern) => {
445 if let Some(val) = metadata.get_string(field) {
446 let re = regex::Regex::new(pattern).map_err(|e| {
447 IoError::ValidationError(format!("Invalid regex pattern: {e}"))
448 })?;
449 if !re.is_match(val) {
450 return Err(IoError::ValidationError(format!(
451 "Field '{}' value '{}' does not match pattern '{}'",
452 field, val, pattern
453 )));
454 }
455 }
456 }
457 MetadataConstraint::OneOf(field, allowed) => {
458 if let Some(val) = metadata.get(field) {
459 if !allowed.contains(val) {
460 return Err(IoError::ValidationError(format!(
461 "Field '{}' value is not in allowed set",
462 field
463 )));
464 }
465 }
466 }
467 }
468 Ok(())
469 }
470}
471
472pub struct MetadataTransformer {
474 mappings: HashMap<String, String>,
475 transformations: HashMap<String, Box<dyn Fn(&MetadataValue) -> MetadataValue>>,
476}
477
478impl Default for MetadataTransformer {
479 fn default() -> Self {
480 Self::new()
481 }
482}
483
484impl MetadataTransformer {
485 pub fn new() -> Self {
486 Self {
487 mappings: HashMap::new(),
488 transformations: HashMap::new(),
489 }
490 }
491
492 pub fn map_field(mut self, from: impl Into<String>, to: impl Into<String>) -> Self {
494 self.mappings.insert(from.into(), to.into());
495 self
496 }
497
498 pub fn transform(&self, input: &Metadata) -> Metadata {
500 let mut output = Metadata::new();
501 output.schema_version = input.schema_version.clone();
502
503 for (key, value) in &input.data {
504 let new_key = self
505 .mappings
506 .get(key)
507 .cloned()
508 .unwrap_or_else(|| key.clone());
509 let new_value = if let Some(transform) = self.transformations.get(key) {
510 transform(value)
511 } else {
512 value.clone()
513 };
514 output.set(new_key, new_value);
515 }
516
517 output.extensions = input.extensions.clone();
518 output
519 }
520}
521
522pub mod schemas {
524 use super::*;
525
526 pub fn image_schema() -> MetadataSchema {
528 MetadataSchema::new()
529 .require("width")
530 .require("height")
531 .field_type("width", MetadataFieldType::Integer)
532 .field_type("height", MetadataFieldType::Integer)
533 .field_type("channels", MetadataFieldType::Integer)
534 .field_type("bit_depth", MetadataFieldType::Integer)
535 .constraint(MetadataConstraint::MinValue("width".to_string(), 1.0))
536 .constraint(MetadataConstraint::MinValue("height".to_string(), 1.0))
537 }
538
539 pub fn time_series_schema() -> MetadataSchema {
541 MetadataSchema::new()
542 .require("start_time")
543 .require("sampling_rate")
544 .field_type("start_time", MetadataFieldType::DateTime)
545 .field_type("sampling_rate", MetadataFieldType::Float)
546 .field_type("units", MetadataFieldType::String)
547 .constraint(MetadataConstraint::MinValue(
548 "sampling_rate".to_string(),
549 0.0,
550 ))
551 }
552
553 pub fn geospatial_schema() -> MetadataSchema {
555 MetadataSchema::new()
556 .require("coordinate_system")
557 .field_type("coordinate_system", MetadataFieldType::String)
558 .field_type(
559 "bounds",
560 MetadataFieldType::Array(Box::new(MetadataFieldType::Float)),
561 )
562 .field_type("projection", MetadataFieldType::String)
563 }
564}
565
566impl From<String> for MetadataValue {
567 fn from(s: String) -> Self {
568 MetadataValue::String(s)
569 }
570}
571
572impl From<&str> for MetadataValue {
573 fn from(s: &str) -> Self {
574 MetadataValue::String(s.to_string())
575 }
576}
577
578impl From<i64> for MetadataValue {
579 fn from(i: i64) -> Self {
580 MetadataValue::Integer(i)
581 }
582}
583
584impl From<f64> for MetadataValue {
585 fn from(f: f64) -> Self {
586 MetadataValue::Float(f)
587 }
588}
589
590impl From<bool> for MetadataValue {
591 fn from(b: bool) -> Self {
592 MetadataValue::Boolean(b)
593 }
594}
595
596impl From<DateTime<Utc>> for MetadataValue {
597 fn from(dt: DateTime<Utc>) -> Self {
598 MetadataValue::DateTime(dt)
599 }
600}
601
602#[cfg(test)]
603mod tests {
604 use super::*;
605
606 #[test]
607 fn test_metadata_basic_operations() {
608 let mut metadata = Metadata::new();
609 metadata.set("title", "Test Dataset");
610 metadata.set("version", 1i64);
611 metadata.set("temperature", 25.5f64);
612
613 assert_eq!(metadata.get_string("title"), Some("Test Dataset"));
614 assert_eq!(metadata.get_integer("version"), Some(1));
615 assert_eq!(metadata.get_float("temperature"), Some(25.5));
616 }
617
618 #[test]
619 fn test_metadata_schema_validation() {
620 let schema = MetadataSchema::new()
621 .require("title")
622 .require("version")
623 .field_type("version", MetadataFieldType::Integer)
624 .constraint(MetadataConstraint::MinValue("version".to_string(), 1.0));
625
626 let mut metadata = Metadata::new();
627 metadata.set("title", "Test");
628 metadata.set("version", 2i64);
629
630 assert!(schema.validate(&metadata).is_ok());
631 }
632
633 #[test]
634 fn test_processing_history() {
635 let mut metadata = Metadata::new();
636
637 let entry = ProcessingHistoryEntry::new("normalize")
638 .with_parameter("method", "z-score")
639 .with_parameter("mean", 0.0)
640 .with_parameter("std", 1.0);
641
642 metadata
643 .add_processing_history(entry)
644 .expect("Operation failed");
645
646 let history = metadata.get(standard_keys::PROCESSING_HISTORY);
647 assert!(matches!(history, Some(MetadataValue::Array(_))));
648 }
649}
650
651#[derive(Debug, Clone)]
653pub struct MetadataIndex {
654 text_index: HashMap<String, HashSet<String>>,
656 numeric_index: HashMap<String, Vec<(String, f64)>>,
658 date_index: HashMap<String, Vec<(String, DateTime<Utc>)>>,
660}
661
662impl Default for MetadataIndex {
663 fn default() -> Self {
664 Self::new()
665 }
666}
667
668impl MetadataIndex {
669 pub fn new() -> Self {
670 Self {
671 text_index: HashMap::new(),
672 numeric_index: HashMap::new(),
673 date_index: HashMap::new(),
674 }
675 }
676
677 pub fn index_metadata(&mut self, id: &str, metadata: &Metadata) {
679 for (key, value) in &metadata.data {
680 self.index_value(id, key, value);
681 }
682 }
683
684 fn index_value(&mut self, id: &str, key: &str, value: &MetadataValue) {
685 match value {
686 MetadataValue::String(s) => {
687 for token in s.to_lowercase().split_whitespace() {
689 self.text_index
690 .entry(format!("{key}:{token}"))
691 .or_default()
692 .insert(id.to_string());
693 }
694 }
695 MetadataValue::Integer(i) => {
696 self.numeric_index
697 .entry(key.to_string())
698 .or_default()
699 .push((id.to_string(), *i as f64));
700 }
701 MetadataValue::Float(f) => {
702 self.numeric_index
703 .entry(key.to_string())
704 .or_default()
705 .push((id.to_string(), *f));
706 }
707 MetadataValue::DateTime(dt) => {
708 self.date_index
709 .entry(key.to_string())
710 .or_default()
711 .push((id.to_string(), *dt));
712 }
713 MetadataValue::Array(arr) => {
714 for (i, item) in arr.iter().enumerate() {
715 self.index_value(id, &format!("{key}[{i}]"), item);
716 }
717 }
718 MetadataValue::Object(obj) => {
719 for (sub_key, sub_value) in obj {
720 self.index_value(id, &format!("{key}.{sub_key}"), sub_value);
721 }
722 }
723 _ => {}
724 }
725 }
726
727 pub fn searchtext(&self, field: &str, query: &str) -> HashSet<String> {
729 let key = format!("{field}:{}", query.to_lowercase());
730 self.text_index.get(&key).cloned().unwrap_or_default()
731 }
732
733 pub fn search_range(&self, field: &str, min: f64, max: f64) -> HashSet<String> {
735 self.numeric_index
736 .get(field)
737 .map(|values| {
738 values
739 .iter()
740 .filter(|(_, v)| *v >= min && *v <= max)
741 .map(|(id_, _)| id_.clone())
742 .collect()
743 })
744 .unwrap_or_default()
745 }
746
747 pub fn search_date_range(
749 &self,
750 field: &str,
751 start: DateTime<Utc>,
752 end: DateTime<Utc>,
753 ) -> HashSet<String> {
754 self.date_index
755 .get(field)
756 .map(|values| {
757 values
758 .iter()
759 .filter(|(_, dt)| *dt >= start && *dt <= end)
760 .map(|(id_, _)| id_.clone())
761 .collect()
762 })
763 .unwrap_or_default()
764 }
765}
766
767#[derive(Debug, Clone)]
769pub struct MetadataVersionControl {
770 current: Arc<RwLock<Metadata>>,
772 history: Arc<RwLock<Vec<MetadataVersion>>>,
774 max_versions: usize,
776}
777
778#[derive(Debug, Clone)]
779pub struct MetadataVersion {
780 pub id: String,
781 pub timestamp: DateTime<Utc>,
782 pub metadata: Metadata,
783 pub parent_id: Option<String>,
784 pub message: String,
785 pub author: Option<String>,
786 pub hash: String,
787}
788
789impl MetadataVersionControl {
790 pub fn new(initial: Metadata) -> Self {
791 let version = MetadataVersion {
792 id: uuid::Uuid::new_v4().to_string(),
793 timestamp: Utc::now(),
794 metadata: initial.clone(),
795 parent_id: None,
796 message: "Initial version".to_string(),
797 author: std::env::var("USER").ok(),
798 hash: Self::compute_hash(&initial),
799 };
800
801 Self {
802 current: Arc::new(RwLock::new(initial)),
803 history: Arc::new(RwLock::new(vec![version])),
804 max_versions: 100,
805 }
806 }
807
808 pub fn commit(&self, metadata: Metadata, message: impl Into<String>) -> Result<String> {
810 let mut history = self.history.write().expect("Operation failed");
811 let parent_id = history.last().map(|v| v.id.clone());
812
813 let version = MetadataVersion {
814 id: uuid::Uuid::new_v4().to_string(),
815 timestamp: Utc::now(),
816 metadata: metadata.clone(),
817 parent_id,
818 message: message.into(),
819 author: std::env::var("USER").ok(),
820 hash: Self::compute_hash(&metadata),
821 };
822
823 let versionid = version.id.clone();
824 history.push(version);
825
826 if history.len() > self.max_versions {
828 let keep_count = self.max_versions;
829 let remove_count = history.len() - keep_count;
830 history.drain(0..remove_count);
831 }
832
833 *self.current.write().expect("Operation failed") = metadata;
834
835 Ok(versionid)
836 }
837
838 pub fn get_version(&self, versionid: &str) -> Option<MetadataVersion> {
840 self.history
841 .read()
842 .expect("Operation failed")
843 .iter()
844 .find(|v| v.id == versionid)
845 .cloned()
846 }
847
848 pub fn get_history(&self) -> Vec<MetadataVersion> {
850 self.history.read().expect("Operation failed").clone()
851 }
852
853 pub fn diff(&self, version1: &str, version2: &str) -> Option<MetadataDiff> {
855 let history = self.history.read().expect("Operation failed");
856 let v1 = history.iter().find(|v| v.id == version1)?;
857 let v2 = history.iter().find(|v| v.id == version2)?;
858
859 Some(MetadataDiff::compute(&v1.metadata, &v2.metadata))
860 }
861
862 pub fn rollback(&self, versionid: &str) -> Result<()> {
864 let version = self
865 .get_version(versionid)
866 .ok_or_else(|| IoError::NotFound(format!("Version {versionid} not found")))?;
867
868 self.commit(version.metadata.clone(), format!("Rollback to {versionid}"))?;
869 Ok(())
870 }
871
872 fn compute_hash(metadata: &Metadata) -> String {
873 let json = serde_json::to_string(metadata).unwrap_or_default();
874 let mut hasher = Sha256::new();
875 hasher.update(json.as_bytes());
876 crate::encoding_utils::hex_encode(hasher.finalize())
877 }
878}
879
880#[derive(Debug, Clone)]
882pub struct MetadataDiff {
883 pub added: IndexMap<String, MetadataValue>,
884 pub removed: IndexMap<String, MetadataValue>,
885 pub modified: IndexMap<String, (MetadataValue, MetadataValue)>,
886}
887
888impl MetadataDiff {
889 pub fn compute(old: &Metadata, new: &Metadata) -> Self {
890 let mut added = IndexMap::new();
891 let mut removed = IndexMap::new();
892 let mut modified = IndexMap::new();
893
894 for (key, old_value) in &old.data {
896 match new.data.get(key) {
897 None => {
898 removed.insert(key.clone(), old_value.clone());
899 }
900 Some(new_value) if new_value != old_value => {
901 modified.insert(key.clone(), (old_value.clone(), new_value.clone()));
902 }
903 _ => {}
904 }
905 }
906
907 for (key, new_value) in &new.data {
909 if !old.data.contains_key(key) {
910 added.insert(key.clone(), new_value.clone());
911 }
912 }
913
914 Self {
915 added,
916 removed,
917 modified,
918 }
919 }
920
921 pub fn is_empty(&self) -> bool {
922 self.added.is_empty() && self.removed.is_empty() && self.modified.is_empty()
923 }
924}
925
926#[derive(Debug, Clone)]
928pub struct MetadataTemplate {
929 base: Metadata,
931 overridable: HashSet<String>,
933 required: HashSet<String>,
935 defaults: IndexMap<String, MetadataValue>,
937}
938
939impl MetadataTemplate {
940 pub fn new(base: Metadata) -> Self {
941 Self {
942 base,
943 overridable: HashSet::new(),
944 required: HashSet::new(),
945 defaults: IndexMap::new(),
946 }
947 }
948
949 pub fn allow_override(mut self, field: impl Into<String>) -> Self {
950 self.overridable.insert(field.into());
951 self
952 }
953
954 pub fn require_field(mut self, field: impl Into<String>) -> Self {
955 self.required.insert(field.into());
956 self
957 }
958
959 pub fn default_value(
960 mut self,
961 field: impl Into<String>,
962 value: impl Into<MetadataValue>,
963 ) -> Self {
964 self.defaults.insert(field.into(), value.into());
965 self
966 }
967
968 pub fn instantiate(&self, overrides: IndexMap<String, MetadataValue>) -> Result<Metadata> {
970 let mut metadata = self.base.clone();
971
972 for (key, value) in &self.defaults {
974 if !metadata.data.contains_key(key) {
975 metadata.set(key.clone(), value.clone());
976 }
977 }
978
979 for (key, value) in overrides {
981 if !self.overridable.contains(&key) && self.base.data.contains_key(&key) {
982 return Err(IoError::ValidationError(format!(
983 "Field '{}' cannot be overridden",
984 key
985 )));
986 }
987 metadata.set(key, value);
988 }
989
990 for field in &self.required {
992 if !metadata.data.contains_key(field) {
993 return Err(IoError::ValidationError(format!(
994 "Required field '{}' is missing",
995 field
996 )));
997 }
998 }
999
1000 Ok(metadata)
1001 }
1002}
1003
1004#[derive(Debug, Clone)]
1006pub struct MetadataReferenceResolver {
1007 registry: Arc<RwLock<HashMap<String, Metadata>>>,
1009 references: Arc<RwLock<HashMap<String, HashSet<String>>>>,
1011}
1012
1013impl Default for MetadataReferenceResolver {
1014 fn default() -> Self {
1015 Self::new()
1016 }
1017}
1018
1019impl MetadataReferenceResolver {
1020 pub fn new() -> Self {
1021 Self {
1022 registry: Arc::new(RwLock::new(HashMap::new())),
1023 references: Arc::new(RwLock::new(HashMap::new())),
1024 }
1025 }
1026
1027 pub fn register(&self, id: impl Into<String>, metadata: Metadata) -> Result<()> {
1029 let id = id.into();
1030 let refs = self.extract_references(&metadata);
1031
1032 self.registry
1033 .write()
1034 .expect("Operation failed")
1035 .insert(id.clone(), metadata);
1036 self.references
1037 .write()
1038 .expect("Operation failed")
1039 .insert(id, refs);
1040
1041 Ok(())
1042 }
1043
1044 pub fn resolve(&self, metadata: &mut Metadata) -> Result<()> {
1046 self.resolve_value(&mut metadata.data)?;
1047 Ok(())
1048 }
1049
1050 fn resolve_value(&self, data: &mut IndexMap<String, MetadataValue>) -> Result<()> {
1051 for value in data.values_mut() {
1052 match value {
1053 MetadataValue::String(s) if s.starts_with("ref:") => {
1054 let ref_id = s.strip_prefix("ref:").expect("Operation failed");
1055 let registry = self.registry.read().expect("Operation failed");
1056 if let Some(referenced) = registry.get(ref_id) {
1057 *value = MetadataValue::Object(referenced.data.clone());
1058 } else {
1059 return Err(IoError::NotFound(format!(
1060 "Reference '{}' not found",
1061 ref_id
1062 )));
1063 }
1064 }
1065 MetadataValue::Object(obj) => {
1066 self.resolve_value(obj)?;
1067 }
1068 MetadataValue::Array(arr) => {
1069 for item in arr {
1070 if let MetadataValue::Object(obj) = item {
1071 self.resolve_value(obj)?;
1072 }
1073 }
1074 }
1075 _ => {}
1076 }
1077 }
1078 Ok(())
1079 }
1080
1081 fn extract_references(&self, metadata: &Metadata) -> HashSet<String> {
1082 let mut refs = HashSet::new();
1083 self.extract_refs_from_value(&metadata.data, &mut refs);
1084 refs
1085 }
1086
1087 #[allow(clippy::only_used_in_recursion)]
1088 fn extract_refs_from_value(
1089 &self,
1090 data: &IndexMap<String, MetadataValue>,
1091 refs: &mut HashSet<String>,
1092 ) {
1093 for value in data.values() {
1094 match value {
1095 MetadataValue::String(s) if s.starts_with("ref:") => {
1096 if let Some(ref_id) = s.strip_prefix("ref:") {
1097 refs.insert(ref_id.to_string());
1098 }
1099 }
1100 MetadataValue::Object(obj) => {
1101 self.extract_refs_from_value(obj, refs);
1102 }
1103 MetadataValue::Array(arr) => {
1104 for item in arr {
1105 if let MetadataValue::Object(obj) = item {
1106 self.extract_refs_from_value(obj, refs);
1107 }
1108 }
1109 }
1110 _ => {}
1111 }
1112 }
1113 }
1114
1115 pub fn get_referencing(&self, id: &str) -> Vec<String> {
1117 let references = self.references.read().expect("Operation failed");
1118 references
1119 .iter()
1120 .filter(|(_, refs)| refs.contains(id))
1121 .map(|(referencing_id_, _)| referencing_id_.clone())
1122 .collect()
1123 }
1124}
1125
1126#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct MetadataProvenance {
1129 chain: Vec<ProvenanceEntry>,
1131 signatures: HashMap<String, String>,
1133}
1134
1135#[derive(Debug, Clone, Serialize, Deserialize)]
1136pub struct ProvenanceEntry {
1137 pub id: String,
1138 pub timestamp: DateTime<Utc>,
1139 pub action: String,
1140 pub agent: String,
1141 pub previous_hash: Option<String>,
1142 pub data_hash: String,
1143 pub metadata_snapshot: Option<Metadata>,
1144}
1145
1146impl Default for MetadataProvenance {
1147 fn default() -> Self {
1148 Self::new()
1149 }
1150}
1151
1152impl MetadataProvenance {
1153 pub fn new() -> Self {
1154 Self {
1155 chain: Vec::new(),
1156 signatures: HashMap::new(),
1157 }
1158 }
1159
1160 pub fn add_entry(
1162 &mut self,
1163 action: impl Into<String>,
1164 agent: impl Into<String>,
1165 metadata: &Metadata,
1166 ) {
1167 let previous_hash = self.chain.last().map(|e| e.data_hash.clone());
1168
1169 let entry = ProvenanceEntry {
1170 id: uuid::Uuid::new_v4().to_string(),
1171 timestamp: Utc::now(),
1172 action: action.into(),
1173 agent: agent.into(),
1174 previous_hash: previous_hash.clone(),
1175 data_hash: self.compute_hash(metadata, previous_hash.as_deref()),
1176 metadata_snapshot: Some(metadata.clone()),
1177 };
1178
1179 self.chain.push(entry);
1180 }
1181
1182 pub fn verify_chain(&self) -> Result<()> {
1184 let mut previous_hash: Option<String> = None;
1185
1186 for entry in &self.chain {
1187 if entry.previous_hash != previous_hash {
1188 return Err(IoError::ValidationError(format!(
1189 "Provenance chain broken at entry {}",
1190 entry.id
1191 )));
1192 }
1193
1194 if let Some(metadata) = &entry.metadata_snapshot {
1195 let expected_hash = self.compute_hash(metadata, previous_hash.as_deref());
1196 if entry.data_hash != expected_hash {
1197 return Err(IoError::ValidationError(format!(
1198 "Data hash mismatch at entry {}",
1199 entry.id
1200 )));
1201 }
1202 }
1203
1204 previous_hash = Some(entry.data_hash.clone());
1205 }
1206
1207 Ok(())
1208 }
1209
1210 fn compute_hash(&self, metadata: &Metadata, previous: Option<&str>) -> String {
1211 let mut hasher = Sha256::new();
1212 if let Some(prev) = previous {
1213 hasher.update(prev.as_bytes());
1214 }
1215 let json = serde_json::to_string(metadata).unwrap_or_default();
1216 hasher.update(json.as_bytes());
1217 crate::encoding_utils::hex_encode(hasher.finalize())
1218 }
1219
1220 pub fn export_certificate(&self) -> Result<String> {
1222 serde_json::to_string_pretty(self).map_err(|e| IoError::SerializationError(e.to_string()))
1223 }
1224}
1225
1226#[cfg(feature = "reqwest")]
1228#[derive(Debug, Clone)]
1229pub struct MetadataRepository {
1230 url: String,
1232 cache: Arc<RwLock<HashMap<String, Metadata>>>,
1234 client: reqwest::blocking::Client,
1236}
1237
1238#[cfg(feature = "reqwest")]
1239impl MetadataRepository {
1240 pub fn new(url: impl Into<String>) -> Self {
1241 crate::tls::ensure_default_tls_provider();
1244 Self {
1245 url: url.into(),
1246 cache: Arc::new(RwLock::new(HashMap::new())),
1247 client: reqwest::blocking::Client::new(),
1248 }
1249 }
1250
1251 pub fn fetch(&self, id: &str) -> Result<Metadata> {
1253 if let Some(metadata) = self.cache.read().expect("Operation failed").get(id) {
1255 return Ok(metadata.clone());
1256 }
1257
1258 let url = format!("{}/metadata/{id}", self.url);
1260 let response = self
1261 .client
1262 .get(&url)
1263 .send()
1264 .map_err(|e| IoError::NetworkError(e.to_string()))?;
1265
1266 if !response.status().is_success() {
1267 return Err(IoError::NetworkError(format!(
1268 "Failed to fetch metadata: {}",
1269 response.status()
1270 )));
1271 }
1272
1273 let metadata: Metadata = response
1274 .json()
1275 .map_err(|e| IoError::SerializationError(e.to_string()))?;
1276
1277 self.cache
1279 .write()
1280 .expect("Operation failed")
1281 .insert(id.to_string(), metadata.clone());
1282
1283 Ok(metadata)
1284 }
1285
1286 pub fn push(&self, id: &str, metadata: &Metadata) -> Result<()> {
1288 let url = format!("{}/metadata/{id}", self.url);
1289 let response = self
1290 .client
1291 .put(&url)
1292 .json(metadata)
1293 .send()
1294 .map_err(|e| IoError::NetworkError(e.to_string()))?;
1295
1296 if !response.status().is_success() {
1297 return Err(IoError::NetworkError(format!(
1298 "Failed to push metadata: {}",
1299 response.status()
1300 )));
1301 }
1302
1303 self.cache
1305 .write()
1306 .expect("Operation failed")
1307 .insert(id.to_string(), metadata.clone());
1308
1309 Ok(())
1310 }
1311
1312 pub fn search(&self, query: &str) -> Result<Vec<String>> {
1314 let url = format!(
1315 "{}/search?q={}",
1316 self.url,
1317 crate::encoding_utils::percent_encode(query)
1318 );
1319 let response = self
1320 .client
1321 .get(&url)
1322 .send()
1323 .map_err(|e| IoError::NetworkError(e.to_string()))?;
1324
1325 if !response.status().is_success() {
1326 return Err(IoError::NetworkError(format!(
1327 "Search failed: {}",
1328 response.status()
1329 )));
1330 }
1331
1332 let results: Vec<String> = response
1333 .json()
1334 .map_err(|e| IoError::SerializationError(e.to_string()))?;
1335
1336 Ok(results)
1337 }
1338}
1339
1340pub struct MetadataExtractor {
1342 extractors: HashMap<String, Box<dyn Fn(&Path) -> Result<Metadata> + Send + Sync>>,
1343}
1344
1345impl Default for MetadataExtractor {
1346 fn default() -> Self {
1347 Self::new()
1348 }
1349}
1350
1351impl MetadataExtractor {
1352 pub fn new() -> Self {
1353 let mut extractor = Self {
1354 extractors: HashMap::new(),
1355 };
1356
1357 extractor.register_defaults();
1359 extractor
1360 }
1361
1362 fn register_defaults(&mut self) {
1363 #[cfg(feature = "image_io")]
1365 self.register(
1366 "image",
1367 Box::new(|path| {
1368 let mut metadata = Metadata::new();
1369
1370 if let Ok(img) = image::open(path) {
1372 metadata.set("width", img.width() as i64);
1373 metadata.set("height", img.height() as i64);
1374 metadata.set("color_type", format!("{:?}", img.color()));
1375 }
1376
1377 if let Ok(file) = std::fs::File::open(path) {
1379 let exif_reader = exif::Reader::new();
1380 if let Ok(exif) =
1381 exif_reader.read_from_container(&mut std::io::BufReader::new(file))
1382 {
1383 for field in exif.fields() {
1384 let key = format!("exif.{}", field.tag);
1385 let value = field.display_value().to_string();
1386 metadata.set_extension("exif", key, value);
1387 }
1388 }
1389 }
1390
1391 Ok(metadata)
1392 }),
1393 );
1394
1395 self.register(
1397 "audio",
1398 Box::new(|path| {
1399 let mut metadata = Metadata::new();
1400
1401 if let Ok(meta) = std::fs::metadata(path) {
1403 metadata.set("file_size", meta.len() as i64);
1404 if let Ok(modified) = meta.modified() {
1405 metadata.set("modified", MetadataValue::DateTime(modified.into()));
1406 }
1407 }
1408
1409 Ok(metadata)
1413 }),
1414 );
1415
1416 self.register(
1418 "netcdf",
1419 Box::new(|_path| {
1420 let metadata = Metadata::new();
1421
1422 Ok(metadata)
1426 }),
1427 );
1428 }
1429
1430 pub fn register(
1432 &mut self,
1433 format: &str,
1434 extractor: Box<dyn Fn(&Path) -> Result<Metadata> + Send + Sync>,
1435 ) {
1436 self.extractors.insert(format.to_string(), extractor);
1437 }
1438
1439 pub fn extract(&self, path: impl AsRef<Path>) -> Result<Metadata> {
1441 let path = path.as_ref();
1442 let extension = path.extension().and_then(|ext| ext.to_str()).unwrap_or("");
1443
1444 let format = match extension {
1446 "png" | "jpg" | "jpeg" | "gif" | "bmp" | "tiff" => "image",
1447 "wav" | "mp3" | "flac" | "ogg" => "audio",
1448 "nc" | "nc4" => "netcdf",
1449 "h5" | "hdf5" => "hdf5",
1450 _ => {
1451 return Err(IoError::UnsupportedFormat(format!(
1452 "No extractor for format: {}",
1453 extension
1454 )))
1455 }
1456 };
1457
1458 if let Some(extractor) = self.extractors.get(format) {
1459 extractor(path)
1460 } else {
1461 Err(IoError::UnsupportedFormat(format!(
1462 "No extractor for format: {}",
1463 format
1464 )))
1465 }
1466 }
1467
1468 pub fn extract_composite(&self, paths: &[impl AsRef<Path>]) -> Result<Metadata> {
1470 let mut composite = Metadata::new();
1471
1472 for (i, path) in paths.iter().enumerate() {
1473 let metadata = self.extract(path)?;
1474
1475 let key = format!("file_{}", i);
1477 composite.set(key, MetadataValue::Object(metadata.data));
1478 }
1479
1480 composite.set("file_count", paths.len() as i64);
1481 composite.update_modification_date();
1482
1483 Ok(composite)
1484 }
1485}