use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::infra::io::durable::atomic_write;
use crate::platform::error::PlatformError;
pub const FILES_BACKEND_FILE: &str = "files-backend.json";
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct FilesBackendSelection {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub credential_id: Option<String>,
}
pub fn read_selection(store_dir: &Path) -> Result<FilesBackendSelection, PlatformError> {
let path = store_dir.join(FILES_BACKEND_FILE);
let bytes = match std::fs::read(&path) {
Ok(bytes) => bytes,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(FilesBackendSelection::default());
}
Err(err) => {
return Err(PlatformError::new(
"PROJECT_FILES_BACKEND_READ",
format!("{}: {err}", path.display()),
));
}
};
serde_json::from_slice(&bytes).map_err(|err| {
PlatformError::new(
"PROJECT_FILES_BACKEND_INVALID",
format!("{}: {err}", path.display()),
)
})
}
pub fn write_selection(
store_dir: &Path,
selection: &FilesBackendSelection,
) -> Result<(), PlatformError> {
std::fs::create_dir_all(store_dir).map_err(|err| {
PlatformError::new(
"PROJECT_FILES_BACKEND_WRITE",
format!("{}: {err}", store_dir.display()),
)
})?;
let path = store_dir.join(FILES_BACKEND_FILE);
let mut bytes = serde_json::to_vec_pretty(selection)
.map_err(|err| PlatformError::new("PROJECT_FILES_BACKEND_WRITE", err.to_string()))?;
bytes.push(b'\n');
atomic_write(&path, &bytes).map_err(|err| {
PlatformError::new(
"PROJECT_FILES_BACKEND_WRITE",
format!("{}: {err}", path.display()),
)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_absent_document_is_the_default_and_a_written_one_reads_back() {
let dir = tempfile::tempdir().unwrap();
assert_eq!(read_selection(dir.path()).unwrap(), FilesBackendSelection::default());
let selection = FilesBackendSelection {
credential_id: Some("seaweed-local".into()),
};
write_selection(dir.path(), &selection).unwrap();
assert_eq!(read_selection(dir.path()).unwrap(), selection);
assert!(dir.path().join(FILES_BACKEND_FILE).is_file());
}
#[test]
fn a_document_with_a_field_this_build_does_not_know_is_refused_by_name() {
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join(FILES_BACKEND_FILE),
br#"{"credential_id":"a","bucket":"b"}"#,
)
.unwrap();
let err = read_selection(dir.path()).unwrap_err();
assert_eq!(err.code, "PROJECT_FILES_BACKEND_INVALID");
assert!(err.message.contains("bucket"), "{}", err.message);
}
}