use std::collections::BTreeSet;
use std::io::Write;
use std::path::PathBuf;
use crate::Error;
#[derive(Default)]
pub struct Stager {
files: Vec<(&'static str, &'static [u8])>,
}
impl Stager {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn add(mut self, relative_path: &'static str, bytes: &'static [u8]) -> Self {
self.files.push((relative_path, bytes));
self
}
#[must_use]
pub fn with<I>(mut self, iter: I) -> Self
where
I: IntoIterator<Item = (&'static str, &'static [u8])>,
{
self.files.extend(iter);
self
}
pub fn stage(self) -> Result<tempfile::TempDir, Error> {
let mut seen: BTreeSet<&str> = BTreeSet::new();
for (path, _) in &self.files {
if !seen.insert(*path) {
return Err(Error::DuplicatePath {
path: (*path).to_string(),
});
}
}
let dir = tempfile::tempdir()?;
for (rel, bytes) in self.files {
let target: PathBuf = dir.path().join(rel);
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent)?;
}
let mut f = std::fs::File::create(&target)?;
f.write_all(bytes)?;
}
Ok(dir)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stages_single_file_at_relative_path() {
let dir = Stager::new()
.add("a/v1/x.proto", b"syntax = \"proto3\";")
.stage()
.expect("stage");
let p = dir.path().join("a/v1/x.proto");
assert!(p.exists(), "expected staged file at {p:?}");
let body = std::fs::read(&p).expect("read");
assert_eq!(body.as_slice(), b"syntax = \"proto3\";");
}
#[test]
fn stages_multiple_files_under_nested_subdirs() {
let dir = Stager::new()
.add("a/v1/x.proto", b"x")
.add("b/v2/y.proto", b"y")
.stage()
.expect("stage");
assert!(dir.path().join("a/v1/x.proto").exists());
assert!(dir.path().join("b/v2/y.proto").exists());
}
#[test]
fn with_iterator_extends_files() {
let pairs: Vec<(&str, &[u8])> = vec![
("a/v1/x.proto", b"x" as &[u8]),
("b/v1/y.proto", b"y" as &[u8]),
];
let dir = Stager::new().with(pairs).stage().expect("stage");
assert!(dir.path().join("a/v1/x.proto").exists());
assert!(dir.path().join("b/v1/y.proto").exists());
}
#[test]
fn duplicate_paths_return_error() {
let err = Stager::new()
.add("a/v1/x.proto", b"first")
.add("a/v1/x.proto", b"second")
.stage()
.expect_err("should error on duplicate");
match err {
Error::DuplicatePath { path } => assert_eq!(path, "a/v1/x.proto"),
other => panic!("expected DuplicatePath, got {other:?}"),
}
}
}