use crate::domain::lock::Lock;
use serde::Deserialize;
use std::fs;
use std::path::{Path, PathBuf};
use thiserror::Error;
pub const LOCK_FILE_NAME: &str = "gx.lock";
#[derive(Debug, Error)]
pub enum Error {
#[error("failed to read lock file: {}", path.display())]
Read {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("failed to parse lock file: {}", path.display())]
Parse {
path: PathBuf,
#[source]
source: Box<toml::de::Error>,
},
#[error("failed to write lock file: {}", path.display())]
Write {
path: PathBuf,
#[source]
source: std::io::Error,
},
#[error("unrecognized lock file format: {}", path.display())]
UnrecognizedFormat { path: PathBuf },
}
pub struct Store {
path: PathBuf,
}
impl Store {
#[must_use]
pub fn new(path: &Path) -> Self {
Self {
path: path.to_path_buf(),
}
}
}
#[expect(
clippy::multiple_inherent_impl,
reason = "constructor and methods are in separate impl blocks for readability"
)]
impl Store {
pub fn load(&self) -> Result<Lock, Error> {
if !self.path.exists() {
return Ok(Lock::default());
}
let content = fs::read_to_string(&self.path).map_err(|source| Error::Read {
path: self.path.clone(),
source,
})?;
if content.trim().is_empty() {
return Ok(Lock::default());
}
if let Some(lock) = super::format::try_parse(&content, &self.path)? {
return Ok(lock);
}
if let Some(lock) = super::migration::try_parse(&content, &self.path)? {
return Ok(lock);
}
Err(Error::UnrecognizedFormat {
path: self.path.clone(),
})
}
pub fn save(&self, lock: &Lock) -> Result<(), Error> {
let output = super::format::write(lock);
fs::write(&self.path, output).map_err(|source| Error::Write {
path: self.path.clone(),
source,
})?;
Ok(())
}
}
pub(super) fn parse_toml<T: for<'de> Deserialize<'de>>(
content: &str,
path: &Path,
) -> Result<T, Error> {
toml::from_str(content).map_err(|source| Error::Parse {
path: path.to_path_buf(),
source: Box::new(source),
})
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
reason = "tests use unwrap, indexing, and other patterns freely"
)]
#[path = "tests.rs"]
mod tests;