backend.rs (3697B)
1 //! Redacted SQL failure classification shared by the existing storage adapters. 2 3 use radroots_storage::Error; 4 5 pub(crate) fn map_backend(source: sqlx::Error) -> Error { 6 if is_capacity(&source) { 7 Error::SpaceInsufficient 8 } else { 9 Error::BackendUnavailable 10 } 11 } 12 13 pub(crate) fn is_capacity(source: &sqlx::Error) -> bool { 14 match source { 15 sqlx::Error::Database(error) => error 16 .code() 17 .and_then(|code| code.parse::<u32>().ok()) 18 .is_some_and(|code| code & 0xff == 13), 19 sqlx::Error::Io(error) => is_io_capacity(error), 20 _ => false, 21 } 22 } 23 24 pub(crate) fn is_io_capacity(source: &std::io::Error) -> bool { 25 matches!( 26 source.kind(), 27 std::io::ErrorKind::StorageFull | std::io::ErrorKind::QuotaExceeded 28 ) 29 } 30 31 pub(crate) fn startup_error(source: &sqlx::Error, fallback: crate::Error) -> crate::Error { 32 if is_capacity(source) { 33 crate::Error::SpaceInsufficient 34 } else { 35 fallback 36 } 37 } 38 39 #[cfg(test)] 40 mod tests { 41 use super::*; 42 use sqlx::error::{DatabaseError, ErrorKind}; 43 use std::{borrow::Cow, error::Error as StdError, fmt}; 44 45 #[derive(Debug)] 46 struct Coded(Option<&'static str>); 47 impl fmt::Display for Coded { 48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 f.write_str("private path and raw database detail") 50 } 51 } 52 impl StdError for Coded {} 53 impl DatabaseError for Coded { 54 fn message(&self) -> &str { 55 "private path and raw database detail" 56 } 57 fn code(&self) -> Option<Cow<'_, str>> { 58 self.0.map(Cow::Borrowed) 59 } 60 fn as_error(&self) -> &(dyn StdError + Send + Sync + 'static) { 61 self 62 } 63 fn as_error_mut(&mut self) -> &mut (dyn StdError + Send + Sync + 'static) { 64 self 65 } 66 fn into_error(self: Box<Self>) -> Box<dyn StdError + Send + Sync + 'static> { 67 self 68 } 69 fn kind(&self) -> ErrorKind { 70 ErrorKind::Other 71 } 72 } 73 74 #[test] 75 fn numeric_capacity_codes_are_redacted_and_other_database_errors_stay_generic() { 76 for code in [Some("13"), Some("269"), Some("525")] { 77 let mapped = map_backend(sqlx::Error::Database(Box::new(Coded(code)))); 78 assert_eq!(mapped, Error::SpaceInsufficient); 79 assert_eq!(mapped.to_string(), "storage space is insufficient"); 80 assert_eq!(format!("{mapped:?}"), "SpaceInsufficient"); 81 } 82 for code in [ 83 None, 84 Some(""), 85 Some("full"), 86 Some("-13"), 87 Some("4294967296"), 88 Some("11"), 89 Some("5"), 90 Some("10"), 91 ] { 92 assert_eq!( 93 map_backend(sqlx::Error::Database(Box::new(Coded(code)))), 94 Error::BackendUnavailable 95 ); 96 } 97 } 98 99 #[test] 100 fn typed_io_capacity_is_distinct_without_inventing_a_no_effect_receipt() { 101 use std::io::ErrorKind as Io; 102 for kind in [Io::StorageFull, Io::QuotaExceeded] { 103 let source = std::io::Error::new(kind, "private path and raw file detail"); 104 assert_eq!( 105 map_backend(sqlx::Error::Io(source)), 106 Error::SpaceInsufficient 107 ); 108 } 109 for kind in [Io::PermissionDenied, Io::Other] { 110 assert_eq!( 111 map_backend(sqlx::Error::Io(std::io::Error::from(kind))), 112 Error::BackendUnavailable 113 ); 114 } 115 assert_eq!( 116 map_backend(sqlx::Error::PoolClosed), 117 Error::BackendUnavailable 118 ); 119 } 120 }