1use std::io::prelude::*;
3use std::path::{Path, PathBuf};
4
5use serde_json::Value;
6use ssri::{Algorithm, Integrity};
7
8use crate::content::write;
9use crate::errors::{Error, Internal, Result};
10use crate::index;
11
12pub fn write<P, D, K>(cache: P, key: K, data: D) -> Result<Integrity>
24where
25 P: AsRef<Path>,
26 D: AsRef<[u8]>,
27 K: AsRef<str>,
28{
29 let mut writer = Writer::create(cache.as_ref(), key.as_ref())?;
30 writer.write_all(data.as_ref()).with_context(|| {
31 format!(
32 "Failed to write to cache data for key {} for cache at {:?}",
33 key.as_ref(),
34 cache.as_ref()
35 )
36 })?;
37 writer.written = data.as_ref().len();
38 writer.commit()
39}
40
41pub fn write_hash<P, D>(cache: P, data: D) -> Result<Integrity>
53where
54 P: AsRef<Path>,
55 D: AsRef<[u8]>,
56{
57 let mut writer = WriteOpts::new()
58 .algorithm(Algorithm::Sha256)
59 .size(data.as_ref().len())
60 .open_hash(cache.as_ref())?;
61 writer.write_all(data.as_ref()).with_context(|| {
62 format!(
63 "Failed to write to cache data for cache at {:?}",
64 cache.as_ref()
65 )
66 })?;
67 writer.written = data.as_ref().len();
68 writer.commit()
69}
70
71#[derive(Clone, Default)]
73pub struct WriteOpts {
74 pub(crate) algorithm: Option<Algorithm>,
75 pub(crate) sri: Option<Integrity>,
76 pub(crate) size: Option<usize>,
77 pub(crate) time: Option<u128>,
78 pub(crate) metadata: Option<Value>,
79}
80
81impl WriteOpts {
82 pub fn new() -> WriteOpts {
84 Default::default()
85 }
86
87 pub fn open<P, K>(self, cache: P, key: K) -> Result<Writer>
89 where
90 P: AsRef<Path>,
91 K: AsRef<str>,
92 {
93 Ok(Writer {
94 cache: cache.as_ref().to_path_buf(),
95 key: Some(String::from(key.as_ref())),
96 written: 0,
97 writer: write::Writer::new(
98 cache.as_ref(),
99 *self.algorithm.as_ref().unwrap_or(&Algorithm::Sha256),
100 self.size,
101 )?,
102 opts: self,
103 })
104 }
105
106 pub fn open_hash<P>(self, cache: P) -> Result<Writer>
108 where
109 P: AsRef<Path>,
110 {
111 Ok(Writer {
112 cache: cache.as_ref().to_path_buf(),
113 key: None,
114 written: 0,
115 writer: write::Writer::new(
116 cache.as_ref(),
117 *self.algorithm.as_ref().unwrap_or(&Algorithm::Sha256),
118 self.size,
119 )?,
120 opts: self,
121 })
122 }
123
124 pub fn algorithm(mut self, algo: Algorithm) -> Self {
126 self.algorithm = Some(algo);
127 self
128 }
129
130 pub fn size(mut self, size: usize) -> Self {
133 self.size = Some(size);
134 self
135 }
136
137 pub fn metadata(mut self, metadata: Value) -> Self {
139 self.metadata = Some(metadata);
140 self
141 }
142
143 pub fn time(mut self, time: u128) -> Self {
147 self.time = Some(time);
148 self
149 }
150
151 pub fn integrity(mut self, sri: Integrity) -> Self {
155 self.sri = Some(sri);
156 self
157 }
158}
159
160pub struct Writer {
162 cache: PathBuf,
163 key: Option<String>,
164 written: usize,
165 pub(crate) writer: write::Writer,
166 opts: WriteOpts,
167}
168
169impl Write for Writer {
170 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
171 let written = self.writer.write(buf)?;
172 self.written += written;
173 Ok(written)
174 }
175 fn flush(&mut self) -> std::io::Result<()> {
176 self.writer.flush()
177 }
178}
179
180impl Writer {
181 pub fn create<P, K>(cache: P, key: K) -> Result<Writer>
196 where
197 P: AsRef<Path>,
198 K: AsRef<str>,
199 {
200 WriteOpts::new()
201 .algorithm(Algorithm::Sha256)
202 .open(cache.as_ref(), key.as_ref())
203 }
204
205 pub fn commit(mut self) -> Result<Integrity> {
210 let cache = self.cache;
211 let writer_sri = self.writer.close()?;
212 if let Some(sri) = &self.opts.sri {
213 if sri.matches(&writer_sri).is_none() {
214 return Err(ssri::Error::IntegrityCheckError(sri.clone(), writer_sri).into());
215 }
216 } else {
217 self.opts.sri = Some(writer_sri.clone());
218 }
219 if let Some(size) = self.opts.size {
220 if size != self.written {
221 return Err(Error::SizeError(size, self.written));
222 }
223 }
224 if let Some(key) = self.key {
225 index::insert(&cache, &key, self.opts)
226 } else {
227 Ok(writer_sri)
228 }
229 }
230}
231
232#[cfg(test)]
233mod tests {
234
235 #[test]
236 fn round_trip() {
237 let tmp = tempfile::tempdir().unwrap();
238 let dir = tmp.path().to_owned();
239 crate::write(&dir, "hello", b"hello").unwrap();
240 let data = crate::read(&dir, "hello").unwrap();
241 assert_eq!(data, b"hello");
242 }
243
244 #[test]
245 fn hash_write() {
246 let tmp = tempfile::tempdir().unwrap();
247 let dir = tmp.path().to_owned();
248 let original = format!("hello world{}", 5);
249 let integrity = crate::write_hash(&dir, &original)
250 .expect("should be able to write a hash synchronously");
251 let bytes = crate::read_hash(&dir, &integrity)
252 .expect("should be able to read the data we just wrote");
253 let result =
254 String::from_utf8(bytes).expect("we wrote valid utf8 but did not read valid utf8 back");
255 assert_eq!(result, original, "we did not read back what we wrote");
256 }
257}