refactor: reduce map_err boilerplate with context helper methods (TODO-025)

Add per-crate variant_ctx() helper methods to StorageError, BlobCacheError,
ProtocolError, and BridgeError. Replace 78 .map_err(|e| format!(...))
closures with concise context calls. Identical error messages preserved.
This commit is contained in:
Edison Jwa
2026-06-11 12:25:00 +09:00
parent 1efeaac19d
commit 40883c40c8
4 changed files with 89 additions and 49 deletions
+6
View File
@@ -98,6 +98,12 @@ pub enum BridgeError {
Unmapped(String), Unmapped(String),
} }
impl BridgeError {
fn unmapped_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
BridgeError::Unmapped(format!("{ctx}: {e}"))
}
}
impl From<chanora_core::CoreError> for BridgeError { impl From<chanora_core::CoreError> for BridgeError {
fn from(e: chanora_core::CoreError) -> Self { fn from(e: chanora_core::CoreError) -> Self {
match e { match e {
+14 -8
View File
@@ -29,6 +29,12 @@ pub enum BlobCacheError {
InvalidKey(String), InvalidKey(String),
} }
impl BlobCacheError {
fn io_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
BlobCacheError::Io(format!("{ctx}: {e}"))
}
}
/// Content-addressed blob cache backed by cacache. /// Content-addressed blob cache backed by cacache.
pub struct BlobCache { pub struct BlobCache {
cache_dir: PathBuf, cache_dir: PathBuf,
@@ -51,7 +57,7 @@ impl BlobCache {
// cacache creates the directory on first write, but we create // cacache creates the directory on first write, but we create
// it eagerly so total_size() works before any writes. // it eagerly so total_size() works before any writes.
std::fs::create_dir_all(&cache_dir) std::fs::create_dir_all(&cache_dir)
.map_err(|e| BlobCacheError::Io(format!("mkdir cache: {e}")))?; .map_err(|e| BlobCacheError::io_ctx("mkdir cache", e))?;
Ok(Self { Ok(Self {
cache_dir, cache_dir,
max_bytes, max_bytes,
@@ -73,7 +79,7 @@ impl BlobCache {
let cache_key = format!("{prefix}{key}"); let cache_key = format!("{prefix}{key}");
cacache::write(&self.cache_dir, &cache_key, data) cacache::write(&self.cache_dir, &cache_key, data)
.await .await
.map_err(|e| BlobCacheError::Io(format!("cacache write: {e}")))?; .map_err(|e| BlobCacheError::io_ctx("cacache write", e))?;
Ok(()) Ok(())
} }
@@ -106,7 +112,7 @@ impl BlobCache {
let cache_key = format!("{prefix}{key}"); let cache_key = format!("{prefix}{key}");
cacache::remove(&self.cache_dir, &cache_key) cacache::remove(&self.cache_dir, &cache_key)
.await .await
.map_err(|e| BlobCacheError::Io(format!("cacache remove: {e}")))?; .map_err(|e| BlobCacheError::io_ctx("cacache remove", e))?;
Ok(()) Ok(())
} }
@@ -116,14 +122,14 @@ impl BlobCache {
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
if path.exists() { if path.exists() {
std::fs::remove_dir_all(&path) std::fs::remove_dir_all(&path)
.map_err(|e| BlobCacheError::Io(format!("clear cache: {e}")))?; .map_err(|e| BlobCacheError::io_ctx("clear cache", e))?;
std::fs::create_dir_all(&path) std::fs::create_dir_all(&path)
.map_err(|e| BlobCacheError::Io(format!("recreate cache dir: {e}")))?; .map_err(|e| BlobCacheError::io_ctx("recreate cache dir", e))?;
} }
Ok(()) Ok(())
}) })
.await .await
.map_err(|e| BlobCacheError::Io(format!("clear task: {e}")))? .map_err(|e| BlobCacheError::io_ctx("clear task", e))?
} }
/// Return total bytes used by all blobs. /// Return total bytes used by all blobs.
@@ -148,7 +154,7 @@ impl BlobCache {
Ok(total) Ok(total)
}) })
.await .await
.map_err(|e| BlobCacheError::Io(format!("total_size task: {e}")))? .map_err(|e| BlobCacheError::io_ctx("total_size task", e))?
} }
/// Evict oldest entries by timestamp until total size is under /// Evict oldest entries by timestamp until total size is under
@@ -198,7 +204,7 @@ impl BlobCache {
Ok(()) Ok(())
}) })
.await .await
.map_err(|e| BlobCacheError::Io(format!("evict task: {e}")))? .map_err(|e| BlobCacheError::io_ctx("evict task", e))?
} }
} }
+10
View File
@@ -120,3 +120,13 @@ pub enum ProtocolError {
#[error("file transfer failed: {0}")] #[error("file transfer failed: {0}")]
FileTransfer(String), FileTransfer(String),
} }
impl ProtocolError {
fn lost(msg: impl Into<String>) -> Self {
ProtocolError::Lost(msg.into())
}
fn backend_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
ProtocolError::Backend(format!("{ctx}: {e}"))
}
}
+59 -41
View File
@@ -79,8 +79,26 @@ pub enum StorageError {
Crypto(String), Crypto(String),
} }
impl StorageError {
fn io_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
StorageError::Io(format!("{ctx}: {e}"))
}
fn crypto_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
StorageError::Crypto(format!("{ctx}: {e}"))
}
fn sqlite_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
StorageError::Sqlite(format!("{ctx}: {e}"))
}
fn migration_ctx(ctx: impl std::fmt::Display, e: impl std::fmt::Display) -> Self {
StorageError::Migration(format!("{ctx}: {e}"))
}
}
fn ensure_dir(dir: &Path) -> Result<(), StorageError> { fn ensure_dir(dir: &Path) -> Result<(), StorageError> {
fs::create_dir_all(dir).map_err(|e| StorageError::Io(format!("mkdir {dir:?}: {e}"))) fs::create_dir_all(dir).map_err(|e| StorageError::io_ctx(format!("mkdir {dir:?}"), e))
} }
/// Audio-related per-identity settings persisted alongside the /// Audio-related per-identity settings persisted alongside the
@@ -246,7 +264,7 @@ impl IdentityFileStore {
Ok(b64) => { Ok(b64) => {
let bytes = base64::engine::general_purpose::STANDARD let bytes = base64::engine::general_purpose::STANDARD
.decode(b64.as_bytes()) .decode(b64.as_bytes())
.map_err(|e| StorageError::Crypto(format!("keyring dek decode: {e}")))?; .map_err(|e| StorageError::crypto_ctx("keyring dek decode", e))?;
if bytes.len() != 32 { if bytes.len() != 32 {
return Err(StorageError::Crypto(format!( return Err(StorageError::Crypto(format!(
"keyring dek length {} (expected 32)", "keyring dek length {} (expected 32)",
@@ -359,9 +377,9 @@ impl IdentityFileStore {
let _ = self.keyring_save(&key); let _ = self.keyring_save(&key);
let mut f = open_private(&self.dek_path)?; let mut f = open_private(&self.dek_path)?;
f.write_all(&key) f.write_all(&key)
.map_err(|e| StorageError::Io(format!("write dek: {e}")))?; .map_err(|e| StorageError::io_ctx("write dek", e))?;
f.sync_all() f.sync_all()
.map_err(|e| StorageError::Io(format!("sync dek: {e}")))?; .map_err(|e| StorageError::io_ctx("sync dek", e))?;
info!(target: "chanora_storage", path = ?self.dek_path, "DEK generated (file fallback)"); info!(target: "chanora_storage", path = ?self.dek_path, "DEK generated (file fallback)");
key.zeroize(); key.zeroize();
Ok(()) Ok(())
@@ -397,11 +415,11 @@ impl IdentityFileStore {
let mut f = match fs::File::open(&self.path) { let mut f = match fs::File::open(&self.path) {
Ok(f) => f, Ok(f) => f,
Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None), Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(StorageError::Io(format!("open {:?}: {e}", self.path))), Err(e) => return Err(StorageError::io_ctx(format!("open {:?}", self.path), e)),
}; };
let mut buf = Vec::new(); let mut buf = Vec::new();
f.read_to_end(&mut buf) f.read_to_end(&mut buf)
.map_err(|e| StorageError::Io(format!("read {:?}: {e}", self.path)))?; .map_err(|e| StorageError::io_ctx(format!("read {:?}", self.path), e))?;
if buf.is_empty() { if buf.is_empty() {
return Ok(None); return Ok(None);
} }
@@ -420,11 +438,11 @@ impl IdentityFileStore {
let nonce = Nonce::from_slice(nonce_bytes); let nonce = Nonce::from_slice(nonce_bytes);
let pt = cipher.decrypt(nonce, &buf[12..]).map_err(|e| { let pt = cipher.decrypt(nonce, &buf[12..]).map_err(|e| {
key_bytes.zeroize(); key_bytes.zeroize();
StorageError::Crypto(format!("decrypt: {e}")) StorageError::crypto_ctx("decrypt", e)
})?; })?;
key_bytes.zeroize(); key_bytes.zeroize();
let s = String::from_utf8(pt) let s = String::from_utf8(pt)
.map_err(|e| StorageError::Crypto(format!("plaintext not utf8: {e}")))?; .map_err(|e| StorageError::crypto_ctx("plaintext not utf8", e))?;
let trimmed = s.trim().to_string(); let trimmed = s.trim().to_string();
if trimmed.is_empty() { if trimmed.is_empty() {
return Ok(None); return Ok(None);
@@ -440,7 +458,7 @@ impl IdentityFileStore {
"identity file is in legacy plaintext format; will encrypt on next save" "identity file is in legacy plaintext format; will encrypt on next save"
); );
let s = String::from_utf8(buf) let s = String::from_utf8(buf)
.map_err(|e| StorageError::Io(format!("legacy not utf8: {e}")))?; .map_err(|e| StorageError::io_ctx("legacy not utf8", e))?;
let trimmed = s.trim().to_string(); let trimmed = s.trim().to_string();
if trimmed.is_empty() { if trimmed.is_empty() {
Ok(None) Ok(None)
@@ -462,7 +480,7 @@ impl IdentityFileStore {
let nonce = Nonce::from_slice(&nonce_bytes); let nonce = Nonce::from_slice(&nonce_bytes);
let ct = cipher.encrypt(nonce, plaintext).map_err(|e| { let ct = cipher.encrypt(nonce, plaintext).map_err(|e| {
key_bytes.zeroize(); key_bytes.zeroize();
StorageError::Crypto(format!("encrypt: {e}")) StorageError::crypto_ctx("encrypt", e)
})?; })?;
key_bytes.zeroize(); key_bytes.zeroize();
@@ -471,14 +489,14 @@ impl IdentityFileStore {
{ {
let mut f = open_private(&tmp)?; let mut f = open_private(&tmp)?;
f.write_all(&nonce_bytes) f.write_all(&nonce_bytes)
.map_err(|e| StorageError::Io(format!("write nonce: {e}")))?; .map_err(|e| StorageError::io_ctx("write nonce", e))?;
f.write_all(&ct) f.write_all(&ct)
.map_err(|e| StorageError::Io(format!("write ct: {e}")))?; .map_err(|e| StorageError::io_ctx("write ct", e))?;
f.sync_all() f.sync_all()
.map_err(|e| StorageError::Io(format!("sync {tmp:?}: {e}")))?; .map_err(|e| StorageError::io_ctx(format!("sync {tmp:?}"), e))?;
} }
fs::rename(&tmp, &self.path) fs::rename(&tmp, &self.path)
.map_err(|e| StorageError::Io(format!("rename {tmp:?} -> {:?}: {e}", self.path)))?; .map_err(|e| StorageError::io_ctx(format!("rename {tmp:?} -> {:?}", self.path), e))?;
info!(target: "chanora_storage", path = ?self.path, "identity persisted (encrypted)"); info!(target: "chanora_storage", path = ?self.path, "identity persisted (encrypted)");
Ok(()) Ok(())
} }
@@ -507,17 +525,17 @@ impl IdentityFileStore {
let path = self.meta_path(); let path = self.meta_path();
let tmp = path.with_extension("json.tmp"); let tmp = path.with_extension("json.tmp");
let body = serde_json::to_vec_pretty(m) let body = serde_json::to_vec_pretty(m)
.map_err(|e| StorageError::Io(format!("meta serialize: {e}")))?; .map_err(|e| StorageError::io_ctx("meta serialize", e))?;
{ {
let mut f = fs::File::create(&tmp) let mut f = fs::File::create(&tmp)
.map_err(|e| StorageError::Io(format!("open meta {tmp:?}: {e}")))?; .map_err(|e| StorageError::io_ctx(format!("open meta {tmp:?}"), e))?;
f.write_all(&body) f.write_all(&body)
.map_err(|e| StorageError::Io(format!("write meta: {e}")))?; .map_err(|e| StorageError::io_ctx("write meta", e))?;
f.sync_all() f.sync_all()
.map_err(|e| StorageError::Io(format!("sync meta: {e}")))?; .map_err(|e| StorageError::io_ctx("sync meta", e))?;
} }
fs::rename(&tmp, &path) fs::rename(&tmp, &path)
.map_err(|e| StorageError::Io(format!("rename meta {tmp:?} -> {path:?}: {e}")))?; .map_err(|e| StorageError::io_ctx(format!("rename meta {tmp:?} -> {path:?}"), e))?;
Ok(()) Ok(())
} }
@@ -623,10 +641,10 @@ fn is_plausibly_legacy_plaintext(buf: &[u8]) -> bool {
/// and the legacy migration path inside `ensure_dek`. /// and the legacy migration path inside `ensure_dek`.
fn read_file_dek(path: &Path) -> Result<[u8; 32], StorageError> { fn read_file_dek(path: &Path) -> Result<[u8; 32], StorageError> {
let mut f = let mut f =
fs::File::open(path).map_err(|e| StorageError::Io(format!("open dek {:?}: {e}", path)))?; fs::File::open(path).map_err(|e| StorageError::io_ctx(format!("open dek {path:?}"), e))?;
let mut key = [0u8; 32]; let mut key = [0u8; 32];
f.read_exact(&mut key) f.read_exact(&mut key)
.map_err(|e| StorageError::Io(format!("read dek: {e}")))?; .map_err(|e| StorageError::io_ctx("read dek", e))?;
Ok(key) Ok(key)
} }
@@ -656,7 +674,7 @@ fn open_private(p: &Path) -> Result<fs::File, StorageError> {
.truncate(true) .truncate(true)
.mode(0o600) .mode(0o600)
.open(p) .open(p)
.map_err(|e| StorageError::Io(format!("open {p:?}: {e}"))) .map_err(|e| StorageError::io_ctx(format!("open {p:?}"), e))
} }
#[cfg(not(unix))] #[cfg(not(unix))]
@@ -670,7 +688,7 @@ fn open_private(p: &Path) -> Result<fs::File, StorageError> {
.write(true) .write(true)
.truncate(true) .truncate(true)
.open(p) .open(p)
.map_err(|e| StorageError::Io(format!("open {p:?}: {e}"))) .map_err(|e| StorageError::io_ctx(format!("open {p:?}"), e))
} }
/// Public abstraction over the per-install envelope-encryption /// Public abstraction over the per-install envelope-encryption
@@ -725,7 +743,7 @@ impl DekCrypto {
let nonce = Nonce::from_slice(&nonce_bytes); let nonce = Nonce::from_slice(&nonce_bytes);
let ct = cipher let ct = cipher
.encrypt(nonce, plaintext) .encrypt(nonce, plaintext)
.map_err(|e| StorageError::Crypto(format!("encrypt: {e}")))?; .map_err(|e| StorageError::crypto_ctx("encrypt", e))?;
let mut out = Vec::with_capacity(12 + ct.len()); let mut out = Vec::with_capacity(12 + ct.len());
out.extend_from_slice(&nonce_bytes); out.extend_from_slice(&nonce_bytes);
out.extend_from_slice(&ct); out.extend_from_slice(&ct);
@@ -744,7 +762,7 @@ impl DekCrypto {
let nonce = Nonce::from_slice(&blob[..12]); let nonce = Nonce::from_slice(&blob[..12]);
cipher cipher
.decrypt(nonce, &blob[12..]) .decrypt(nonce, &blob[12..])
.map_err(|e| StorageError::Crypto(format!("decrypt: {e}"))) .map_err(|e| StorageError::crypto_ctx("decrypt", e))
} }
} }
@@ -821,9 +839,9 @@ impl BookmarkRepository {
ensure_dir(dir)?; ensure_dir(dir)?;
let path = dir.join("chanora.db"); let path = dir.join("chanora.db");
let conn = Connection::open(&path) let conn = Connection::open(&path)
.map_err(|e| StorageError::Sqlite(format!("open {path:?}: {e}")))?; .map_err(|e| StorageError::sqlite_ctx(format!("open {path:?}"), e))?;
conn.pragma_update(None, "foreign_keys", "ON") conn.pragma_update(None, "foreign_keys", "ON")
.map_err(|e| StorageError::Sqlite(format!("pragma: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("pragma", e))?;
conn.execute_batch( conn.execute_batch(
"CREATE TABLE IF NOT EXISTS bookmarks ( "CREATE TABLE IF NOT EXISTS bookmarks (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -837,7 +855,7 @@ impl BookmarkRepository {
); );
INSERT OR IGNORE INTO schema_version(v) VALUES (1);", INSERT OR IGNORE INTO schema_version(v) VALUES (1);",
) )
.map_err(|e| StorageError::Migration(format!("init schema: {e}")))?; .map_err(|e| StorageError::migration_ctx("init schema", e))?;
// Schema v2 migration: encrypted password column. Idempotent. // Schema v2 migration: encrypted password column. Idempotent.
let has_blob: i64 = conn let has_blob: i64 = conn
.query_row( .query_row(
@@ -845,12 +863,12 @@ impl BookmarkRepository {
[], [],
|r| r.get(0), |r| r.get(0),
) )
.map_err(|e| StorageError::Migration(format!("table_info: {e}")))?; .map_err(|e| StorageError::migration_ctx("table_info", e))?;
if has_blob == 0 { if has_blob == 0 {
conn.execute("ALTER TABLE bookmarks ADD COLUMN password_blob BLOB", []) conn.execute("ALTER TABLE bookmarks ADD COLUMN password_blob BLOB", [])
.map_err(|e| StorageError::Migration(format!("add password_blob: {e}")))?; .map_err(|e| StorageError::migration_ctx("add password_blob", e))?;
conn.execute("INSERT OR IGNORE INTO schema_version(v) VALUES (2)", []) conn.execute("INSERT OR IGNORE INTO schema_version(v) VALUES (2)", [])
.map_err(|e| StorageError::Migration(format!("bump version: {e}")))?; .map_err(|e| StorageError::migration_ctx("bump version", e))?;
info!(target: "chanora_storage", "bookmark db migrated to v2 (password_blob)"); info!(target: "chanora_storage", "bookmark db migrated to v2 (password_blob)");
} }
info!(target: "chanora_storage", path = ?path, "bookmark db opened"); info!(target: "chanora_storage", path = ?path, "bookmark db opened");
@@ -886,7 +904,7 @@ impl BookmarkRepository {
"INSERT INTO bookmarks (display_name, host, nickname, password, password_blob) VALUES (?1, ?2, ?3, ?4, ?5)", "INSERT INTO bookmarks (display_name, host, nickname, password, password_blob) VALUES (?1, ?2, ?3, ?4, ?5)",
params![b.display_name, b.host, b.nickname, plain, blob], params![b.display_name, b.host, b.nickname, plain, blob],
) )
.map_err(|e| StorageError::Sqlite(format!("insert: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("insert", e))?;
Ok(conn.last_insert_rowid()) Ok(conn.last_insert_rowid())
} }
@@ -915,20 +933,20 @@ impl BookmarkRepository {
|row| row.get(0), |row| row.get(0),
) )
.optional() .optional()
.map_err(|e| StorageError::Sqlite(format!("select: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("select", e))?;
if let Some(id) = existing { if let Some(id) = existing {
conn.execute( conn.execute(
"UPDATE bookmarks SET nickname = ?1, password = ?2, password_blob = ?3 WHERE id = ?4", "UPDATE bookmarks SET nickname = ?1, password = ?2, password_blob = ?3 WHERE id = ?4",
params![b.nickname, plain, blob, id], params![b.nickname, plain, blob, id],
) )
.map_err(|e| StorageError::Sqlite(format!("update: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("update", e))?;
Ok(id) Ok(id)
} else { } else {
conn.execute( conn.execute(
"INSERT INTO bookmarks (display_name, host, nickname, password, password_blob) VALUES (?1, ?2, ?3, ?4, ?5)", "INSERT INTO bookmarks (display_name, host, nickname, password, password_blob) VALUES (?1, ?2, ?3, ?4, ?5)",
params![b.display_name, b.host, b.nickname, plain, blob], params![b.display_name, b.host, b.nickname, plain, blob],
) )
.map_err(|e| StorageError::Sqlite(format!("insert: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("insert", e))?;
Ok(conn.last_insert_rowid()) Ok(conn.last_insert_rowid())
} }
} }
@@ -957,7 +975,7 @@ impl BookmarkRepository {
"UPDATE bookmarks SET display_name=?1, host=?2, nickname=?3, password=?4, password_blob=?5 WHERE id=?6", "UPDATE bookmarks SET display_name=?1, host=?2, nickname=?3, password=?4, password_blob=?5 WHERE id=?6",
params![b.display_name, b.host, b.nickname, plain, blob, b.id], params![b.display_name, b.host, b.nickname, plain, blob, b.id],
) )
.map_err(|e| StorageError::Sqlite(format!("update: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("update", e))?;
if n == 0 { if n == 0 {
Err(StorageError::NotFound) Err(StorageError::NotFound)
} else { } else {
@@ -972,7 +990,7 @@ impl BookmarkRepository {
.lock() .lock()
.map_err(|_| StorageError::Sqlite("poisoned lock".to_string()))?; .map_err(|_| StorageError::Sqlite("poisoned lock".to_string()))?;
conn.execute("DELETE FROM bookmarks WHERE id = ?1", params![id]) conn.execute("DELETE FROM bookmarks WHERE id = ?1", params![id])
.map_err(|e| StorageError::Sqlite(format!("delete: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("delete", e))?;
Ok(()) Ok(())
} }
@@ -988,7 +1006,7 @@ impl BookmarkRepository {
.prepare( .prepare(
"SELECT id, display_name, host, nickname, password, password_blob FROM bookmarks ORDER BY id", "SELECT id, display_name, host, nickname, password, password_blob FROM bookmarks ORDER BY id",
) )
.map_err(|e| StorageError::Sqlite(format!("prepare: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("prepare", e))?;
let rows = stmt let rows = stmt
.query_map([], |row| { .query_map([], |row| {
let id: i64 = row.get(0)?; let id: i64 = row.get(0)?;
@@ -999,15 +1017,15 @@ impl BookmarkRepository {
let blob: Option<Vec<u8>> = row.get(5)?; let blob: Option<Vec<u8>> = row.get(5)?;
Ok((id, display_name, host, nickname, plain, blob)) Ok((id, display_name, host, nickname, plain, blob))
}) })
.map_err(|e| StorageError::Sqlite(format!("query: {e}")))?; .map_err(|e| StorageError::sqlite_ctx("query", e))?;
let mut out = Vec::new(); let mut out = Vec::new();
for r in rows { for r in rows {
let (id, display_name, host, nickname, plain, blob) = let (id, display_name, host, nickname, plain, blob) =
r.map_err(|e| StorageError::Sqlite(format!("row: {e}")))?; r.map_err(|e| StorageError::sqlite_ctx("row", e))?;
let password = match (blob.as_ref(), self.crypto.as_ref()) { let password = match (blob.as_ref(), self.crypto.as_ref()) {
(Some(b), Some(c)) => Some( (Some(b), Some(c)) => Some(
String::from_utf8(c.decrypt(b)?) String::from_utf8(c.decrypt(b)?)
.map_err(|e| StorageError::Crypto(format!("blob utf8: {e}")))?, .map_err(|e| StorageError::crypto_ctx("blob utf8", e))?,
), ),
(Some(_), None) => { (Some(_), None) => {
// We have an encrypted blob but no key. Skip the // We have an encrypted blob but no key. Skip the