use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::Read; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; use time::{OffsetDateTime, format_description::well_known::Rfc2822}; #[derive(Serialize, Deserialize)] pub struct Metadata { pub id: String, #[serde(alias = "createdOn")] pub created_on: u64, } impl Metadata { /// Reads `Metadata` from a file or creates a new one. /// /// This method will create new metadata if it can't read the file, /// whether it's because of malformed JSON, UTF-8 or because the file /// is not readable. pub fn read_or_create(file_path: &PathBuf) -> Metadata { if let Some(metadata) = Metadata::read_metadata_file(file_path) { metadata } else { let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).map_or_else( |_| 0, |duration| u64::try_from(duration.as_millis()).unwrap_or(0), ); Metadata { id: timestamp.to_string(), created_on: timestamp, } } } /// Returns the metadata `created_on` as an RFC-2822 string. pub fn created_on_utc(&self) -> Option { let date = OffsetDateTime::from_unix_timestamp_nanos((self.created_on * 1_000_000).into()).ok()?; date.format(&Rfc2822).ok() } fn read_metadata_file(file_path: &PathBuf) -> Option { let mut file = File::open(file_path).ok()?; let mut contents = String::new(); file.read_to_string(&mut contents).ok()?; serde_json::from_str(&contents).ok() } } #[cfg(test)] mod tests { use std::fs::write; use super::*; use test_utilities::*; #[test] fn test_reads_metadata_if_file_exists() { let test_dir = setup_test_dir(); create_test_file( &test_dir.join("metadata.json"), "\ { \"id\": \"cool\", \"created_on\": 1736105008957 }", ); let metadata = Metadata::read_or_create(&test_dir.join("metadata.json")); assert_eq!(metadata.id, "cool"); assert_eq!(metadata.created_on, 1_736_105_008_957); cleanup_test_dir(&test_dir); } #[test] fn test_creates_metadata_if_file_does_not_exist() { let test_dir = setup_test_dir(); assert!(!test_dir.join("metadata.json").exists()); let metadata = Metadata::read_or_create(&test_dir.join("metadata.json")); assert_eq!(metadata.created_on.to_string(), metadata.id); cleanup_test_dir(&test_dir); } #[test] fn test_creates_metadata_if_file_is_malformed() { let test_dir = setup_test_dir(); write(test_dir.join("metadata.json"), vec![0xFF, 0xFF]).expect("Failed to write file"); let metadata = Metadata::read_or_create(&test_dir.join("metadata.json")); assert_eq!(metadata.created_on.to_string(), metadata.id); cleanup_test_dir(&test_dir); } #[test] fn test_it_returns_created_on_as_rfc_2822_utc() { let metadata = Metadata { id: "cool".to_string(), created_on: 1_736_035_200_000, }; if let Some(created_on_utc) = metadata.created_on_utc() { assert_eq!(created_on_utc, "Sun, 05 Jan 2025 00:00:00 +0000"); } else { panic!("Could not generate RFC-2822 string"); } } }