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,33 +147,32 @@ pub fn following(profile: &Profile, alias: Option<Alias>) -> anyhow::Result<()>
|
||||||
term::format::default(String::from("Policy")),
|
term::format::default(String::from("Policy")),
|
||||||
]);
|
]);
|
||||||
t.divider();
|
t.divider();
|
||||||
|
push_policies(&mut t, &aliases, store.follow_policies()?, &alias);
|
||||||
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)),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
t.print();
|
t.print();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_policies(
|
fn push_policies(
|
||||||
t: &mut Table<3, Paint<String>>,
|
t: &mut Table<3, Paint<String>>,
|
||||||
aliases: &impl AliasStore,
|
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 {
|
for policy in policies {
|
||||||
|
match policy {
|
||||||
|
Ok(policy::FollowPolicy {
|
||||||
nid: id,
|
nid: id,
|
||||||
alias,
|
alias,
|
||||||
policy,
|
policy,
|
||||||
} in policies
|
}) => {
|
||||||
{
|
if match (filter, &alias) {
|
||||||
|
(None, _) => false,
|
||||||
|
(Some(filter), Some(alias)) => *filter != *alias,
|
||||||
|
(Some(_), None) => true,
|
||||||
|
} {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
t.push([
|
t.push([
|
||||||
term::format::highlight(Did::from(id).to_string()),
|
term::format::highlight(Did::from(id).to_string()),
|
||||||
match alias {
|
match alias {
|
||||||
|
|
@ -183,6 +182,11 @@ fn push_policies(
|
||||||
term::format::secondary(policy.to_string()),
|
term::format::secondary(policy.to_string()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
term::error(format!("Failed to read a follow policy: {err}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fallback_alias(nid: &PublicKey, aliases: &impl AliasStore) -> String {
|
fn fallback_alias(nid: &PublicKey, aliases: &impl AliasStore) -> String {
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,9 @@ pub fn seeding(profile: &Profile) -> anyhow::Result<()> {
|
||||||
]);
|
]);
|
||||||
t.divider();
|
t.divider();
|
||||||
|
|
||||||
for policy::SeedPolicy { rid, policy } in store.seed_policies()? {
|
for policy in store.seed_policies()? {
|
||||||
|
match policy {
|
||||||
|
Ok(policy::SeedPolicy { rid, policy }) => {
|
||||||
let id = rid.to_string();
|
let id = rid.to_string();
|
||||||
let name = storage
|
let name = storage
|
||||||
.repository(rid)
|
.repository(rid)
|
||||||
|
|
@ -219,6 +221,11 @@ pub fn seeding(profile: &Profile) -> anyhow::Result<()> {
|
||||||
term::format::dim(scope),
|
term::format::dim(scope),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
Err(err) => {
|
||||||
|
term::error(format!("Failed to read a seeding policy: {err}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if t.is_empty() {
|
if t.is_empty() {
|
||||||
term::print(term::format::dim("No seeding policies to show."));
|
term::print(term::format::dim("No seeding policies to show."));
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.unwrap()?;
|
.unwrap()?;
|
||||||
let count = row.read::<i64, _>(0) as usize;
|
let count = row.try_read::<i64, _>(0)? as usize;
|
||||||
|
|
||||||
stats.repos.unique = count;
|
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.
|
// SAFETY: `COUNT` always returns a row.
|
||||||
let row = stmt.iter().next().unwrap()?;
|
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.
|
let since = now - LocalDuration::from_mins(60 * 24 * 7); // 1 week.
|
||||||
stmt.reset()?;
|
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))?;
|
stmt.bind((2, now.as_millis() as i64))?;
|
||||||
|
|
||||||
let row = stmt.iter().next().unwrap()?;
|
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()
|
.next()
|
||||||
// SAFETY: `COUNT` always returns a row.
|
// SAFETY: `COUNT` always returns a row.
|
||||||
.unwrap()?;
|
.unwrap()?;
|
||||||
let count = row.read::<i64, _>(0) as usize;
|
let count = row.try_read::<i64, _>(0)? as usize;
|
||||||
|
|
||||||
stats.nodes.public_daily = count;
|
stats.nodes.public_daily = count;
|
||||||
}
|
}
|
||||||
|
|
@ -187,7 +187,7 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
.next()
|
.next()
|
||||||
// SAFETY: `COUNT` always returns a row.
|
// SAFETY: `COUNT` always returns a row.
|
||||||
.unwrap()?;
|
.unwrap()?;
|
||||||
let count = row.read::<i64, _>(0) as usize;
|
let count = row.try_read::<i64, _>(0)? as usize;
|
||||||
|
|
||||||
stats.nodes.seeding_weekly = count;
|
stats.nodes.seeding_weekly = count;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,9 +30,22 @@ impl Allowed {
|
||||||
let nodes = config
|
let nodes = config
|
||||||
.follow_policies()
|
.follow_policies()
|
||||||
.map_err(|err| error::Policy::FailedNodes { rid, err })?;
|
.map_err(|err| error::Policy::FailedNodes { rid, err })?;
|
||||||
let followed: HashSet<_> = nodes
|
|
||||||
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
let mut followed = HashSet::new();
|
||||||
.collect();
|
|
||||||
|
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 })
|
Ok(Allowed::Followed { remotes: followed })
|
||||||
}
|
}
|
||||||
|
|
@ -62,10 +75,23 @@ impl BlockList {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_config(config: &Config<Read>) -> Result<BlockList, error::Blocked> {
|
pub fn from_config(config: &Config<Read>) -> Result<BlockList, error::Blocked> {
|
||||||
Ok(config
|
let mut blocked = HashSet::new();
|
||||||
.follow_policies()?
|
|
||||||
.filter_map(|entry| (entry.policy == Policy::Block).then_some(entry.nid))
|
for entry in config.follow_policies()? {
|
||||||
.collect())
|
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
|
// 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
|
// only re-compute the filter when we've unseeded a certain amount of repos
|
||||||
// and the filter is really out of date.
|
// and the filter is really out of date.
|
||||||
//
|
self.filter = Filter::allowed_by(self.policies.seed_policies()?);
|
||||||
// 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)),
|
|
||||||
);
|
|
||||||
// Update and announce new inventory.
|
// Update and announce new inventory.
|
||||||
if let Err(e) = self.remove_inventory(id) {
|
if let Err(e) = self.remove_inventory(id) {
|
||||||
error!(target: "service", "Error updating inventory after unseed: {e}");
|
error!(target: "service", "Error updating inventory after unseed: {e}");
|
||||||
|
|
@ -750,11 +744,7 @@ where
|
||||||
.remove_inventories(private.iter(), &nid)?;
|
.remove_inventories(private.iter(), &nid)?;
|
||||||
|
|
||||||
// Setup subscription filter for seeded repos.
|
// Setup subscription filter for seeded repos.
|
||||||
self.filter = Filter::new(
|
self.filter = Filter::allowed_by(self.policies.seed_policies()?);
|
||||||
self.policies
|
|
||||||
.seed_policies()?
|
|
||||||
.filter_map(|t| (t.policy.is_allow()).then_some(t.rid)),
|
|
||||||
);
|
|
||||||
// Connect to configured peers.
|
// Connect to configured peers.
|
||||||
let addrs = self.config.connect.clone();
|
let addrs = self.config.connect.clone();
|
||||||
for (id, addr) in addrs.into_iter().map(|ca| ca.into()) {
|
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.
|
/// Fetch all repositories that are seeded but missing from storage.
|
||||||
fn fetch_missing_repositories(&mut self) -> Result<(), Error> {
|
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<_>>();
|
let policies = self.policies.seed_policies()?.collect::<Vec<_>>();
|
||||||
for policy in policies {
|
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;
|
let rid = policy.rid;
|
||||||
|
|
||||||
if !policy.is_allow() {
|
if !policy.is_allow() {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,30 @@ impl Filter {
|
||||||
Self(bloom)
|
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.
|
/// Empty filter with nothing set.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
Self(BloomFilter::from(vec![0x0; FILTER_SIZE_S]))
|
Self(BloomFilter::from(vec![0x0; FILTER_SIZE_S]))
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ impl Store for Database {
|
||||||
|
|
||||||
if let Some(row) = stmt.into_iter().next() {
|
if let Some(row) = stmt.into_iter().next() {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
let id = row.read::<i64, _>("rowid");
|
let id = row.try_read::<i64, _>("rowid")?;
|
||||||
|
|
||||||
Ok(Some(id as AnnouncementId))
|
Ok(Some(id as AnnouncementId))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -342,9 +342,9 @@ mod parse {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn announcement(row: sql::Row) -> Result<(AnnouncementId, Announcement), Error> {
|
pub fn announcement(row: sql::Row) -> Result<(AnnouncementId, Announcement), Error> {
|
||||||
let id = row.read::<i64, _>("rowid") as AnnouncementId;
|
let id = row.try_read::<i64, _>("rowid")? as AnnouncementId;
|
||||||
let node = row.read::<NodeId, _>("node");
|
let node = row.try_read::<NodeId, _>("node")?;
|
||||||
let gt = row.read::<GossipType, _>("type");
|
let gt = row.try_read::<GossipType, _>("type")?;
|
||||||
let message = match gt {
|
let message = match gt {
|
||||||
GossipType::Refs => {
|
GossipType::Refs => {
|
||||||
let ann = row.try_read::<RefsAnnouncement, _>("message")?;
|
let ann = row.try_read::<RefsAnnouncement, _>("message")?;
|
||||||
|
|
@ -359,8 +359,8 @@ mod parse {
|
||||||
AnnouncementMessage::Node(ann)
|
AnnouncementMessage::Node(ann)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let signature = row.read::<Signature, _>("signature");
|
let signature = row.try_read::<Signature, _>("signature")?;
|
||||||
let timestamp = row.read::<Timestamp, _>("timestamp");
|
let timestamp = row.try_read::<Timestamp, _>("timestamp")?;
|
||||||
|
|
||||||
debug_assert_eq!(timestamp, message.timestamp());
|
debug_assert_eq!(timestamp, message.timestamp());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ pub fn run(
|
||||||
|
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
let id = row.read::<&str, _>("id");
|
let id = row.try_read::<&str, _>("id")?;
|
||||||
let mut patch = json::from_str::<json::Value>(row.read::<&str, _>("patch"))
|
let mut patch = json::from_str::<json::Value>(row.try_read::<&str, _>("patch")?)
|
||||||
.map_err(Error::MalformedJson)?;
|
.map_err(Error::MalformedJson)?;
|
||||||
let patch = patch.as_object_mut().ok_or(Error::MalformedJsonSchema)?;
|
let patch = patch.as_object_mut().ok_or(Error::MalformedJsonSchema)?;
|
||||||
let revisions = patch["revisions"]
|
let revisions = patch["revisions"]
|
||||||
|
|
@ -117,7 +117,7 @@ mod tests {
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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 actual: serde_json::Value = serde_json::from_str(patch).unwrap();
|
||||||
let expected: serde_json::Value = serde_json::from_str(PATCH_V2).unwrap();
|
let expected: serde_json::Value = serde_json::from_str(PATCH_V2).unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -440,8 +440,8 @@ pub struct IssuesIter<'a> {
|
||||||
|
|
||||||
impl IssuesIter<'_> {
|
impl IssuesIter<'_> {
|
||||||
fn parse_row(row: sql::Row) -> Result<(IssueId, Issue), Error> {
|
fn parse_row(row: sql::Row) -> Result<(IssueId, Issue), Error> {
|
||||||
let id = IssueId::from_str(row.read::<&str, _>("id"))?;
|
let id = IssueId::from_str(row.try_read::<&str, _>("id")?)?;
|
||||||
let issue = serde_json::from_str::<Issue>(row.read::<&str, _>("issue"))?;
|
let issue = serde_json::from_str::<Issue>(row.try_read::<&str, _>("issue")?)?;
|
||||||
Ok((id, issue))
|
Ok((id, issue))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -537,7 +537,7 @@ mod query {
|
||||||
match stmt.into_iter().next().transpose()? {
|
match stmt.into_iter().next().transpose()? {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(row) => {
|
Some(row) => {
|
||||||
let issue = row.read::<&str, _>("issue");
|
let issue = row.try_read::<&str, _>("issue")?;
|
||||||
let issue = serde_json::from_str(issue)?;
|
let issue = serde_json::from_str(issue)?;
|
||||||
Ok(Some(issue))
|
Ok(Some(issue))
|
||||||
}
|
}
|
||||||
|
|
@ -597,8 +597,8 @@ mod query {
|
||||||
stmt.into_iter()
|
stmt.into_iter()
|
||||||
.try_fold(IssueCounts::default(), |mut counts, row| {
|
.try_fold(IssueCounts::default(), |mut counts, row| {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
let count = row.read::<i64, _>("count") as usize;
|
let count = row.try_read::<i64, _>("count")? as usize;
|
||||||
let status = serde_json::from_str::<State>(row.read::<&str, _>("state"))?;
|
let status = serde_json::from_str::<State>(row.try_read::<&str, _>("state")?)?;
|
||||||
match status {
|
match status {
|
||||||
State::Closed { .. } => counts.closed += count,
|
State::Closed { .. } => counts.closed += count,
|
||||||
State::Open => counts.open += count,
|
State::Open => counts.open += count,
|
||||||
|
|
|
||||||
|
|
@ -427,8 +427,8 @@ pub struct PatchesIter<'a> {
|
||||||
|
|
||||||
impl PatchesIter<'_> {
|
impl PatchesIter<'_> {
|
||||||
fn parse_row(row: sql::Row) -> Result<(PatchId, Patch), Error> {
|
fn parse_row(row: sql::Row) -> Result<(PatchId, Patch), Error> {
|
||||||
let id = PatchId::from_str(row.read::<&str, _>("id"))?;
|
let id = PatchId::from_str(row.try_read::<&str, _>("id")?)?;
|
||||||
let patch = serde_json::from_str::<Patch>(row.read::<&str, _>("patch"))
|
let patch = serde_json::from_str::<Patch>(row.try_read::<&str, _>("patch")?)
|
||||||
.map_err(|e| Error::Object(id, e))?;
|
.map_err(|e| Error::Object(id, e))?;
|
||||||
Ok((id, patch))
|
Ok((id, patch))
|
||||||
}
|
}
|
||||||
|
|
@ -592,7 +592,7 @@ mod query {
|
||||||
match stmt.into_iter().next().transpose()? {
|
match stmt.into_iter().next().transpose()? {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(row) => {
|
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))?;
|
let patch = serde_json::from_str(patch).map_err(|e| Error::Object(*id, e))?;
|
||||||
Ok(Some(patch))
|
Ok(Some(patch))
|
||||||
}
|
}
|
||||||
|
|
@ -618,10 +618,11 @@ mod query {
|
||||||
match stmt.into_iter().next().transpose()? {
|
match stmt.into_iter().next().transpose()? {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(row) => {
|
Some(row) => {
|
||||||
let id = PatchId::from_str(row.read::<&str, _>("id"))?;
|
let id = PatchId::from_str(row.try_read::<&str, _>("id")?)?;
|
||||||
let patch = serde_json::from_str::<Patch>(row.read::<&str, _>("patch"))
|
let patch = serde_json::from_str::<Patch>(row.try_read::<&str, _>("patch")?)
|
||||||
.map_err(|e| Error::Object(id, e))?;
|
.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 {
|
Ok(Some(ByRevision {
|
||||||
id,
|
id,
|
||||||
patch,
|
patch,
|
||||||
|
|
@ -686,8 +687,8 @@ mod query {
|
||||||
stmt.into_iter()
|
stmt.into_iter()
|
||||||
.try_fold(PatchCounts::default(), |mut counts, row| {
|
.try_fold(PatchCounts::default(), |mut counts, row| {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
let count = row.read::<i64, _>("count") as usize;
|
let count = row.try_read::<i64, _>("count")? as usize;
|
||||||
let status = serde_json::from_str::<State>(row.read::<&str, _>("state"))?;
|
let status = serde_json::from_str::<State>(row.try_read::<&str, _>("state")?)?;
|
||||||
match status {
|
match status {
|
||||||
State::Draft => counts.draft += count,
|
State::Draft => counts.draft += count,
|
||||||
State::Open { .. } => counts.open += count,
|
State::Open { .. } => counts.open += count,
|
||||||
|
|
|
||||||
|
|
@ -115,15 +115,15 @@ impl Store for Database {
|
||||||
stmt.bind((1, node))?;
|
stmt.bind((1, node))?;
|
||||||
|
|
||||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
if let Some(Ok(row)) = stmt.into_iter().next() {
|
||||||
let version = row.read::<i64, _>("version").try_into()?;
|
let version = row.try_read::<i64, _>("version")?.try_into()?;
|
||||||
let features = row.read::<node::Features, _>("features");
|
let features = row.try_read::<node::Features, _>("features")?;
|
||||||
let alias = Alias::from_str(row.read::<&str, _>("alias"))?;
|
let alias = Alias::from_str(row.try_read::<&str, _>("alias")?)?;
|
||||||
let timestamp = row.read::<Timestamp, _>("timestamp");
|
let timestamp = row.try_read::<Timestamp, _>("timestamp")?;
|
||||||
let pow = row.read::<i64, _>("pow") as u32;
|
let pow = row.try_read::<i64, _>("pow")? as u32;
|
||||||
let agent = row.read::<UserAgent, _>("agent");
|
let agent = row.try_read::<UserAgent, _>("agent")?;
|
||||||
let penalty = row.read::<i64, _>("penalty").min(u8::MAX as i64);
|
let penalty = row.try_read::<i64, _>("penalty")?.min(u8::MAX as i64);
|
||||||
let penalty = Penalty(penalty as u8);
|
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)?;
|
let addrs = self.addresses_of(node)?;
|
||||||
|
|
||||||
Ok(Some(Node {
|
Ok(Some(Node {
|
||||||
|
|
@ -154,8 +154,8 @@ impl Store for Database {
|
||||||
|
|
||||||
if let Some(row) = stmt.into_iter().next() {
|
if let Some(row) = stmt.into_iter().next() {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
let addr_banned = row.read::<i64, _>(0).is_positive();
|
let addr_banned = row.try_read::<i64, _>(0)?.is_positive();
|
||||||
let node_banned = row.read::<i64, _>(1).is_positive();
|
let node_banned = row.try_read::<i64, _>(1)?.is_positive();
|
||||||
|
|
||||||
Ok(node_banned || addr_banned)
|
Ok(node_banned || addr_banned)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -183,16 +183,16 @@ impl Store for Database {
|
||||||
|
|
||||||
for row in stmt.into_iter() {
|
for row in stmt.into_iter() {
|
||||||
let row = row?;
|
let row = row?;
|
||||||
let _typ = row.read::<AddressType, _>("type");
|
let _typ = row.try_read::<AddressType, _>("type")?;
|
||||||
let addr = row.read::<Address, _>("value");
|
let addr = row.try_read::<Address, _>("value")?;
|
||||||
let source = row.read::<Source, _>("source");
|
let source = row.try_read::<Source, _>("source")?;
|
||||||
let last_attempt = row
|
let last_attempt = row
|
||||||
.read::<Option<i64>, _>("last_attempt")
|
.read::<Option<i64>, _>("last_attempt")
|
||||||
.map(|t| LocalTime::from_millis(t as u128));
|
.map(|t| LocalTime::from_millis(t as u128));
|
||||||
let last_success = row
|
let last_success = row
|
||||||
.read::<Option<i64>, _>("last_success")
|
.read::<Option<i64>, _>("last_success")
|
||||||
.map(|t| LocalTime::from_millis(t as u128));
|
.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 {
|
addrs.push(KnownAddress {
|
||||||
addr,
|
addr,
|
||||||
|
|
@ -212,7 +212,7 @@ impl Store for Database {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::NoRows)??;
|
.ok_or(Error::NoRows)??;
|
||||||
let count = row.read::<i64, _>(0) as usize;
|
let count = row.try_read::<i64, _>(0)? as usize;
|
||||||
|
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +224,7 @@ impl Store for Database {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::NoRows)??;
|
.ok_or(Error::NoRows)??;
|
||||||
let count = row.read::<i64, _>(0) as usize;
|
let count = row.try_read::<i64, _>(0)? as usize;
|
||||||
|
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
@ -299,17 +299,17 @@ impl Store for Database {
|
||||||
let mut entries = Vec::new();
|
let mut entries = Vec::new();
|
||||||
|
|
||||||
while let Some(Ok(row)) = stmt.next() {
|
while let Some(Ok(row)) = stmt.next() {
|
||||||
let node = row.read::<NodeId, _>("node");
|
let node = row.try_read::<NodeId, _>("node")?;
|
||||||
let _typ = row.read::<AddressType, _>("type");
|
let _typ = row.try_read::<AddressType, _>("type")?;
|
||||||
let addr = row.read::<Address, _>("value");
|
let addr = row.try_read::<Address, _>("value")?;
|
||||||
let source = row.read::<Source, _>("source");
|
let source = row.try_read::<Source, _>("source")?;
|
||||||
let last_success = row.read::<Option<i64>, _>("last_success");
|
let last_success = row.try_read::<Option<i64>, _>("last_success")?;
|
||||||
let last_attempt = row.read::<Option<i64>, _>("last_attempt");
|
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_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 last_attempt = last_attempt.map(|t| LocalTime::from_millis(t as u128));
|
||||||
let version = row.read::<i64, _>("version").try_into()?;
|
let version = row.try_read::<i64, _>("version")?.try_into()?;
|
||||||
let banned = row.read::<i64, _>("banned").is_positive();
|
let banned = row.try_read::<i64, _>("banned")?.is_positive();
|
||||||
let penalty = row.read::<i64, _>("penalty");
|
let penalty = row.try_read::<i64, _>("penalty")?;
|
||||||
let penalty = Penalty(penalty as u8); // Clamped at `u8::MAX`.
|
let penalty = Penalty(penalty as u8); // Clamped at `u8::MAX`.
|
||||||
|
|
||||||
entries.push(AddressEntry {
|
entries.push(AddressEntry {
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,20 @@ impl<T> Config<T> {
|
||||||
let nodes = self
|
let nodes = self
|
||||||
.follow_policies()
|
.follow_policies()
|
||||||
.map_err(|err| FailedNodes { rid: *rid, err })?;
|
.map_err(|err| FailedNodes { rid: *rid, err })?;
|
||||||
let mut followed: HashSet<_> = nodes
|
|
||||||
.filter_map(|node| (node.policy == Policy::Allow).then_some(node.nid))
|
let mut followed: HashSet<_> = HashSet::new();
|
||||||
.collect();
|
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) {
|
if let Ok(repo) = storage.repository(*rid) {
|
||||||
let delegates = repo
|
let delegates = repo
|
||||||
|
|
|
||||||
|
|
@ -257,13 +257,13 @@ impl<T> Store<T> {
|
||||||
stmt.bind((1, id))?;
|
stmt.bind((1, id))?;
|
||||||
|
|
||||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
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
|
let alias = alias
|
||||||
.is_empty()
|
.is_empty()
|
||||||
.not()
|
.not()
|
||||||
.then_some(alias.to_owned())
|
.then_some(alias.to_owned())
|
||||||
.and_then(|s| Alias::from_str(&s).ok());
|
.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 {
|
return Ok(Some(FollowPolicy {
|
||||||
nid: *id,
|
nid: *id,
|
||||||
|
|
@ -283,9 +283,9 @@ impl<T> Store<T> {
|
||||||
stmt.bind((1, id))?;
|
stmt.bind((1, id))?;
|
||||||
|
|
||||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
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 {
|
Policy::Allow => SeedingPolicy::Allow {
|
||||||
scope: row.read::<Scope, _>("scope"),
|
scope: row.try_read::<Scope, _>("scope")?,
|
||||||
},
|
},
|
||||||
Policy::Block => SeedingPolicy::Block,
|
Policy::Block => SeedingPolicy::Block,
|
||||||
};
|
};
|
||||||
|
|
@ -329,25 +329,38 @@ pub struct FollowPolicies<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for FollowPolicies<'_> {
|
impl Iterator for FollowPolicies<'_> {
|
||||||
type Item = FollowPolicy;
|
type Item = Result<FollowPolicy, Error>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let row = self.inner.next()?;
|
let row = self.inner.next()?;
|
||||||
let Ok(row) = row else { return self.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
|
let alias = alias
|
||||||
.is_empty()
|
.is_empty()
|
||||||
.not()
|
.not()
|
||||||
.then_some(alias.to_owned())
|
.then_some(alias.to_owned())
|
||||||
.and_then(|s| Alias::from_str(&s).ok());
|
.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,
|
nid: id,
|
||||||
alias,
|
alias,
|
||||||
policy,
|
policy,
|
||||||
})
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -356,19 +369,35 @@ pub struct SeedPolicies<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for SeedPolicies<'_> {
|
impl Iterator for SeedPolicies<'_> {
|
||||||
type Item = SeedPolicy;
|
type Item = Result<SeedPolicy, Error>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let row = self.inner.next()?;
|
let row = self.inner.next()?;
|
||||||
let Ok(row) = row else { return self.next() };
|
let Ok(row) = row else { return self.next() };
|
||||||
let id = row.read("id");
|
|
||||||
let policy = match row.read::<Policy, _>("policy") {
|
let id = match row.try_read("id") {
|
||||||
Policy::Allow => SeedingPolicy::Allow {
|
Ok(id) => id,
|
||||||
scope: row.read::<Scope, _>("scope"),
|
Err(err) => return Some(Err(err.into())),
|
||||||
},
|
|
||||||
Policy::Block => SeedingPolicy::Block,
|
|
||||||
};
|
};
|
||||||
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());
|
assert!(db.follow(id, None).unwrap());
|
||||||
}
|
}
|
||||||
let mut entries = db.follow_policies().unwrap();
|
let mut entries = db.follow_policies().unwrap();
|
||||||
assert_matches!(entries.next(), Some(FollowPolicy { nid, .. }) if nid == ids[0]);
|
assert_matches!(entries.next(), Some(Ok(FollowPolicy { nid, .. })) if nid == ids[0]);
|
||||||
assert_matches!(entries.next(), Some(FollowPolicy { nid, .. }) if nid == ids[1]);
|
assert_matches!(entries.next(), Some(Ok(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[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -471,9 +500,9 @@ mod test {
|
||||||
assert!(db.seed(id, Scope::All).unwrap());
|
assert!(db.seed(id, Scope::All).unwrap());
|
||||||
}
|
}
|
||||||
let mut entries = db.seed_policies().unwrap();
|
let mut entries = db.seed_policies().unwrap();
|
||||||
assert_matches!(entries.next(), Some(SeedPolicy { rid, .. }) if rid == ids[0]);
|
assert_matches!(entries.next(), Some(Ok(SeedPolicy { rid, .. })) if rid == ids[0]);
|
||||||
assert_matches!(entries.next(), Some(SeedPolicy { rid, .. }) if rid == ids[1]);
|
assert_matches!(entries.next(), Some(Ok(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[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ impl Store for Database {
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.next()
|
.next()
|
||||||
.ok_or(Error::NoRows)??;
|
.ok_or(Error::NoRows)??;
|
||||||
let count = row.read::<i64, _>(0) as usize;
|
let count = row.try_read::<i64, _>(0)? as usize;
|
||||||
|
|
||||||
Ok(count)
|
Ok(count)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ impl Store for Database {
|
||||||
stmt.bind((2, node))?;
|
stmt.bind((2, node))?;
|
||||||
|
|
||||||
if let Some(Ok(row)) = stmt.into_iter().next() {
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue