use std::fmt;
use std::sync::{Arc, Mutex, MutexGuard};
mod error;
mod glob;
mod mem;
mod path;
use error::StorePoisoned;
pub use error::{PathReason, StoreError, StoreErrorKind};
use glob::{MAX_GLOB_PATTERN_BYTES, compile_glob, matches_tokens, validate_glob_grammar};
pub use mem::{MemStore, Store};
use path::StorePath;
#[derive(Clone)]
#[non_exhaustive]
pub struct StoreRef {
inner: Arc<Mutex<Box<dyn Store + Send>>>,
}
impl fmt::Debug for StoreRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StoreRef").finish_non_exhaustive()
}
}
impl StoreRef {
#[must_use]
pub fn new(backend: Box<dyn Store + Send>) -> StoreRef {
StoreRef {
inner: Arc::new(Mutex::new(backend)),
}
}
#[must_use]
pub fn memory() -> StoreRef {
StoreRef::new(Box::new(MemStore::new()))
}
fn lock(&self) -> Result<MutexGuard<'_, Box<dyn Store + Send>>, StoreError> {
self.inner
.lock()
.map_err(|_| StoreError::backend(StorePoisoned))
}
pub fn write(&self, path: &str, contents: &str) -> Result<(), StoreError> {
let path = StorePath::parse(path)?;
self.lock()?.write(path.as_str(), contents)
}
pub fn append(&self, path: &str, contents: &str) -> Result<(), StoreError> {
let path = StorePath::parse(path)?;
self.lock()?.append(path.as_str(), contents)
}
pub fn read_lines(&self, path: &str) -> Result<String, StoreError> {
let path = StorePath::parse(path)?;
self.lock()?.read_lines(path.as_str())
}
pub fn read(&self, path: &str) -> Result<String, StoreError> {
let path = StorePath::parse(path)?;
self.lock()?.read(path.as_str())
}
pub fn inject(&self, path: &str) -> Result<String, StoreError> {
let path = StorePath::parse(path)?;
let contents = self.lock()?.read(path.as_str())?;
Ok(crate::untrusted::wrap(&contents))
}
pub fn str_replace(&self, path: &str, old: &str, new: &str) -> Result<(), StoreError> {
let path = StorePath::parse(path)?;
if old.is_empty() {
return Err(StoreError::InvalidAnchor {
path: path.as_str().to_owned(),
reason: "anchor must not be empty",
});
}
self.lock()?.str_replace(path.as_str(), old, new)
}
pub fn delete(&self, path: &str) -> Result<(), StoreError> {
let path = StorePath::parse(path)?;
self.lock()?.delete(path.as_str())
}
pub fn glob(&self, pattern: &str) -> Result<Vec<String>, StoreError> {
if pattern.is_empty() {
return Err(StoreError::InvalidPattern {
pattern: pattern.to_owned(),
reason: "pattern is empty".to_owned(),
});
}
if pattern.len() > MAX_GLOB_PATTERN_BYTES {
return Err(StoreError::InvalidPattern {
pattern: pattern.to_owned(),
reason: format!("pattern exceeds {MAX_GLOB_PATTERN_BYTES} bytes"),
});
}
if pattern.bytes().any(|b| b < 0x20 || b == 0x7f) {
return Err(StoreError::InvalidPattern {
pattern: pattern.to_owned(),
reason: "pattern contains a control character".to_owned(),
});
}
if let Err(reason) = validate_glob_grammar(pattern) {
return Err(StoreError::InvalidPattern {
pattern: pattern.to_owned(),
reason: reason.to_owned(),
});
}
let snapshot = self.lock()?.glob("**")?;
let tokens = compile_glob(pattern.as_bytes());
Ok(snapshot
.into_iter()
.filter(|path| matches_tokens(&tokens, path.as_bytes()))
.collect())
}
pub fn exists(&self, path: &str) -> Result<bool, StoreError> {
let path = StorePath::parse(path)?;
self.lock()?.exists(path.as_str())
}
}
#[cfg(test)]
mod tests;