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

Merge pull request #25 from at-microcosm/nsid-prefix

failing test: panic on unwrap

Changed files
+355 -17
ufos
+9 -3
ufos/src/lib.rs
···
},
}
-
#[derive(Debug, Serialize, JsonSchema)]
+
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct NsidCount {
nsid: String,
creates: u64,
···
dids_estimate: u64,
}
-
#[derive(Debug, Serialize, JsonSchema)]
+
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
pub struct PrefixCount {
prefix: String,
creates: u64,
···
dids_estimate: u64,
}
-
#[derive(Debug, Serialize, JsonSchema)]
+
#[derive(Debug, PartialEq, Serialize, JsonSchema)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum PrefixChild {
Collection(NsidCount),
···
#[derive(Debug, Serialize, JsonSchema)]
pub struct NsidPrefix(String);
impl NsidPrefix {
+
/// Input must not include a trailing dot.
pub fn new(pre: &str) -> EncodingResult<Self> {
// it's a valid prefix if appending `.name` makes it a valid NSID
Nsid::new(format!("{pre}.name")).map_err(EncodingError::BadAtriumStringType)?;
···
);
self.0 == other.domain_authority()
}
+
/// The prefix as initialized (no trailing dot)
pub fn as_str(&self) -> &str {
self.0.as_str()
+
}
+
/// The prefix with a trailing `.` appended to avoid matching a longer segment
+
pub fn terminated(&self) -> String {
+
format!("{}.", self.0)
}
}
+346 -14
ufos/src/storage_fjall.rs
···
cursor: Option<Vec<u8>>,
buckets: Vec<CursorBucket>,
) -> StorageResult<(JustCount, Vec<PrefixChild>, Option<Vec<u8>>)> {
-
let prefix_sub = String::sub_prefix(prefix.as_str())?;
+
// let prefix_sub_with_null = prefix.as_str().to_string().to_db_bytes()?;
+
let prefix_sub = String::sub_prefix(&prefix.terminated())?; // with trailing dot to ensure full segment match
let cursor_child = cursor
.as_deref()
.map(|encoded_bytes| {
let decoded: String = db_complete(encoded_bytes)?;
-
let as_sub_prefix = String::sub_prefix(&decoded)?;
-
Ok::<_, EncodingError>(as_sub_prefix)
+
// TODO: write some tests for cursors, there's probably bugs here
+
let as_sub_prefix_with_null = decoded.to_db_bytes()?;
+
Ok::<_, EncodingError>(as_sub_prefix_with_null)
})
.transpose()?;
let mut iters: Vec<NsidCounter> = Vec::with_capacity(buckets.len());
···
let mut iters: Vec<_> = iters
.into_iter()
.map(|it| {
-
it.map(|bla| bla.map(|(nsid, v)| (Child::from_prefix(&nsid, &prefix), v)))
-
.peekable()
+
it.map(|bla| {
+
bla.map(|(nsid, v)| {
+
let Some(child) = Child::from_prefix(&nsid, &prefix) else {
+
panic!("failed from_prefix: {nsid:?} {prefix:?} (bad iter bounds?)");
+
};
+
(child, v)
+
})
+
})
+
.peekable()
})
.collect();
···
ChildPrefix(String),
}
impl Child {
-
fn from_prefix(nsid: &Nsid, prefix: &NsidPrefix) -> Self {
+
fn from_prefix(nsid: &Nsid, prefix: &NsidPrefix) -> Option<Self> {
if prefix.is_group_of(nsid) {
-
Child::FullNsid(nsid.to_string())
-
} else {
-
let suffix = nsid
-
.as_str()
-
.strip_prefix(&format!("{}.", prefix.0))
-
.unwrap();
-
let (segment, _) = suffix.split_once('.').unwrap();
-
Child::ChildPrefix(format!("{}.{segment}", prefix.0))
+
return Some(Child::FullNsid(nsid.to_string()));
}
+
let suffix = nsid.as_str().strip_prefix(&format!("{}.", prefix.0))?;
+
let (segment, _) = suffix.split_once('.').unwrap();
+
let child_prefix = format!("{}.{segment}", prefix.0);
+
Some(Child::ChildPrefix(child_prefix))
}
fn is_before(&self, other: &Child) -> bool {
match (self, other) {
···
let (n, _) = write.step_rollup()?;
assert_eq!(n, 0);
+
Ok(())
+
}
+
+
#[test]
+
fn get_prefix_children_lexi_empty() {
+
let (read, _) = fjall_db();
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read
+
.get_prefix(
+
NsidPrefix::new("aaa.aaa").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)
+
.unwrap();
+
+
assert_eq!(creates, 0);
+
assert_eq!(dids_estimate, 0);
+
assert_eq!(children, vec![]);
+
assert_eq!(cursor, None);
+
}
+
+
#[test]
+
fn get_prefix_excludes_exact_collection() -> anyhow::Result<()> {
+
let (read, mut write) = fjall_db();
+
+
let mut batch = TestBatch::default();
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a",
+
"rkey-aaa",
+
"{}",
+
Some("rev-aaa"),
+
None,
+
10_000,
+
);
+
write.insert_batch(batch.batch)?;
+
write.step_rollup()?;
+
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read.get_prefix(
+
NsidPrefix::new("a.a.a").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)?;
+
assert_eq!(creates, 0);
+
assert_eq!(dids_estimate, 0);
+
assert_eq!(children, vec![]);
+
assert_eq!(cursor, None);
+
Ok(())
+
}
+
+
#[test]
+
fn get_prefix_excludes_neighbour_collection() -> anyhow::Result<()> {
+
let (read, mut write) = fjall_db();
+
+
let mut batch = TestBatch::default();
+
batch.create(
+
"did:plc:person-a",
+
"a.a.aa",
+
"rkey-aaa",
+
"{}",
+
Some("rev-aaa"),
+
None,
+
10_000,
+
);
+
write.insert_batch(batch.batch)?;
+
write.step_rollup()?;
+
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read.get_prefix(
+
NsidPrefix::new("a.a.a").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)?;
+
assert_eq!(creates, 0);
+
assert_eq!(dids_estimate, 0);
+
assert_eq!(children, vec![]);
+
assert_eq!(cursor, None);
+
Ok(())
+
}
+
+
#[test]
+
fn get_prefix_includes_child_collection() -> anyhow::Result<()> {
+
let (read, mut write) = fjall_db();
+
+
let mut batch = TestBatch::default();
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a",
+
"rkey-aaa",
+
"{}",
+
Some("rev-aaa"),
+
None,
+
10_000,
+
);
+
write.insert_batch(batch.batch)?;
+
write.step_rollup()?;
+
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read.get_prefix(
+
NsidPrefix::new("a.a").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)?;
+
assert_eq!(creates, 1);
+
assert_eq!(dids_estimate, 1);
+
assert_eq!(
+
children,
+
vec![PrefixChild::Collection(NsidCount {
+
nsid: "a.a.a".to_string(),
+
creates: 1,
+
dids_estimate: 1
+
}),]
+
);
+
assert_eq!(cursor, None);
+
Ok(())
+
}
+
+
#[test]
+
fn get_prefix_includes_child_prefix() -> anyhow::Result<()> {
+
let (read, mut write) = fjall_db();
+
+
let mut batch = TestBatch::default();
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a.a",
+
"rkey-aaaa",
+
"{}",
+
Some("rev-aaaa"),
+
None,
+
10_000,
+
);
+
write.insert_batch(batch.batch)?;
+
write.step_rollup()?;
+
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read.get_prefix(
+
NsidPrefix::new("a.a").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)?;
+
assert_eq!(creates, 1);
+
assert_eq!(dids_estimate, 1);
+
assert_eq!(
+
children,
+
vec![PrefixChild::Prefix(PrefixCount {
+
prefix: "a.a.a".to_string(),
+
creates: 1,
+
dids_estimate: 1
+
}),]
+
);
+
assert_eq!(cursor, None);
+
Ok(())
+
}
+
+
#[test]
+
fn get_prefix_merges_child_prefixes() -> anyhow::Result<()> {
+
let (read, mut write) = fjall_db();
+
+
let mut batch = TestBatch::default();
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a.a",
+
"rkey-aaaa",
+
"{}",
+
Some("rev-aaaa"),
+
None,
+
10_000,
+
);
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a.b",
+
"rkey-aaab",
+
"{}",
+
Some("rev-aaab"),
+
None,
+
10_001,
+
);
+
write.insert_batch(batch.batch)?;
+
write.step_rollup()?;
+
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read.get_prefix(
+
NsidPrefix::new("a.a").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)?;
+
assert_eq!(creates, 2);
+
assert_eq!(dids_estimate, 1);
+
assert_eq!(
+
children,
+
vec![PrefixChild::Prefix(PrefixCount {
+
prefix: "a.a.a".to_string(),
+
creates: 2,
+
dids_estimate: 1
+
}),]
+
);
+
assert_eq!(cursor, None);
+
Ok(())
+
}
+
+
#[test]
+
fn get_prefix_exact_and_child_and_prefix() -> anyhow::Result<()> {
+
let (read, mut write) = fjall_db();
+
+
let mut batch = TestBatch::default();
+
// exact:
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a",
+
"rkey-aaa",
+
"{}",
+
Some("rev-aaa"),
+
None,
+
10_000,
+
);
+
// child:
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a.a",
+
"rkey-aaaa",
+
"{}",
+
Some("rev-aaaa"),
+
None,
+
10_001,
+
);
+
// prefix:
+
batch.create(
+
"did:plc:person-a",
+
"a.a.a.a.a",
+
"rkey-aaaaa",
+
"{}",
+
Some("rev-aaaaa"),
+
None,
+
10_002,
+
);
+
write.insert_batch(batch.batch)?;
+
write.step_rollup()?;
+
+
let (
+
JustCount {
+
creates,
+
dids_estimate,
+
..
+
},
+
children,
+
cursor,
+
) = read.get_prefix(
+
NsidPrefix::new("a.a.a").unwrap(),
+
10,
+
OrderCollectionsBy::Lexi { cursor: None },
+
None,
+
None,
+
)?;
+
assert_eq!(creates, 2);
+
assert_eq!(dids_estimate, 1);
+
assert_eq!(
+
children,
+
vec![
+
PrefixChild::Collection(NsidCount {
+
nsid: "a.a.a.a".to_string(),
+
creates: 1,
+
dids_estimate: 1
+
}),
+
PrefixChild::Prefix(PrefixCount {
+
prefix: "a.a.a.a".to_string(),
+
creates: 1,
+
dids_estimate: 1
+
}),
+
]
+
);
+
assert_eq!(cursor, None);
Ok(())