use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Project {
pub metadata: ProjectMetadata,
pub data: serde_json::Value,
#[serde(skip)]
pub file_path: Option<PathBuf>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProjectMetadata {
pub name: String,
pub version: String,
pub app_version: String,
pub created_at: u64,
pub modified_at: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub author: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
impl Project {
pub fn new(name: impl Into<String>) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Self {
metadata: ProjectMetadata {
name: name.into(),
version: "1.0.0".to_string(),
app_version: env!("CARGO_PKG_VERSION").to_string(),
created_at: now,
modified_at: now,
author: None,
description: None,
},
data: serde_json::Value::Null,
file_path: None,
}
}
pub fn with_data(name: impl Into<String>, data: serde_json::Value) -> Self {
let mut project = Self::new(name);
project.data = data;
project
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json)
}
pub fn from_file(path: impl Into<PathBuf>) -> Result<Self, ProjectError> {
let path = path.into();
let json = std::fs::read_to_string(&path)?;
let mut project: Project = serde_json::from_str(&json)?;
project.file_path = Some(path);
Ok(project)
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
pub fn to_file(&mut self, path: impl Into<PathBuf>) -> Result<(), ProjectError> {
let path = path.into();
let json = self.to_json()?;
std::fs::write(&path, json)?;
self.file_path = Some(path);
self.metadata.modified_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
Ok(())
}
pub fn save(&mut self) -> Result<(), ProjectError> {
if let Some(path) = self.file_path.clone() {
self.to_file(path)
} else {
Err(ProjectError::NoFilePath)
}
}
pub fn mark_modified(&mut self) {
self.metadata.modified_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
}
}
#[derive(Debug)]
pub enum ProjectError {
Io(std::io::Error),
Json(serde_json::Error),
NoFilePath,
}
impl From<std::io::Error> for ProjectError {
fn from(err: std::io::Error) -> Self {
ProjectError::Io(err)
}
}
impl From<serde_json::Error> for ProjectError {
fn from(err: serde_json::Error) -> Self {
ProjectError::Json(err)
}
}
impl std::fmt::Display for ProjectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProjectError::Io(e) => write!(f, "IO error: {}", e),
ProjectError::Json(e) => write!(f, "JSON error: {}", e),
ProjectError::NoFilePath => write!(f, "No file path set for project"),
}
}
}
impl std::error::Error for ProjectError {}