radicle: type safe tracking config
The tracking database can be opened in read-only or read-write mode. Knowing which is being used is significant in concurrent scenarios, e.g. it's safer to have a read-only handle on multiple threads. Provide static type information by signifying what kind of database handle is being worked with, read or read-write. Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
6b6f087c14
commit
dfa7ba1df5
|
|
@ -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")),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue