lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

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 }