Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
heartwood crates radicle src node policy.rs
pub mod config;
pub mod store;

use std::fmt;
use std::str::FromStr;

use serde::{Deserialize, Serialize};
use thiserror::Error;

use crate::prelude::RepoId;

pub use super::{Alias, NodeId};

/// Repository seeding policy.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct SeedPolicy {
    pub rid: RepoId,
    pub policy: SeedingPolicy,
}

impl std::ops::Deref for SeedPolicy {
    type Target = SeedingPolicy;

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

/// Node following policy.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FollowPolicy {
    pub nid: NodeId,
    pub alias: Option<Alias>,
    pub policy: Policy,
}

/// Seeding policy of a node or repo.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", tag = "policy")]
pub enum SeedingPolicy {
    /// Allow seeding.
    Allow {
        /// Seeding scope.
        #[serde(default)]
        scope: Scope,
    },
    /// Block seeding.
    #[default]
    Block,
}

impl SeedingPolicy {
    /// Is this an "allow" policy.
    pub fn is_allow(&self) -> bool {
        matches!(self, Self::Allow { .. })
    }

    /// Is this a "block" policy.
    pub fn is_block(&self) -> bool {
        !self.is_allow()
    }

    /// Scope, if any.
    pub fn scope(&self) -> Option<Scope> {
        match self {
            Self::Allow { scope } => Some(*scope),
            Self::Block => None,
        }
    }
}

impl From<SeedingPolicy> for Policy {
    fn from(p: SeedingPolicy) -> Self {
        match p {
            SeedingPolicy::Block => Policy::Block,
            SeedingPolicy::Allow { .. } => Policy::Allow,
        }
    }
}

impl std::fmt::Display for SeedingPolicy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{} ({})",
            Policy::from(*self),
            self.scope().unwrap_or_default()
        )
    }
}

/// Resource policy.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Policy {
    /// The resource is allowed.
    Allow,
    /// The resource is blocked.
    #[default]
    Block,
}

impl fmt::Display for Policy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Allow => write!(f, "allow"),
            Self::Block => write!(f, "block"),
        }
    }
}

impl FromStr for Policy {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "allow" => Ok(Self::Allow),
            "block" => Ok(Self::Block),
            _ => Err(s.to_owned()),
        }
    }
}

impl sqlite::BindableWithIndex for Policy {
    fn bind<I: sqlite::ParameterIndex>(
        self,
        stmt: &mut sqlite::Statement<'_>,
        i: I,
    ) -> sqlite::Result<()> {
        match self {
            Self::Allow => "allow",
            Self::Block => "block",
        }
        .bind(stmt, i)
    }
}

impl TryFrom<&sqlite::Value> for Policy {
    type Error = sqlite::Error;

    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
        let message = Some("sql: invalid policy value".to_owned());

        match value {
            sqlite::Value::String(s) if s == "allow" => Ok(Policy::Allow),
            sqlite::Value::String(s) if s == "block" => Ok(Policy::Block),
            sqlite::Value::String(s) => Err(sqlite::Error {
                code: None,
                message: Some(format!("sql: invalid policy '{s}'")),
            }),
            _ => Err(sqlite::Error {
                code: None,
                message,
            }),
        }
    }
}

/// Follow scope of a seeded repository.
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum Scope {
    /// Seed remotes that are explicitly followed.
    Followed,
    /// Seed all remotes.
    #[default]
    All,
}

impl fmt::Display for Scope {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Scope::Followed => f.write_str("followed"),
            Scope::All => f.write_str("all"),
        }
    }
}

#[derive(Debug, Error)]
#[error("invalid seeding scope: {0:?}")]
pub struct ParseScopeError(String);

impl FromStr for Scope {
    type Err = ParseScopeError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "followed" => Ok(Self::Followed),
            "all" => Ok(Self::All),
            _ => Err(ParseScopeError(s.to_string())),
        }
    }
}

impl sqlite::BindableWithIndex for Scope {
    fn bind<I: sqlite::ParameterIndex>(
        self,
        stmt: &mut sqlite::Statement<'_>,
        i: I,
    ) -> sqlite::Result<()> {
        let s = match self {
            Self::Followed => "followed",
            Self::All => "all",
        };
        s.bind(stmt, i)
    }
}

impl TryFrom<&sqlite::Value> for Scope {
    type Error = sqlite::Error;

    fn try_from(value: &sqlite::Value) -> Result<Self, Self::Error> {
        let message = Some("invalid remote scope".to_owned());

        match value {
            sqlite::Value::String(scope) => Scope::from_str(scope).map_err(|_| sqlite::Error {
                code: None,
                message,
            }),
            _ => Err(sqlite::Error {
                code: None,
                message,
            }),
        }
    }
}