source_window.rs (3479B)
1 use super::{Candidate, FetchCursor, RelayCursor, candidate_is_after_cursor, parse_cursor}; 2 3 const UNTIL_PREFIX: &str = "nostr-until-v1"; 4 5 /// Concrete-owner positions remain opaque to transport-neutral callers. 6 pub(super) enum Position { 7 After(RelayCursor), 8 Through(u64), 9 } 10 11 impl Position { 12 pub(super) fn parse( 13 cursor: &FetchCursor, 14 scope: &str, 15 ) -> Result<Self, radroots_transport::Error> { 16 if !cursor.as_str().starts_with(UNTIL_PREFIX) { 17 return parse_cursor(cursor, scope).map(Self::After); 18 } 19 let mut parts = cursor.as_str().split(':'); 20 let prefix = parts.next(); 21 let time = parts.next(); 22 let actual_scope = parts.next(); 23 let until = time.and_then(|value| value.parse::<u64>().ok()); 24 match (prefix, time, actual_scope, until, parts.next()) { 25 (Some(UNTIL_PREFIX), Some(time), Some(actual), Some(until), None) 26 if actual == scope && until.to_string() == time => 27 { 28 Ok(Self::Through(until)) 29 } 30 _ => Err(radroots_transport::Error::InvalidFetchCursor), 31 } 32 } 33 34 pub(super) fn until(&self) -> u64 { 35 match self { 36 Self::After(cursor) => cursor.created_at_unix_s(), 37 Self::Through(until) => *until, 38 } 39 } 40 41 pub(super) fn includes(&self, candidate: &Candidate) -> bool { 42 match self { 43 Self::After(cursor) => candidate_is_after_cursor(candidate, cursor), 44 Self::Through(until) => candidate.created_at <= *until, 45 } 46 } 47 } 48 49 pub(super) fn effective_until(selector: Option<u64>, position: Option<&Position>) -> Option<u64> { 50 match (selector, position) { 51 (Some(until), Some(position)) => Some(until.min(position.until())), 52 (Some(until), None) => Some(until), 53 (None, Some(position)) => Some(position.until()), 54 (None, None) => None, 55 } 56 } 57 58 pub(super) fn before_boundary(boundary: u64, scope: &str) -> Option<FetchCursor> { 59 let until = boundary.checked_sub(1)?; 60 FetchCursor::parse(format!("{UNTIL_PREFIX}:{until}:{scope}")).ok() 61 } 62 63 #[cfg(test)] 64 mod tests { 65 use super::*; 66 67 #[test] 68 fn older_positions_are_canonical_bounded_and_never_underflow() { 69 let scope = "a".repeat(64); 70 assert!(before_boundary(0, &scope).is_none()); 71 for boundary in [1, 100, u64::MAX] { 72 let cursor = before_boundary(boundary, &scope).unwrap(); 73 let position = Position::parse(&cursor, &scope).unwrap(); 74 assert_eq!(position.until(), boundary - 1); 75 assert_eq!(effective_until(None, Some(&position)), Some(boundary - 1)); 76 assert_eq!(effective_until(Some(0), Some(&position)), Some(0)); 77 } 78 assert_eq!(effective_until(Some(10), None), Some(10)); 79 assert_eq!(effective_until(None, None), None); 80 for value in ["", "-1", "+1", "01", "18446744073709551616"] { 81 let cursor = FetchCursor::parse(format!("{UNTIL_PREFIX}:{value}:{scope}")).unwrap(); 82 assert!(Position::parse(&cursor, &scope).is_err()); 83 } 84 for value in [ 85 format!("{UNTIL_PREFIX}:1:{}", "b".repeat(64)), 86 format!("{UNTIL_PREFIX}:1:{scope}:extra"), 87 format!("{UNTIL_PREFIX}:1"), 88 format!("{UNTIL_PREFIX}x:1:{scope}"), 89 ] { 90 assert!(Position::parse(&FetchCursor::parse(value).unwrap(), &scope).is_err()); 91 } 92 } 93 }