Skip to main content

scirs2_io/
metadata.rs

1//! Advanced metadata management for scientific data
2//!
3//! Provides comprehensive metadata handling across different file formats with
4//! unified interfaces for storing, retrieving, and transforming metadata.
5
6#![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
19/// Standard metadata keys commonly used across scientific data formats
20pub 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/// Metadata value types supporting various data formats
40#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
41#[serde(untagged)]
42pub enum MetadataValue {
43    /// String value
44    String(String),
45    /// Integer value
46    Integer(i64),
47    /// Floating point value
48    Float(f64),
49    /// Boolean value
50    Boolean(bool),
51    /// Date/time value
52    DateTime(DateTime<Utc>),
53    /// Array of values
54    Array(Vec<MetadataValue>),
55    /// Nested metadata object
56    Object(IndexMap<String, MetadataValue>),
57    /// Binary data
58    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/// Advanced metadata container with rich functionality
84#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85pub struct Metadata {
86    /// Core metadata stored in insertion order
87    data: IndexMap<String, MetadataValue>,
88    /// Format-specific metadata extensions
89    extensions: HashMap<String, IndexMap<String, MetadataValue>>,
90    /// Metadata schema version
91    schema_version: String,
92}
93
94impl Metadata {
95    /// Create a new empty metadata container
96    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    /// Create metadata with a specific schema version
105    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    /// Set a metadata value
114    pub fn set(&mut self, key: impl Into<String>, value: impl Into<MetadataValue>) {
115        self.data.insert(key.into(), value.into());
116    }
117
118    /// Get a metadata value
119    pub fn get(&self, key: &str) -> Option<&MetadataValue> {
120        self.data.get(key)
121    }
122
123    /// Get a typed metadata value
124    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    /// Get integer metadata value
132    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    /// Get float metadata value
140    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    /// Set format-specific extension metadata
149    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    /// Get format-specific extension metadata
162    pub fn get_extension(&self, format: &str) -> Option<&IndexMap<String, MetadataValue>> {
163        self.extensions.get(format)
164    }
165
166    /// Merge metadata from another container
167    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    /// Validate metadata against a schema
180    pub fn validate(&self, schema: &MetadataSchema) -> Result<()> {
181        schema.validate(self)
182    }
183
184    /// Convert metadata to a specific format
185    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    /// Load metadata from a file
199    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    /// Save metadata to a file
221    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    /// Add processing history entry
242    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    /// Update modification timestamp
273    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/// Processing history entry for tracking data transformations
282#[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/// Metadata output formats
311#[derive(Debug, Clone, Copy)]
312pub enum MetadataFormat {
313    Json,
314    Yaml,
315    Toml,
316}
317
318/// Metadata schema for validation
319#[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        // Check required fields
377        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        // Validate field types
387        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        // Apply constraints
399        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
472/// Metadata transformer for converting between different metadata formats
473pub 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    /// Add a field mapping
493    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    /// Transform metadata using configured mappings
499    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
522/// Predefined metadata schemas for common scientific data types
523pub mod schemas {
524    use super::*;
525
526    /// Schema for image metadata
527    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    /// Schema for time series metadata
540    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    /// Schema for geospatial metadata
554    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/// Advanced metadata index for fast searching and querying
652#[derive(Debug, Clone)]
653pub struct MetadataIndex {
654    /// Inverted index for text search
655    text_index: HashMap<String, HashSet<String>>,
656    /// Numeric index for range queries
657    numeric_index: HashMap<String, Vec<(String, f64)>>,
658    /// Date index for temporal queries
659    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    /// Index a metadata collection
678    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                // Tokenize and index text
688                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    /// Search for metadata by text query
728    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    /// Search for metadata by numeric range
734    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    /// Search for metadata by date range
748    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/// Metadata version control system
768#[derive(Debug, Clone)]
769pub struct MetadataVersionControl {
770    /// Current version
771    current: Arc<RwLock<Metadata>>,
772    /// Version history
773    history: Arc<RwLock<Vec<MetadataVersion>>>,
774    /// Maximum number of versions to keep
775    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    /// Commit a new version
809    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        // Prune old versions if necessary
827        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    /// Get a specific version
839    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    /// Get version history
849    pub fn get_history(&self) -> Vec<MetadataVersion> {
850        self.history.read().expect("Operation failed").clone()
851    }
852
853    /// Compute diff between two versions
854    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    /// Rollback to a specific version
863    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/// Represents differences between two metadata objects
881#[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        // Find removed and modified fields
895        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        // Find added fields
908        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/// Metadata inheritance and composition system
927#[derive(Debug, Clone)]
928pub struct MetadataTemplate {
929    /// Base metadata to inherit from
930    base: Metadata,
931    /// Fields that can be overridden
932    overridable: HashSet<String>,
933    /// Fields that must be provided
934    required: HashSet<String>,
935    /// Default values for optional fields
936    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    /// Create a new metadata instance from this template
969    pub fn instantiate(&self, overrides: IndexMap<String, MetadataValue>) -> Result<Metadata> {
970        let mut metadata = self.base.clone();
971
972        // Apply defaults
973        for (key, value) in &self.defaults {
974            if !metadata.data.contains_key(key) {
975                metadata.set(key.clone(), value.clone());
976            }
977        }
978
979        // Apply overrides
980        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        // Check required fields
991        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/// Cross-reference resolution system
1005#[derive(Debug, Clone)]
1006pub struct MetadataReferenceResolver {
1007    /// Registry of metadata objects by ID
1008    registry: Arc<RwLock<HashMap<String, Metadata>>>,
1009    /// Reference graph for dependency tracking
1010    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    /// Register a metadata object
1028    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    /// Resolve all references in a metadata object
1045    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    /// Get all objects that reference a given ID
1116    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/// Metadata provenance tracking with cryptographic verification
1127#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct MetadataProvenance {
1129    /// Chain of custody
1130    chain: Vec<ProvenanceEntry>,
1131    /// Cryptographic signatures
1132    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    /// Add a provenance entry
1161    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    /// Verify the integrity of the provenance chain
1183    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    /// Export provenance as a verifiable certificate
1221    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/// External metadata repository integration
1227#[cfg(feature = "reqwest")]
1228#[derive(Debug, Clone)]
1229pub struct MetadataRepository {
1230    /// Repository URL
1231    url: String,
1232    /// Local cache
1233    cache: Arc<RwLock<HashMap<String, Metadata>>>,
1234    /// HTTP client
1235    client: reqwest::blocking::Client,
1236}
1237
1238#[cfg(feature = "reqwest")]
1239impl MetadataRepository {
1240    pub fn new(url: impl Into<String>) -> Self {
1241        // `reqwest::blocking::Client::new()` builds the client eagerly and panics without a
1242        // process-default rustls CryptoProvider -- install the pure-Rust OxiTLS one if needed.
1243        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    /// Fetch metadata from repository
1252    pub fn fetch(&self, id: &str) -> Result<Metadata> {
1253        // Check cache first
1254        if let Some(metadata) = self.cache.read().expect("Operation failed").get(id) {
1255            return Ok(metadata.clone());
1256        }
1257
1258        // Fetch from repository
1259        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        // Update cache
1278        self.cache
1279            .write()
1280            .expect("Operation failed")
1281            .insert(id.to_string(), metadata.clone());
1282
1283        Ok(metadata)
1284    }
1285
1286    /// Push metadata to repository
1287    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        // Update cache
1304        self.cache
1305            .write()
1306            .expect("Operation failed")
1307            .insert(id.to_string(), metadata.clone());
1308
1309        Ok(())
1310    }
1311
1312    /// Search repository
1313    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
1340/// Automatic metadata extraction from files
1341pub 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        // Register default extractors
1358        extractor.register_defaults();
1359        extractor
1360    }
1361
1362    fn register_defaults(&mut self) {
1363        // Image metadata extractor (requires image_io feature)
1364        #[cfg(feature = "image_io")]
1365        self.register(
1366            "image",
1367            Box::new(|path| {
1368                let mut metadata = Metadata::new();
1369
1370                // Use image crate to extract metadata
1371                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                // Extract EXIF data if available
1378                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        // Audio metadata extractor
1396        self.register(
1397            "audio",
1398            Box::new(|path| {
1399                let mut metadata = Metadata::new();
1400
1401                // Basic audio file info
1402                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                // Extract audio-specific metadata
1410                // This would use audio-specific libraries in a real implementation
1411
1412                Ok(metadata)
1413            }),
1414        );
1415
1416        // NetCDF metadata extractor
1417        self.register(
1418            "netcdf",
1419            Box::new(|_path| {
1420                let metadata = Metadata::new();
1421
1422                // Extract NetCDF global attributes
1423                // This would use the netcdf module in a real implementation
1424
1425                Ok(metadata)
1426            }),
1427        );
1428    }
1429
1430    /// Register a custom extractor
1431    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    /// Extract metadata from a file
1440    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        // Determine format from extension
1445        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    /// Extract and merge metadata from multiple sources
1469    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            // Store each file's metadata under a numbered key
1476            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}