Here is a fun one for the expert Rust heads to get their head around!
Essentially I want something like this pseudo macro code:
macro_rules! legacy_compat_retrieval {
($hash: ident, $fut: expr ) => {{
let processed = full_path_processed(hash);
// this match scrutinee shouldn't be hardcoded,
// but it depends on the path generated in the line above.
match self.retrieve(&processed).await {
Ok(file) => Ok(file),
Err(BlobStorageError::FileNotFound) => {
let original = full_path_original(hash);
// this match scrutinee shouldn't be hardcoded,
// but it depends on the path generated in the line above.
match self.retrieve(&original).await {
Ok(file) => Ok(file),
Err(BlobStorageError::FileNotFound) => {
let legacy = self.legacy_file_path(hash);
// the expr inside `Ok` shouldn't be hardcoded,
// but it depends on the path generated in the line above.
Ok(self.retrieve(&legacy).await?)
}
Err(e) => Err(e),
}
}
Err(e) => Err(e),
}
}};
}
where Instead of self.retrieve(&processed).await
I can plug in any async function that takes at least a path, but maybe more parameters.
So I got a version working with closures:
async fn legacy_compat_retrieval<T, F1, F2, F3, Fut1, Fut2, Fut3>(
&self,
hash: &Hash,
f1: F1,
f2: F2,
f3: F3,
) -> Result<T, BlobStorageError>
where
F1: FnOnce(String) -> Fut1,
F2: FnOnce(String) -> Fut2,
F3: FnOnce(String) -> Fut3,
Fut1: Future<Output = Result<T, BlobStorageError>>,
Fut2: Future<Output = Result<T, BlobStorageError>>,
Fut3: Future<Output = Result<T, BlobStorageError>>,
{
let processed = full_path_processed(hash);
match f1(processed).await {
Ok(file) => Ok(file),
Err(BlobStorageError::FileNotFound) => {
let original = full_path_original(hash);
match f2(original).await {
Ok(file) => Ok(file),
Err(BlobStorageError::FileNotFound) => {
let legacy = self.legacy_file_path(hash);
f3(legacy).await
}
Err(e) => Err(e),
}
}
Err(e) => Err(e),
}
}
And this works, when called like so:
pub async fn retrieve_file(&self, hash: &Hash) -> Result<Vec<u8>, BlobStorageError> {
self.legacy_compat_retrieval(
hash,
|path| async move { self.retrieve(&path).await },
|path| async move { self.retrieve(&path).await },
|path| async move { self.retrieve(&path).await },
)
.await
}
The problem is, I cant pass &mut into the closures. So this doesn't work
pub async fn retrieve_file_streaming<W>(
&self,
hash: &Hash,
writer: &mut W,
) -> Result<(), BlobStorageError>
where
W: AsyncWrite + Send + Unpin,
{
let w1 = writer;
let w2 = writer;
let w3 = writer;
self.legacy_compat_retrieval(
hash,
|path| async move { self.retrieve_streaming(&path, w1).await },
|path| async move { self.retrieve_streaming(&path, w2).await },
|path| async move { self.retrieve_streaming(&path, w3).await },
)
.await
}
Can anyone think of a way where I can refactor out the matching logic so i wouldn't have to WET the legacy compat logic everywhere?