Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle: type safe tracking config
Fintan Halpenny committed 2 years ago
commit dfa7ba1df52a2fa0e335f17810c175f361c3868c
parent 6b6f087c14a80cc7becbfb6ef5e953c5558a4182
6 files changed +96 -34
modified radicle-cli/src/commands/node/tracking.rs
@@ -6,7 +6,7 @@ use term::Element;

use super::TrackingMode;

-
pub fn run(store: &tracking::store::Config, mode: TrackingMode) -> anyhow::Result<()> {
+
pub fn run(store: &tracking::store::ConfigReader, mode: TrackingMode) -> anyhow::Result<()> {
    match mode {
        TrackingMode::Repos => print_repos(store)?,
        TrackingMode::Nodes => print_nodes(store)?,
@@ -14,7 +14,7 @@ pub fn run(store: &tracking::store::Config, mode: TrackingMode) -> anyhow::Resul
    Ok(())
}

-
fn print_repos(store: &tracking::store::Config) -> anyhow::Result<()> {
+
fn print_repos(store: &tracking::store::ConfigReader) -> anyhow::Result<()> {
    let mut t = term::Table::new(term::table::TableOptions::bordered());
    t.push([
        term::format::default(String::from("RID")),
@@ -39,7 +39,7 @@ fn print_repos(store: &tracking::store::Config) -> anyhow::Result<()> {
    Ok(())
}

-
fn print_nodes(store: &tracking::store::Config) -> anyhow::Result<()> {
+
fn print_nodes(store: &tracking::store::ConfigReader) -> anyhow::Result<()> {
    let mut t = term::Table::new(term::table::TableOptions::bordered());
    t.push([
        term::format::default(String::from("DID")),
modified radicle-node/src/service.rs
@@ -32,7 +32,7 @@ use crate::prelude::*;
use crate::runtime::Emitter;
use crate::service::message::{Announcement, AnnouncementMessage, Ping};
use crate::service::message::{NodeAnnouncement, RefsAnnouncement};
-
use crate::service::tracking::Scope;
+
use crate::service::tracking::{store::Write, Scope};
use crate::storage;
use crate::storage::{Namespaces, ReadStorage};
use crate::storage::{ReadRepository, RefUpdate};
@@ -185,7 +185,7 @@ pub struct Service<R, A, S, G> {
    /// Node address manager.
    addresses: A,
    /// Tracking policy configuration.
-
    tracking: tracking::Config,
+
    tracking: tracking::Config<Write>,
    /// State relating to gossip.
    gossip: Gossip,
    /// Peer sessions, currently or recently connected.
@@ -244,7 +244,7 @@ where
        routing: R,
        storage: S,
        addresses: A,
-
        tracking: tracking::Config,
+
        tracking: tracking::Config<Write>,
        signer: G,
        rng: Rng,
        node: NodeAnnouncement,
@@ -349,7 +349,7 @@ where
    }

    /// Get the tracking policy.
-
    pub fn tracking(&self) -> &tracking::Config {
+
    pub fn tracking(&self) -> &tracking::Config<Write> {
        &self.tracking
    }

modified radicle-node/src/service/tracking.rs
@@ -1,3 +1,4 @@
+
use core::fmt;
use std::collections::HashSet;
use std::ops;

@@ -11,6 +12,7 @@ use radicle::storage::{Namespaces, ReadRepository as _, ReadStorage};
use crate::prelude::Id;
use crate::service::NodeId;

+
pub use crate::node::tracking::store;
pub use crate::node::tracking::store::Config as Store;
pub use crate::node::tracking::store::Error;
pub use crate::node::tracking::{Alias, Node, Policy, Repo, Scope};
@@ -42,19 +44,30 @@ pub enum NamespacesError {
}

/// Tracking configuration.
-
#[derive(Debug)]
-
pub struct Config {
+
pub struct Config<T> {
    /// Default policy, if a policy for a specific node or repository was not found.
    policy: Policy,
    /// Default scope, if a scope for a specific repository was not found.
    scope: Scope,
    /// Underlying configuration store.
-
    store: Store,
+
    store: Store<T>,
}

-
impl Config {
+
// N.b. deriving `Debug` will require `T: Debug` so we manually
+
// implement it here.
+
impl<T> fmt::Debug for Config<T> {
+
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+
        f.debug_struct("Config")
+
            .field("policy", &self.policy)
+
            .field("scope", &self.scope)
+
            .field("store", &self.store)
+
            .finish()
+
    }
+
}
+

+
impl<T> Config<T> {
    /// Create a new tracking configuration.
-
    pub fn new(policy: Policy, scope: Scope, store: Store) -> Self {
+
    pub fn new(policy: Policy, scope: Scope, store: Store<T>) -> Self {
        Self {
            policy,
            scope,
@@ -139,15 +152,15 @@ impl Config {
    }
}

-
impl ops::Deref for Config {
-
    type Target = Store;
+
impl<T> ops::Deref for Config<T> {
+
    type Target = Store<T>;

    fn deref(&self) -> &Self::Target {
        &self.store
    }
}

-
impl ops::DerefMut for Config {
+
impl<T> ops::DerefMut for Config<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.store
    }
modified radicle-node/src/test/peer.rs
@@ -153,7 +153,7 @@ where
        mut config: Config<G>,
    ) -> Self {
        let routing = routing::Table::memory().unwrap();
-
        let tracking = tracking::Store::memory().unwrap();
+
        let tracking = tracking::Store::<tracking::store::Write>::memory().unwrap();
        let tracking = tracking::Config::new(config.policy, config.scope, tracking);
        let tempdir = tempfile::tempdir().unwrap();
        let id = *config.signer.public_key();
modified radicle/src/node/tracking/store.rs
@@ -1,4 +1,5 @@
#![allow(clippy::type_complexity)]
+
use std::marker::PhantomData;
use std::path::Path;
use std::{fmt, io, ops::Not as _, str::FromStr, time};

@@ -25,30 +26,31 @@ pub enum Error {
    Internal(#[from] sql::Error),
}

+
/// Read-only type witness.
+
pub struct Read;
+
/// Read-write type witness.
+
pub struct Write;
+

+
/// Read only config.
+
pub type ConfigReader = Config<Read>;
+
/// Read-write config.
+
pub type ConfigWriter = Config<Write>;
+

/// Tracking configuration.
-
pub struct Config {
+
pub struct Config<T> {
    db: sql::Connection,
+
    _marker: PhantomData<T>,
}

-
impl fmt::Debug for Config {
+
impl<T> fmt::Debug for Config<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Config(..)")
    }
}

-
impl Config {
+
impl Config<Read> {
    const SCHEMA: &str = include_str!("schema.sql");

-
    /// Open a policy store at the given path. Creates a new store if it
-
    /// doesn't exist.
-
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
-
        let mut db = sql::Connection::open(path)?;
-
        db.set_busy_timeout(DB_WRITE_TIMEOUT.as_millis() as usize)?;
-
        db.execute(Self::SCHEMA)?;
-

-
        Ok(Self { db })
-
    }
-

    /// Same as [`Self::open`], but in read-only mode. This is useful to have multiple
    /// open databases, as no locking is required.
    pub fn reader<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
@@ -57,7 +59,39 @@ impl Config {
        db.set_busy_timeout(DB_READ_TIMEOUT.as_millis() as usize)?;
        db.execute(Self::SCHEMA)?;

-
        Ok(Self { db })
+
        Ok(Self {
+
            db,
+
            _marker: PhantomData,
+
        })
+
    }
+

+
    /// Create a new in-memory address book.
+
    pub fn memory() -> Result<Self, Error> {
+
        let db =
+
            sql::Connection::open_with_flags(":memory:", sqlite::OpenFlags::new().set_read_only())?;
+
        db.execute(Self::SCHEMA)?;
+

+
        Ok(Self {
+
            db,
+
            _marker: PhantomData,
+
        })
+
    }
+
}
+

+
impl Config<Write> {
+
    const SCHEMA: &str = include_str!("schema.sql");
+

+
    /// Open a policy store at the given path. Creates a new store if it
+
    /// doesn't exist.
+
    pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
+
        let mut db = sql::Connection::open(path)?;
+
        db.set_busy_timeout(DB_WRITE_TIMEOUT.as_millis() as usize)?;
+
        db.execute(Self::SCHEMA)?;
+

+
        Ok(Self {
+
            db,
+
            _marker: PhantomData,
+
        })
    }

    /// Create a new in-memory address book.
@@ -65,7 +99,18 @@ impl Config {
        let db = sql::Connection::open(":memory:")?;
        db.execute(Self::SCHEMA)?;

-
        Ok(Self { db })
+
        Ok(Self {
+
            db,
+
            _marker: PhantomData,
+
        })
+
    }
+

+
    /// Get a read-only version of this store.
+
    pub fn read_only(self) -> ConfigReader {
+
        Config {
+
            db: self.db,
+
            _marker: PhantomData,
+
        }
    }

    /// Track a node.
@@ -155,7 +200,11 @@ impl Config {

        Ok(self.db.change_count() > 0)
    }
+
}

+
/// `Read` methods for `Config`. This implies that a
+
/// `Config<Write>` can access these functions as well.
+
impl<T> Config<T> {
    /// Check if a node is tracked.
    pub fn is_node_tracked(&self, id: &NodeId) -> Result<bool, Error> {
        Ok(matches!(
@@ -265,7 +314,7 @@ impl Config {
    }
}

-
impl AliasStore for Config {
+
impl<T> AliasStore for Config<T> {
    /// Retrieve `alias` of given node.
    /// Calls `Self::node_policy` under the hood.
    fn alias(&self, nid: &NodeId) -> Option<Alias> {
modified radicle/src/profile.rs
@@ -208,7 +208,7 @@ impl Profile {
    }

    /// Return a read-only handle to the tracking configuration of the node.
-
    pub fn tracking(&self) -> Result<tracking::store::Config, tracking::store::Error> {
+
    pub fn tracking(&self) -> Result<tracking::store::ConfigReader, tracking::store::Error> {
        let path = self.home.node().join(node::TRACKING_DB_FILE);
        let config = tracking::store::Config::reader(path)?;

@@ -261,7 +261,7 @@ impl Profile {
/// Holds multiple alias stores, and will try
/// them one by one when asking for an alias.
pub struct Aliases {
-
    tracking: Option<tracking::store::Config>,
+
    tracking: Option<tracking::store::ConfigReader>,
    addresses: Option<address::Book>,
}