radicle: Fix panic when reading from SQLite database fails
I was greeted by `rad patch redact` with
called `Result::unwrap()` on an `Err` value:
Error { code: None, message: Some("failed to convert") }
stack backtrace:
[…]
3: core::result::Result<T,E>::unwrap
at …/rust-1.88.0/lib/rustlib/src/rust/library/core/src/result.rs:1137:23
4: sqlite::cursor::Row::read
at …/index.crates.io-1949cf8c6b5b557f/sqlite-0.32.0/src/cursor.rs:136:9
5: radicle::cob::patch::cache::query::find_by_revision
at ./crates/radicle/src/cob/patch/cache.rs:624:65
6: <… as radicle::cob::patch::cache::Patches>::find_by_revision
at ./crates/radicle/src/cob/patch/cache.rs:553:9
7: radicle_cli::commands::rad_patch::redact::run
at ./crates/radicle-cli/src/commands/patch/redact.rs:23:9
8: radicle_cli::commands::rad_patch::run
at ./crates/radicle-cli/src/commands/patch.rs:1026:13
[…]
It turns out that `sqlite::cursor::Row::read` is the panicky version of
`sqlite::cursor::Row::try_read` (which returns a `Result`).
While it is somewhat rare that SQLite reads fail, it is not unheard of.
In `radicle-cli` it might not be critical, but also `radicle-protocol`
and `radicle-fetch` are affected, and they could potentially panic a
`radicle-node` process.
Use `try_read` instead, and propagate down the error handling.
This commit is contained in:
parent
a568e7f484
commit
192cc993a8
|
|
@ -147,41 +147,45 @@ pub fn following(profile: &Profile, alias: Option<Alias>) -> anyhow::Result<()>
|
|||
term::format::default(String::from("Policy")),
|
||||
]);
|
||||
t.divider();
|
||||
|
||||
match alias {
|
||||
None => push_policies(&mut t, &aliases, store.follow_policies()?),
|
||||
Some(alias) => push_policies(
|
||||
&mut t,
|
||||
&aliases,
|
||||
store
|
||||
.follow_policies()?
|
||||
.filter(|p| p.alias.as_ref().is_some_and(|alias_| *alias_ == alias)),
|
||||
),
|
||||
};
|
||||
push_policies(&mut t, &aliases, store.follow_policies()?, &alias);
|
||||
t.print();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_policies(
|
||||
t: &mut Table<3, Paint<String>>,
|
||||
aliases: &impl AliasStore,
|
||||
policies: impl Iterator<Item = policy::FollowPolicy>,
|
||||
policies: impl Iterator<Item = Result<policy::FollowPolicy, policy::store::Error>>,
|
||||
filter: &Option<Alias>,
|
||||
) {
|
||||
for policy::FollowPolicy {
|
||||
nid: id,
|
||||
alias,
|
||||
policy,
|
||||
} in policies
|
||||
{
|
||||
t.push([
|
||||
term::format::highlight(Did::from(id).to_string()),
|
||||
match alias {
|
||||
None => term::format::secondary(fallback_alias(&id, aliases)),
|
||||
Some(alias) => term::format::secondary(alias.to_string()),
|
||||
},
|
||||
term::format::secondary(policy.to_string()),
|
||||
]);
|
||||
for policy in policies {
|
||||
match policy {
|
||||
Ok(policy::FollowPolicy {
|
||||
nid: id,
|
||||
alias,
|
||||
policy,
|
||||
}) => {
|
||||
if match (filter, &alias) {
|
||||
(None, _) => false,
|
||||
(Some(filter), Some(alias)) => *filter != *alias,
|
||||
(Some(_), None) => true,
|
||||
} {
|
||||
continue;
|
||||
}
|
||||
|
||||
t.push([
|
||||
term::format::highlight(Did::from(id).to_string()),
|
||||
match alias {
|
||||
None => term::format::secondary(fallback_alias(&id, aliases)),
|
||||
Some(alias) => term::format::secondary(alias.to_string()),
|
||||
},
|
||||
term::format::secondary(policy.to_string()),
|
||||
]);
|
||||
}
|
||||
Err(err) => {
|
||||
term::error(format!("Failed to read a follow policy: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -203,21 +203,28 @@ pub fn seeding(profile: &Profile) -> anyhow::Result<()> {
|
|||
]);
|
||||
t.divider();
|
||||
|
||||
for policy::SeedPolicy { rid, policy } in store.seed_policies()? {
|
||||
let id = rid.to_string();
|
||||
let name = storage
|
||||
.repository(rid)
|
||||
.and_then(|repo| repo.project().map(|proj| proj.name().to_string()))
|
||||
.unwrap_or_default();
|
||||
let scope = policy.scope().unwrap_or_default().to_string();
|
||||
let policy = term::format::policy(&Policy::from(policy));
|
||||
for policy in store.seed_policies()? {
|
||||
match policy {
|
||||
Ok(policy::SeedPolicy { rid, policy }) => {
|
||||
let id = rid.to_string();
|
||||
let name = storage
|
||||
.repository(rid)
|
||||
.and_then(|repo| repo.project().map(|proj| proj.name().to_string()))
|
||||
.unwrap_or_default();
|
||||
let scope = policy.scope().unwrap_or_default().to_string();
|
||||
let policy = term::format::policy(&Policy::from(policy));
|
||||
|
||||
t.push([
|
||||
term::format::tertiary(id),
|
||||
name.into(),
|
||||
policy,
|
||||
term::format::dim(scope),
|
||||
])
|
||||
t.push([
|
||||
term::format::tertiary(id),
|
||||
name.into(),
|
||||
policy,
|
||||
term::format::dim(scope),
|
||||
])
|
||||
}
|
||||
Err(err) => {
|
||||
term::error(format!("Failed to read a seeding policy: {err}"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if t.is_empty() {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.into_iter()
|
||||
.next()
|
||||
.unwrap()?;
|
||||
let count = row.read::<i64, _>(0) as usize;
|
||||
let count = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
stats.repos.unique = count;
|
||||
}
|
||||
|
|
@ -141,7 +141,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
|
||||
// SAFETY: `COUNT` always returns a row.
|
||||
let row = stmt.iter().next().unwrap()?;
|
||||
stats.nodes.online_daily = row.read::<i64, _>(0) as usize;
|
||||
stats.nodes.online_daily = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
let since = now - LocalDuration::from_mins(60 * 24 * 7); // 1 week.
|
||||
stmt.reset()?;
|
||||
|
|
@ -149,7 +149,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
stmt.bind((2, now.as_millis() as i64))?;
|
||||
|
||||
let row = stmt.iter().next().unwrap()?;
|
||||
stats.nodes.online_weekly = row.read::<i64, _>(0) as usize;
|
||||
stats.nodes.online_weekly = row.try_read::<i64, _>(0)? as usize;
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -168,7 +168,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.next()
|
||||
// SAFETY: `COUNT` always returns a row.
|
||||
.unwrap()?;
|
||||
let count = row.read::<i64, _>(0) as usize;
|
||||
let count = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
stats.nodes.public_daily = count;
|
||||
}
|
||||
|
|
@ -187,7 +187,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
|||
.next()
|
||||
// SAFETY: `COUNT` always returns a row.
|
||||
.unwrap()?;
|
||||
let count = row.read::<i64, _>(0) as usize;
|
||||
let count = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
stats.nodes.seeding_weekly = count;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,9 +30,22 @@ impl Allowed {
|
|||
let nodes = config
|
||||
.follow_policies()
|
||||
.map_err(|err| error::Policy::FailedNodes { rid, err })?;
|
||||
let followed: HashSet<_> = nodes
|
||||
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
||||
.collect();
|
||||
|
||||
let mut followed = HashSet::new();
|
||||
|
||||
for node in nodes {
|
||||
let node = match node {
|
||||
Ok(policy) => policy,
|
||||
Err(err) => {
|
||||
log::error!(target: "fetch", "Failed to read follow policy for {rid}: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if node.policy == Policy::Allow {
|
||||
followed.insert(node.nid);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Allowed::Followed { remotes: followed })
|
||||
}
|
||||
|
|
@ -62,10 +75,23 @@ impl BlockList {
|
|||
}
|
||||
|
||||
pub fn from_config(config: &Config<Read>) -> Result<BlockList, error::Blocked> {
|
||||
Ok(config
|
||||
.follow_policies()?
|
||||
.filter_map(|entry| (entry.policy == Policy::Block).then_some(entry.nid))
|
||||
.collect())
|
||||
let mut blocked = HashSet::new();
|
||||
|
||||
for entry in config.follow_policies()? {
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(err) => {
|
||||
log::error!(target: "fetch", "Failed to read follow policy: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if entry.policy == Policy::Block {
|
||||
blocked.insert(entry.nid);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(BlockList(blocked))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -570,13 +570,7 @@ where
|
|||
// Nb. This is potentially slow if we have lots of repos. We should probably
|
||||
// only re-compute the filter when we've unseeded a certain amount of repos
|
||||
// and the filter is really out of date.
|
||||
//
|
||||
// TODO: Share this code with initialization code.
|
||||
self.filter = Filter::new(
|
||||
self.policies
|
||||
.seed_policies()?
|
||||
.filter_map(|t| (t.policy.is_allow()).then_some(t.rid)),
|
||||
);
|
||||
self.filter = Filter::allowed_by(self.policies.seed_policies()?);
|
||||
// Update and announce new inventory.
|
||||
if let Err(e) = self.remove_inventory(id) {
|
||||
error!(target: "service", "Error updating inventory after unseed: {e}");
|
||||
|
|
@ -750,11 +744,7 @@ where
|
|||
.remove_inventories(private.iter(), &nid)?;
|
||||
|
||||
// Setup subscription filter for seeded repos.
|
||||
self.filter = Filter::new(
|
||||
self.policies
|
||||
.seed_policies()?
|
||||
.filter_map(|t| (t.policy.is_allow()).then_some(t.rid)),
|
||||
);
|
||||
self.filter = Filter::allowed_by(self.policies.seed_policies()?);
|
||||
// Connect to configured peers.
|
||||
let addrs = self.config.connect.clone();
|
||||
for (id, addr) in addrs.into_iter().map(|ca| ca.into()) {
|
||||
|
|
@ -2510,10 +2500,16 @@ where
|
|||
|
||||
/// Fetch all repositories that are seeded but missing from storage.
|
||||
fn fetch_missing_repositories(&mut self) -> Result<(), Error> {
|
||||
// TODO(finto): could filter the policies based on the continue checks
|
||||
// below, but `storage.contains` is fallible
|
||||
let policies = self.policies.seed_policies()?.collect::<Vec<_>>();
|
||||
for policy in policies {
|
||||
let policy = match policy {
|
||||
Ok(policy) => policy,
|
||||
Err(err) => {
|
||||
log::error!(target: "protocol::filter", "Failed to read seed policy: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let rid = policy.rid;
|
||||
|
||||
if !policy.is_allow() {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,30 @@ impl Filter {
|
|||
Self(bloom)
|
||||
}
|
||||
|
||||
pub fn allowed_by(
|
||||
policies: impl Iterator<
|
||||
Item = Result<radicle::node::policy::SeedPolicy, radicle::node::policy::store::Error>,
|
||||
>,
|
||||
) -> Self {
|
||||
let mut ids = Vec::new();
|
||||
|
||||
for seed in policies {
|
||||
let seed = match seed {
|
||||
Ok(seed) => seed,
|
||||
Err(err) => {
|
||||
log::error!(target: "protocol::filter", "Failed to read seed policy: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if seed.policy.is_allow() {
|
||||
ids.push(seed.rid);
|
||||
}
|
||||
}
|
||||
|
||||
Self::new(ids)
|
||||
}
|
||||
|
||||
/// Empty filter with nothing set.
|
||||
pub fn empty() -> Self {
|
||||
Self(BloomFilter::from(vec![0x0; FILTER_SIZE_S]))
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ impl Store for Database {
|
|||
|
||||
if let Some(row) = stmt.into_iter().next() {
|
||||
let row = row?;
|
||||
let id = row.read::<i64, _>("rowid");
|
||||
let id = row.try_read::<i64, _>("rowid")?;
|
||||
|
||||
Ok(Some(id as AnnouncementId))
|
||||
} else {
|
||||
|
|
@ -342,9 +342,9 @@ mod parse {
|
|||
use super::*;
|
||||
|
||||
pub fn announcement(row: sql::Row) -> Result<(AnnouncementId, Announcement), Error> {
|
||||
let id = row.read::<i64, _>("rowid") as AnnouncementId;
|
||||
let node = row.read::<NodeId, _>("node");
|
||||
let gt = row.read::<GossipType, _>("type");
|
||||
let id = row.try_read::<i64, _>("rowid")? as AnnouncementId;
|
||||
let node = row.try_read::<NodeId, _>("node")?;
|
||||
let gt = row.try_read::<GossipType, _>("type")?;
|
||||
let message = match gt {
|
||||
GossipType::Refs => {
|
||||
let ann = row.try_read::<RefsAnnouncement, _>("message")?;
|
||||
|
|
@ -359,8 +359,8 @@ mod parse {
|
|||
AnnouncementMessage::Node(ann)
|
||||
}
|
||||
};
|
||||
let signature = row.read::<Signature, _>("signature");
|
||||
let timestamp = row.read::<Timestamp, _>("timestamp");
|
||||
let signature = row.try_read::<Signature, _>("signature")?;
|
||||
let timestamp = row.try_read::<Timestamp, _>("timestamp")?;
|
||||
|
||||
debug_assert_eq!(timestamp, message.timestamp());
|
||||
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ pub fn run(
|
|||
|
||||
for row in rows {
|
||||
let row = row?;
|
||||
let id = row.read::<&str, _>("id");
|
||||
let mut patch = json::from_str::<json::Value>(row.read::<&str, _>("patch"))
|
||||
let id = row.try_read::<&str, _>("id")?;
|
||||
let mut patch = json::from_str::<json::Value>(row.try_read::<&str, _>("patch")?)
|
||||
.map_err(Error::MalformedJson)?;
|
||||
let patch = patch.as_object_mut().ok_or(Error::MalformedJsonSchema)?;
|
||||
let revisions = patch["revisions"]
|
||||
|
|
@ -117,7 +117,7 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
|
||||
let patch = row.read::<&str, _>("patch");
|
||||
let patch = row.try_read::<&str, _>("patch").unwrap();
|
||||
let actual: serde_json::Value = serde_json::from_str(patch).unwrap();
|
||||
let expected: serde_json::Value = serde_json::from_str(PATCH_V2).unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -440,8 +440,8 @@ pub struct IssuesIter<'a> {
|
|||
|
||||
impl IssuesIter<'_> {
|
||||
fn parse_row(row: sql::Row) -> Result<(IssueId, Issue), Error> {
|
||||
let id = IssueId::from_str(row.read::<&str, _>("id"))?;
|
||||
let issue = serde_json::from_str::<Issue>(row.read::<&str, _>("issue"))?;
|
||||
let id = IssueId::from_str(row.try_read::<&str, _>("id")?)?;
|
||||
let issue = serde_json::from_str::<Issue>(row.try_read::<&str, _>("issue")?)?;
|
||||
Ok((id, issue))
|
||||
}
|
||||
}
|
||||
|
|
@ -537,7 +537,7 @@ mod query {
|
|||
match stmt.into_iter().next().transpose()? {
|
||||
None => Ok(None),
|
||||
Some(row) => {
|
||||
let issue = row.read::<&str, _>("issue");
|
||||
let issue = row.try_read::<&str, _>("issue")?;
|
||||
let issue = serde_json::from_str(issue)?;
|
||||
Ok(Some(issue))
|
||||
}
|
||||
|
|
@ -597,8 +597,8 @@ mod query {
|
|||
stmt.into_iter()
|
||||
.try_fold(IssueCounts::default(), |mut counts, row| {
|
||||
let row = row?;
|
||||
let count = row.read::<i64, _>("count") as usize;
|
||||
let status = serde_json::from_str::<State>(row.read::<&str, _>("state"))?;
|
||||
let count = row.try_read::<i64, _>("count")? as usize;
|
||||
let status = serde_json::from_str::<State>(row.try_read::<&str, _>("state")?)?;
|
||||
match status {
|
||||
State::Closed { .. } => counts.closed += count,
|
||||
State::Open => counts.open += count,
|
||||
|
|
|
|||
|
|
@ -427,8 +427,8 @@ pub struct PatchesIter<'a> {
|
|||
|
||||
impl PatchesIter<'_> {
|
||||
fn parse_row(row: sql::Row) -> Result<(PatchId, Patch), Error> {
|
||||
let id = PatchId::from_str(row.read::<&str, _>("id"))?;
|
||||
let patch = serde_json::from_str::<Patch>(row.read::<&str, _>("patch"))
|
||||
let id = PatchId::from_str(row.try_read::<&str, _>("id")?)?;
|
||||
let patch = serde_json::from_str::<Patch>(row.try_read::<&str, _>("patch")?)
|
||||
.map_err(|e| Error::Object(id, e))?;
|
||||
Ok((id, patch))
|
||||
}
|
||||
|
|
@ -592,7 +592,7 @@ mod query {
|
|||
match stmt.into_iter().next().transpose()? {
|
||||
None => Ok(None),
|
||||
Some(row) => {
|
||||
let patch = row.read::<&str, _>("patch");
|
||||
let patch = row.try_read::<&str, _>("patch")?;
|
||||
let patch = serde_json::from_str(patch).map_err(|e| Error::Object(*id, e))?;
|
||||
Ok(Some(patch))
|
||||
}
|
||||
|
|
@ -618,10 +618,11 @@ mod query {
|
|||
match stmt.into_iter().next().transpose()? {
|
||||
None => Ok(None),
|
||||
Some(row) => {
|
||||
let id = PatchId::from_str(row.read::<&str, _>("id"))?;
|
||||
let patch = serde_json::from_str::<Patch>(row.read::<&str, _>("patch"))
|
||||
let id = PatchId::from_str(row.try_read::<&str, _>("id")?)?;
|
||||
let patch = serde_json::from_str::<Patch>(row.try_read::<&str, _>("patch")?)
|
||||
.map_err(|e| Error::Object(id, e))?;
|
||||
let revision = serde_json::from_str::<Revision>(row.read::<&str, _>("revision"))?;
|
||||
let revision =
|
||||
serde_json::from_str::<Revision>(row.try_read::<&str, _>("revision")?)?;
|
||||
Ok(Some(ByRevision {
|
||||
id,
|
||||
patch,
|
||||
|
|
@ -686,8 +687,8 @@ mod query {
|
|||
stmt.into_iter()
|
||||
.try_fold(PatchCounts::default(), |mut counts, row| {
|
||||
let row = row?;
|
||||
let count = row.read::<i64, _>("count") as usize;
|
||||
let status = serde_json::from_str::<State>(row.read::<&str, _>("state"))?;
|
||||
let count = row.try_read::<i64, _>("count")? as usize;
|
||||
let status = serde_json::from_str::<State>(row.try_read::<&str, _>("state")?)?;
|
||||
match status {
|
||||
State::Draft => counts.draft += count,
|
||||
State::Open { .. } => counts.open += count,
|
||||
|
|
|
|||
|
|
@ -115,15 +115,15 @@ impl Store for Database {
|
|||
stmt.bind((1, node))?;
|
||||
|
||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||
let version = row.read::<i64, _>("version").try_into()?;
|
||||
let features = row.read::<node::Features, _>("features");
|
||||
let alias = Alias::from_str(row.read::<&str, _>("alias"))?;
|
||||
let timestamp = row.read::<Timestamp, _>("timestamp");
|
||||
let pow = row.read::<i64, _>("pow") as u32;
|
||||
let agent = row.read::<UserAgent, _>("agent");
|
||||
let penalty = row.read::<i64, _>("penalty").min(u8::MAX as i64);
|
||||
let version = row.try_read::<i64, _>("version")?.try_into()?;
|
||||
let features = row.try_read::<node::Features, _>("features")?;
|
||||
let alias = Alias::from_str(row.try_read::<&str, _>("alias")?)?;
|
||||
let timestamp = row.try_read::<Timestamp, _>("timestamp")?;
|
||||
let pow = row.try_read::<i64, _>("pow")? as u32;
|
||||
let agent = row.try_read::<UserAgent, _>("agent")?;
|
||||
let penalty = row.try_read::<i64, _>("penalty")?.min(u8::MAX as i64);
|
||||
let penalty = Penalty(penalty as u8);
|
||||
let banned = row.read::<i64, _>("banned").is_positive();
|
||||
let banned = row.try_read::<i64, _>("banned")?.is_positive();
|
||||
let addrs = self.addresses_of(node)?;
|
||||
|
||||
Ok(Some(Node {
|
||||
|
|
@ -154,8 +154,8 @@ impl Store for Database {
|
|||
|
||||
if let Some(row) = stmt.into_iter().next() {
|
||||
let row = row?;
|
||||
let addr_banned = row.read::<i64, _>(0).is_positive();
|
||||
let node_banned = row.read::<i64, _>(1).is_positive();
|
||||
let addr_banned = row.try_read::<i64, _>(0)?.is_positive();
|
||||
let node_banned = row.try_read::<i64, _>(1)?.is_positive();
|
||||
|
||||
Ok(node_banned || addr_banned)
|
||||
} else {
|
||||
|
|
@ -183,16 +183,16 @@ impl Store for Database {
|
|||
|
||||
for row in stmt.into_iter() {
|
||||
let row = row?;
|
||||
let _typ = row.read::<AddressType, _>("type");
|
||||
let addr = row.read::<Address, _>("value");
|
||||
let source = row.read::<Source, _>("source");
|
||||
let _typ = row.try_read::<AddressType, _>("type")?;
|
||||
let addr = row.try_read::<Address, _>("value")?;
|
||||
let source = row.try_read::<Source, _>("source")?;
|
||||
let last_attempt = row
|
||||
.read::<Option<i64>, _>("last_attempt")
|
||||
.map(|t| LocalTime::from_millis(t as u128));
|
||||
let last_success = row
|
||||
.read::<Option<i64>, _>("last_success")
|
||||
.map(|t| LocalTime::from_millis(t as u128));
|
||||
let banned = row.read::<i64, _>("banned").is_positive();
|
||||
let banned = row.try_read::<i64, _>("banned")?.is_positive();
|
||||
|
||||
addrs.push(KnownAddress {
|
||||
addr,
|
||||
|
|
@ -212,7 +212,7 @@ impl Store for Database {
|
|||
.into_iter()
|
||||
.next()
|
||||
.ok_or(Error::NoRows)??;
|
||||
let count = row.read::<i64, _>(0) as usize;
|
||||
let count = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ impl Store for Database {
|
|||
.into_iter()
|
||||
.next()
|
||||
.ok_or(Error::NoRows)??;
|
||||
let count = row.read::<i64, _>(0) as usize;
|
||||
let count = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
|
@ -299,17 +299,17 @@ impl Store for Database {
|
|||
let mut entries = Vec::new();
|
||||
|
||||
while let Some(Ok(row)) = stmt.next() {
|
||||
let node = row.read::<NodeId, _>("node");
|
||||
let _typ = row.read::<AddressType, _>("type");
|
||||
let addr = row.read::<Address, _>("value");
|
||||
let source = row.read::<Source, _>("source");
|
||||
let last_success = row.read::<Option<i64>, _>("last_success");
|
||||
let last_attempt = row.read::<Option<i64>, _>("last_attempt");
|
||||
let node = row.try_read::<NodeId, _>("node")?;
|
||||
let _typ = row.try_read::<AddressType, _>("type")?;
|
||||
let addr = row.try_read::<Address, _>("value")?;
|
||||
let source = row.try_read::<Source, _>("source")?;
|
||||
let last_success = row.try_read::<Option<i64>, _>("last_success")?;
|
||||
let last_attempt = row.try_read::<Option<i64>, _>("last_attempt")?;
|
||||
let last_success = last_success.map(|t| LocalTime::from_millis(t as u128));
|
||||
let last_attempt = last_attempt.map(|t| LocalTime::from_millis(t as u128));
|
||||
let version = row.read::<i64, _>("version").try_into()?;
|
||||
let banned = row.read::<i64, _>("banned").is_positive();
|
||||
let penalty = row.read::<i64, _>("penalty");
|
||||
let version = row.try_read::<i64, _>("version")?.try_into()?;
|
||||
let banned = row.try_read::<i64, _>("banned")?.is_positive();
|
||||
let penalty = row.try_read::<i64, _>("penalty")?;
|
||||
let penalty = Penalty(penalty as u8); // Clamped at `u8::MAX`.
|
||||
|
||||
entries.push(AddressEntry {
|
||||
|
|
|
|||
|
|
@ -106,9 +106,20 @@ impl<T> Config<T> {
|
|||
let nodes = self
|
||||
.follow_policies()
|
||||
.map_err(|err| FailedNodes { rid: *rid, err })?;
|
||||
let mut followed: HashSet<_> = nodes
|
||||
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
||||
.collect();
|
||||
|
||||
let mut followed: HashSet<_> = HashSet::new();
|
||||
for node in nodes {
|
||||
let node = match node {
|
||||
Ok(node) => node,
|
||||
Err(err) => {
|
||||
log::warn!(target: "service", "Failed to read follow policy: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if node.policy == Policy::Allow {
|
||||
followed.insert(node.nid);
|
||||
}
|
||||
}
|
||||
|
||||
if let Ok(repo) = storage.repository(*rid) {
|
||||
let delegates = repo
|
||||
|
|
|
|||
|
|
@ -257,13 +257,13 @@ impl<T> Store<T> {
|
|||
stmt.bind((1, id))?;
|
||||
|
||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||
let alias = row.read::<&str, _>("alias");
|
||||
let alias = row.try_read::<&str, _>("alias")?;
|
||||
let alias = alias
|
||||
.is_empty()
|
||||
.not()
|
||||
.then_some(alias.to_owned())
|
||||
.and_then(|s| Alias::from_str(&s).ok());
|
||||
let policy = row.read::<Policy, _>("policy");
|
||||
let policy = row.try_read::<Policy, _>("policy")?;
|
||||
|
||||
return Ok(Some(FollowPolicy {
|
||||
nid: *id,
|
||||
|
|
@ -283,9 +283,9 @@ impl<T> Store<T> {
|
|||
stmt.bind((1, id))?;
|
||||
|
||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||
let policy = match row.read::<Policy, _>("policy") {
|
||||
let policy = match row.try_read::<Policy, _>("policy")? {
|
||||
Policy::Allow => SeedingPolicy::Allow {
|
||||
scope: row.read::<Scope, _>("scope"),
|
||||
scope: row.try_read::<Scope, _>("scope")?,
|
||||
},
|
||||
Policy::Block => SeedingPolicy::Block,
|
||||
};
|
||||
|
|
@ -329,25 +329,38 @@ pub struct FollowPolicies<'a> {
|
|||
}
|
||||
|
||||
impl Iterator for FollowPolicies<'_> {
|
||||
type Item = FollowPolicy;
|
||||
type Item = Result<FollowPolicy, Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let row = self.inner.next()?;
|
||||
let Ok(row) = row else { return self.next() };
|
||||
let id = row.read("id");
|
||||
let alias = row.read::<&str, _>("alias").to_owned();
|
||||
|
||||
let id = match row.try_read("id") {
|
||||
Ok(id) => id,
|
||||
Err(err) => return Some(Err(err.into())),
|
||||
};
|
||||
|
||||
let alias = match row.try_read::<&str, _>("alias") {
|
||||
Ok(alias) => alias.to_owned(),
|
||||
Err(err) => return Some(Err(err.into())),
|
||||
};
|
||||
|
||||
let alias = alias
|
||||
.is_empty()
|
||||
.not()
|
||||
.then_some(alias.to_owned())
|
||||
.and_then(|s| Alias::from_str(&s).ok());
|
||||
let policy = row.read::<Policy, _>("policy");
|
||||
|
||||
Some(FollowPolicy {
|
||||
let policy = match row.try_read::<Policy, _>("policy") {
|
||||
Ok(policy) => policy,
|
||||
Err(err) => return Some(Err(err.into())),
|
||||
};
|
||||
|
||||
Some(Ok(FollowPolicy {
|
||||
nid: id,
|
||||
alias,
|
||||
policy,
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -356,19 +369,35 @@ pub struct SeedPolicies<'a> {
|
|||
}
|
||||
|
||||
impl Iterator for SeedPolicies<'_> {
|
||||
type Item = SeedPolicy;
|
||||
type Item = Result<SeedPolicy, Error>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let row = self.inner.next()?;
|
||||
let Ok(row) = row else { return self.next() };
|
||||
let id = row.read("id");
|
||||
let policy = match row.read::<Policy, _>("policy") {
|
||||
Policy::Allow => SeedingPolicy::Allow {
|
||||
scope: row.read::<Scope, _>("scope"),
|
||||
},
|
||||
Policy::Block => SeedingPolicy::Block,
|
||||
|
||||
let id = match row.try_read("id") {
|
||||
Ok(id) => id,
|
||||
Err(err) => return Some(Err(err.into())),
|
||||
};
|
||||
Some(SeedPolicy { rid: id, policy })
|
||||
|
||||
let policy = match row.try_read::<Policy, _>("policy") {
|
||||
Ok(policy) => policy,
|
||||
Err(err) => return Some(Err(err.into())),
|
||||
};
|
||||
|
||||
match policy {
|
||||
Policy::Allow => match row.try_read::<Scope, _>("scope") {
|
||||
Ok(scope) => Some(Ok(SeedPolicy {
|
||||
rid: id,
|
||||
policy: SeedingPolicy::Allow { scope },
|
||||
})),
|
||||
Err(err) => Some(Err(err.into())),
|
||||
},
|
||||
Policy::Block => Some(Ok(SeedPolicy {
|
||||
rid: id,
|
||||
policy: SeedingPolicy::Block,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,9 +486,9 @@ mod test {
|
|||
assert!(db.follow(id, None).unwrap());
|
||||
}
|
||||
let mut entries = db.follow_policies().unwrap();
|
||||
assert_matches!(entries.next(), Some(FollowPolicy { nid, .. }) if nid == ids[0]);
|
||||
assert_matches!(entries.next(), Some(FollowPolicy { nid, .. }) if nid == ids[1]);
|
||||
assert_matches!(entries.next(), Some(FollowPolicy { nid, .. }) if nid == ids[2]);
|
||||
assert_matches!(entries.next(), Some(Ok(FollowPolicy { nid, .. })) if nid == ids[0]);
|
||||
assert_matches!(entries.next(), Some(Ok(FollowPolicy { nid, .. })) if nid == ids[1]);
|
||||
assert_matches!(entries.next(), Some(Ok(FollowPolicy { nid, .. })) if nid == ids[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -471,9 +500,9 @@ mod test {
|
|||
assert!(db.seed(id, Scope::All).unwrap());
|
||||
}
|
||||
let mut entries = db.seed_policies().unwrap();
|
||||
assert_matches!(entries.next(), Some(SeedPolicy { rid, .. }) if rid == ids[0]);
|
||||
assert_matches!(entries.next(), Some(SeedPolicy { rid, .. }) if rid == ids[1]);
|
||||
assert_matches!(entries.next(), Some(SeedPolicy { rid, .. }) if rid == ids[2]);
|
||||
assert_matches!(entries.next(), Some(Ok(SeedPolicy { rid, .. })) if rid == ids[0]);
|
||||
assert_matches!(entries.next(), Some(Ok(SeedPolicy { rid, .. })) if rid == ids[1]);
|
||||
assert_matches!(entries.next(), Some(Ok(SeedPolicy { rid, .. })) if rid == ids[2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ impl Store for Database {
|
|||
.into_iter()
|
||||
.next()
|
||||
.ok_or(Error::NoRows)??;
|
||||
let count = row.read::<i64, _>(0) as usize;
|
||||
let count = row.try_read::<i64, _>(0)? as usize;
|
||||
|
||||
Ok(count)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ impl Store for Database {
|
|||
stmt.bind((2, node))?;
|
||||
|
||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||
return Ok(Some(row.read::<Timestamp, _>("timestamp")));
|
||||
return Ok(Some(row.try_read::<Timestamp, _>("timestamp")?));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue