Constellation, Spacedust, Slingshot, UFOs: atproto crates and services for microcosm
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: null-terminated encoiding for NSID

also actually test that we can prefix against a null-terminated string

this is probably still going to be annoying for building full db-concat'ed keys, but hopefully not too bad

+87 -29
+87 -29
ufos/src/db_types.rs
··· 58 58 } 59 59 60 60 pub trait DbBytes { 61 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError>; 61 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>>; 62 62 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> 63 63 where 64 64 Self: Sized; 65 + } 66 + 67 + pub trait SubPrefixBytes<T> { 68 + fn sub_prefix(input: T) -> EncodingResult<Vec<u8>>; 65 69 } 66 70 67 71 #[derive(PartialEq)] ··· 76 80 pub fn from_pair(prefix: P, suffix: S) -> Self { 77 81 Self { prefix, suffix } 78 82 } 79 - pub fn from_prefix_to_db_bytes(prefix: &P) -> Result<Vec<u8>, EncodingError> { 83 + pub fn from_prefix_to_db_bytes(prefix: &P) -> EncodingResult<Vec<u8>> { 80 84 prefix.to_db_bytes() 81 85 } 82 - pub fn to_prefix_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 86 + pub fn to_prefix_db_bytes(&self) -> EncodingResult<Vec<u8>> { 83 87 self.prefix.to_db_bytes() 84 88 } 85 - pub fn prefix_range_end(prefix: &P) -> Result<Vec<u8>, EncodingError> { 89 + pub fn prefix_range_end(prefix: &P) -> EncodingResult<Vec<u8>> { 86 90 let prefix_bytes = prefix.to_db_bytes()?; 87 91 let (_, Bound::Excluded(range_end)) = prefix_to_range(&prefix_bytes) else { 88 92 return Err(EncodingError::BadRangeBound); 89 93 }; 90 94 Ok(range_end.to_vec()) 91 95 } 92 - pub fn range_end(&self) -> Result<Vec<u8>, EncodingError> { 96 + pub fn range_end(&self) -> EncodingResult<Vec<u8>> { 93 97 Self::prefix_range_end(&self.prefix) 94 98 } 95 99 pub fn range(&self) -> Result<Range<Vec<u8>>, EncodingError> { ··· 120 124 } 121 125 122 126 impl<P: DbBytes, S: DbBytes> DbBytes for DbConcat<P, S> { 123 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 127 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 124 128 let mut combined = self.prefix.to_db_bytes()?; 125 129 combined.append(&mut self.suffix.to_db_bytes()?); 126 130 Ok(combined) ··· 156 160 #[derive(Debug, Default, PartialEq)] 157 161 pub struct DbEmpty(()); 158 162 impl DbBytes for DbEmpty { 159 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 163 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 160 164 Ok(vec![]) 161 165 } 162 166 fn from_db_bytes(_: &[u8]) -> Result<(Self, usize), EncodingError> { ··· 185 189 } 186 190 } 187 191 impl<S: StaticStr> DbBytes for DbStaticStr<S> { 188 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 192 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 189 193 S::static_str().to_string().to_db_bytes() 190 194 } 191 195 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { ··· 212 216 where 213 217 T: BincodeEncode + BincodeDecode<()> + UseBincodePlz + Sized + std::fmt::Debug, 214 218 { 215 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 219 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 216 220 Ok(encode_to_vec(self, bincode_conf())?) 217 221 } 218 222 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { ··· 222 226 223 227 /// helper trait: impl on a type to get helpers to implement DbBytes 224 228 pub trait SerdeBytes: serde::Serialize + for<'a> serde::Deserialize<'a> { 225 - fn to_bytes(&self) -> Result<Vec<u8>, EncodingError> 229 + fn to_bytes(&self) -> EncodingResult<Vec<u8>> 226 230 where 227 231 Self: std::fmt::Debug, 228 232 { ··· 238 242 impl<const N: usize> UseBincodePlz for [u8; N] {} 239 243 240 244 impl DbBytes for Vec<u8> { 241 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 245 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 242 246 Ok(self.to_vec()) 243 247 } 244 248 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { ··· 256 260 /// TODO: wrap in another type. it's actually probably not desirable to serialize strings this way 257 261 /// *except* where needed as a prefix. 258 262 impl DbBytes for String { 259 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 263 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 260 264 let mut v = self.as_bytes().to_vec(); 261 265 if v.contains(&0x00) { 262 266 return Err(EncodingError::StringContainedNull); ··· 276 280 } 277 281 } 278 282 283 + impl SubPrefixBytes<&str> for String { 284 + fn sub_prefix(input: &str) -> EncodingResult<Vec<u8>> { 285 + let v = input.as_bytes(); 286 + if v.contains(&0x00) { 287 + return Err(EncodingError::StringContainedNull); 288 + } 289 + // NO null terminator!! 290 + Ok(v.to_vec()) 291 + } 292 + } 293 + 279 294 impl DbBytes for Did { 280 295 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { 281 296 let (s, n) = decode_from_slice(bytes, bincode_conf())?; 282 297 let me = Self::new(s).map_err(EncodingError::BadAtriumStringType)?; 283 298 Ok((me, n)) 284 299 } 285 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 300 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 286 301 Ok(encode_to_vec(self.as_ref(), bincode_conf())?) 287 302 } 288 303 } 289 304 290 - // BUG: this needs to use the null-terminating string thing!!!!!!!!!!!!!! the whole point of all of this!!!! 291 305 impl DbBytes for Nsid { 292 306 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { 293 - let (s, n) = decode_from_slice(bytes, bincode_conf())?; 307 + let (s, n) = String::from_db_bytes(bytes)?; // null-terminated DbBytes impl!! 294 308 let me = Self::new(s).map_err(EncodingError::BadAtriumStringType)?; 295 309 Ok((me, n)) 296 310 } 297 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 298 - Ok(encode_to_vec(self.as_ref(), bincode_conf())?) 311 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 312 + Ok(String::to_db_bytes(&self.to_string())?) // null-terminated DbBytes impl!!!! 313 + } 314 + } 315 + impl SubPrefixBytes<&str> for Nsid { 316 + fn sub_prefix(input: &str) -> EncodingResult<Vec<u8>> { 317 + String::sub_prefix(input) 299 318 } 300 319 } 301 320 ··· 305 324 let me = Self::new(s).map_err(EncodingError::BadAtriumStringType)?; 306 325 Ok((me, n)) 307 326 } 308 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 327 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 309 328 Ok(encode_to_vec(self.as_ref(), bincode_conf())?) 310 329 } 311 330 } 312 331 313 332 impl DbBytes for Cursor { 314 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 333 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 315 334 Ok(self.to_raw_u64().to_be_bytes().to_vec()) 316 335 } 317 336 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { ··· 325 344 } 326 345 327 346 impl DbBytes for serde_json::Value { 328 - fn to_db_bytes(&self) -> Result<Vec<u8>, EncodingError> { 347 + fn to_db_bytes(&self) -> EncodingResult<Vec<u8>> { 329 348 self.to_string().to_db_bytes() 330 349 } 331 350 fn from_db_bytes(bytes: &[u8]) -> Result<(Self, usize), EncodingError> { ··· 345 364 346 365 #[cfg(test)] 347 366 mod test { 348 - use super::{Cursor, DbBytes, DbConcat, DbEmpty, DbStaticStr, EncodingError, StaticStr}; 367 + use super::{ 368 + Cursor, DbBytes, DbConcat, DbEmpty, DbStaticStr, EncodingResult, Nsid, StaticStr, 369 + SubPrefixBytes, 370 + }; 349 371 350 372 #[test] 351 - fn test_db_empty() -> Result<(), EncodingError> { 373 + fn test_db_empty() -> EncodingResult<()> { 352 374 let original = DbEmpty::default(); 353 375 let serialized = original.to_db_bytes()?; 354 376 assert_eq!(serialized.len(), 0); ··· 359 381 } 360 382 361 383 #[test] 362 - fn test_string_roundtrip() -> Result<(), EncodingError> { 384 + fn test_string_roundtrip() -> EncodingResult<()> { 363 385 for (case, desc) in [ 364 386 ("", "empty string"), 365 387 ("a", "basic string"), ··· 378 400 } 379 401 380 402 #[test] 381 - fn test_string_serialized_lexicographic_sort() -> Result<(), EncodingError> { 403 + fn test_string_serialized_lexicographic_sort() -> EncodingResult<()> { 382 404 let aa = "aa".to_string().to_db_bytes()?; 383 405 let b = "b".to_string().to_db_bytes()?; 384 406 assert!(b > aa); ··· 386 408 } 387 409 388 410 #[test] 389 - fn test_string_cursor_prefix_roundtrip() -> Result<(), EncodingError> { 411 + fn test_nullstring_can_prefix() -> EncodingResult<()> { 412 + for (s, pre, is_pre, desc) in [ 413 + ("", "", true, "empty strings"), 414 + ("", "a", false, "longer prefix"), 415 + ("a", "", true, "empty prefix matches"), 416 + ("a", "a", true, "whole string matches"), 417 + ("a", "b", false, "entirely different"), 418 + ("ab", "a", true, "prefix matches"), 419 + ("ab", "b", false, "shorter and entirely different"), 420 + ] { 421 + let serialized = s.to_string().to_db_bytes()?; 422 + let prefixed = String::sub_prefix(pre)?; 423 + assert_eq!(serialized.starts_with(&prefixed), is_pre, "{}", desc); 424 + } 425 + Ok(()) 426 + } 427 + 428 + #[test] 429 + fn test_nsid_can_prefix() -> EncodingResult<()> { 430 + for (s, pre, is_pre, desc) in [ 431 + ("ab.cd.ef", "", true, "empty prefix"), 432 + ("ab.cd.ef", "a", true, "tiny prefix"), 433 + ("ab.cd.ef", "abc", false, "bad prefix"), 434 + ("ab.cd.ef", "ab", true, "segment prefix"), 435 + ("ab.cd.ef", "ab.cd", true, "multi-segment prefix"), 436 + ("ab.cd.ef", "ab.cd.ef", true, "full match"), 437 + ("ab.cd.ef", "ab.cd.ef.g", false, "prefix longer"), 438 + ] { 439 + let serialized = Nsid::new(s.to_string()).unwrap().to_db_bytes()?; 440 + let prefixed = Nsid::sub_prefix(pre)?; 441 + assert_eq!(serialized.starts_with(&prefixed), is_pre, "{}", desc); 442 + } 443 + Ok(()) 444 + } 445 + 446 + #[test] 447 + fn test_string_cursor_prefix_roundtrip() -> EncodingResult<()> { 390 448 type TwoThings = DbConcat<String, Cursor>; 391 449 for (lazy_prefix, tired_suffix, desc) in [ 392 450 ("", 0, "empty string and cursor"), ··· 411 469 } 412 470 413 471 #[test] 414 - fn test_cursor_string_prefix_roundtrip() -> Result<(), EncodingError> { 472 + fn test_cursor_string_prefix_roundtrip() -> EncodingResult<()> { 415 473 type TwoThings = DbConcat<Cursor, String>; 416 474 for (tired_prefix, sad_suffix, desc) in [ 417 475 (0, "", "empty string and cursor"), ··· 436 494 } 437 495 438 496 #[test] 439 - fn test_static_str() -> Result<(), EncodingError> { 497 + fn test_static_str() -> EncodingResult<()> { 440 498 #[derive(Debug, PartialEq)] 441 499 struct AStaticStr {} 442 500 impl StaticStr for AStaticStr { ··· 457 515 } 458 516 459 517 #[test] 460 - fn test_static_str_empty() -> Result<(), EncodingError> { 518 + fn test_static_str_empty() -> EncodingResult<()> { 461 519 #[derive(Debug, PartialEq)] 462 520 struct AnEmptyStr {} 463 521 impl StaticStr for AnEmptyStr { ··· 477 535 } 478 536 479 537 #[test] 480 - fn test_static_prefix() -> Result<(), EncodingError> { 538 + fn test_static_prefix() -> EncodingResult<()> { 481 539 #[derive(Debug, PartialEq)] 482 540 struct AStaticPrefix {} 483 541 impl StaticStr for AStaticPrefix {