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, 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 { match self { Self::Allow { scope } => Some(*scope), Self::Block => None, } } } impl From 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 { match s { "allow" => Ok(Self::Allow), "block" => Ok(Self::Block), _ => Err(s.to_owned()), } } } impl sqlite::BindableWithIndex for Policy { fn bind( 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 { 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 { match s { "followed" => Ok(Self::Followed), "all" => Ok(Self::All), _ => Err(ParseScopeError(s.to_string())), } } } impl sqlite::BindableWithIndex for Scope { fn bind( 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 { 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, }), } } }