Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
radicle: Introduce `radicle::node::config::Scope`
Adrian Duke committed 2 months ago
commit cee3659ed519c7601f18859f3ffbcfc0ddb5dc5e
parent 306d0af
3 files changed +154 -6
modified crates/radicle/Cargo.toml
@@ -72,6 +72,7 @@ pretty_assertions = { workspace = true }
qcheck = { workspace = true }
qcheck-macros = { workspace = true }
radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] }
+
radicle-core = {workspace = true, features = ["qcheck"] }
radicle-crypto = { workspace = true, features = ["test"] }
radicle-git-metadata = { workspace = true }
tempfile = { workspace = true }
modified crates/radicle/src/node/config.rs
@@ -9,9 +9,11 @@ use serde::{Deserialize, Serialize};
use serde_json as json;

use crate::node;
-
use crate::node::policy::{Scope, SeedingPolicy};
+
use crate::node::policy::SeedingPolicy;
use crate::node::{Address, Alias, NodeId};

+
use super::policy;
+

/// Peer-to-peer protocol version.
pub type ProtocolVersion = u8;

@@ -364,13 +366,13 @@ pub enum AddressConfig {

/// Default seeding policy. Applies when no repository policies for the given repo are found.
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
-
#[serde(rename_all = "camelCase", tag = "default")]
+
#[serde(tag = "default", rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub enum DefaultSeedingPolicy {
    /// Allow seeding.
    Allow {
        /// Seeding scope.
-
        #[serde(default)]
+
        #[serde(skip_serializing_if = "Scope::is_implicit")]
        scope: Scope,
    },
    /// Block seeding.
@@ -378,6 +380,53 @@ pub enum DefaultSeedingPolicy {
    Block,
}

+
/// [`Scope`] provides a schema for [`policy::Scope`], where the inner scope is
+
/// optional. It is introduced to allow ease migration to a future
+
/// version of [`DefaultSeedingPolicy::Allow`], where no or different defaults
+
/// apply to [`DefaultSeedingPolicy::Allow::scope`].
+
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
+
#[serde(transparent)]
+
pub struct Scope(Option<policy::Scope>);
+

+
impl Scope {
+
    /// Construct the implicit scope, where the default value,
+
    /// [`policy::Scope::All`], is chosen for the final scope value.
+
    pub fn implicit() -> Self {
+
        Self(None)
+
    }
+

+
    /// Construct the explicit scope, where the given [`policy::Scope`] is used.
+
    pub fn explicit(scope: policy::Scope) -> Self {
+
        Self(Some(scope))
+
    }
+

+
    /// Resolve this [`Scope`] to its [`policy::Scope`] value.
+
    ///
+
    /// If the scope is implicit, then [`policy::Scope::All`] is returned.
+
    pub fn into_inner(self) -> policy::Scope {
+
        self.0.unwrap_or(policy::Scope::All)
+
    }
+

+
    /// Returns `true` when the scope is implicit, i.e. no [`policy::Scope`] was
+
    /// given.
+
    pub fn is_implicit(&self) -> bool {
+
        self.0.is_none()
+
    }
+

+
    /// Construct the explicit [`Scope`] where the inner scope is
+
    /// [`policy::Scope::All`].
+
    fn all() -> Self {
+
        Self::explicit(policy::Scope::All)
+
    }
+

+
    /// Construct the explicit [`Scope`] where the inner scope is
+
    /// [`policy::Scope::Followed`].
+
    fn followed() -> Self {
+
        Self::explicit(policy::Scope::Followed)
+
    }
+
}
+

impl DefaultSeedingPolicy {
    /// Is this an "allow" policy.
    pub fn is_allow(&self) -> bool {
@@ -386,7 +435,16 @@ impl DefaultSeedingPolicy {

    /// Seed everything from anyone.
    pub fn permissive() -> Self {
-
        Self::Allow { scope: Scope::All }
+
        Self::Allow {
+
            scope: Scope::all(),
+
        }
+
    }
+

+
    /// Seed only delegate changes.
+
    pub fn followed() -> Self {
+
        Self::Allow {
+
            scope: Scope::followed(),
+
        }
    }
}

@@ -394,7 +452,9 @@ impl From<DefaultSeedingPolicy> for SeedingPolicy {
    fn from(policy: DefaultSeedingPolicy) -> Self {
        match policy {
            DefaultSeedingPolicy::Block => Self::Block,
-
            DefaultSeedingPolicy::Allow { scope } => Self::Allow { scope },
+
            DefaultSeedingPolicy::Allow { scope } => SeedingPolicy::Allow {
+
                scope: scope.into_inner(),
+
            },
        }
    }
}
@@ -646,6 +706,10 @@ wrapper!(
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {
+
    use super::{DefaultSeedingPolicy, Scope};
+
    use crate::node::policy;
+
    use serde_json::json;
+

    #[test]
    fn partial() {
        use super::Config;
@@ -683,4 +747,85 @@ mod test {
        );
        assert_eq!(config.limits.connection.outbound.0, 1337);
    }
+

+
    #[test]
+
    fn deserialize_migrating_scope() {
+
        let seeding_policy: DefaultSeedingPolicy = serde_json::from_value(json!({
+
            "default": "allow"
+
        }))
+
        .unwrap();
+

+
        assert_eq!(
+
            seeding_policy,
+
            DefaultSeedingPolicy::Allow { scope: Scope(None) }
+
        );
+

+
        let seeding_policy: DefaultSeedingPolicy = serde_json::from_value(json!({
+
            "default": "allow",
+
            "scope": null
+
        }))
+
        .unwrap();
+

+
        assert_eq!(
+
            seeding_policy,
+
            DefaultSeedingPolicy::Allow { scope: Scope(None) }
+
        );
+

+
        let seeding_policy: DefaultSeedingPolicy = serde_json::from_value(json!({
+
            "default": "allow",
+
            "scope": "all"
+
        }))
+
        .unwrap();
+

+
        assert_eq!(
+
            seeding_policy,
+
            DefaultSeedingPolicy::Allow {
+
                scope: Scope(Some(policy::Scope::All))
+
            }
+
        );
+

+
        let seeding_policy: DefaultSeedingPolicy = serde_json::from_value(json!({
+
            "default": "allow",
+
            "scope": "followed"
+
        }))
+
        .unwrap();
+

+
        assert_eq!(
+
            seeding_policy,
+
            DefaultSeedingPolicy::Allow {
+
                scope: Scope(Some(policy::Scope::Followed))
+
            }
+
        )
+
    }
+

+
    #[test]
+
    fn serialize_migrating_scope() {
+
        assert_eq!(
+
            json!({
+
                "default": "allow"
+
            }),
+
            serde_json::to_value(DefaultSeedingPolicy::Allow { scope: Scope(None) }).unwrap()
+
        );
+

+
        assert_eq!(
+
            json!({
+
                "default": "allow",
+
                "scope": "all"
+
            }),
+
            serde_json::to_value(DefaultSeedingPolicy::Allow {
+
                scope: Scope(Some(policy::Scope::All))
+
            })
+
            .unwrap()
+
        );
+
        assert_eq!(
+
            json!({
+
                "default": "allow",
+
                "scope": "followed"
+
            }),
+
            serde_json::to_value(DefaultSeedingPolicy::Allow {
+
                scope: Scope(Some(policy::Scope::Followed))
+
            })
+
            .unwrap()
+
        );
+
    }
}
modified crates/radicle/src/profile/config.rs
@@ -192,7 +192,9 @@ impl Config {
            ) {
                log::warn!(target: "radicle", "Overwriting `seedingPolicy` configuration");
                cfg.node.seeding_policy = match policy {
-
                    Policy::Allow => DefaultSeedingPolicy::Allow { scope },
+
                    Policy::Allow => DefaultSeedingPolicy::Allow {
+
                        scope: node::config::Scope::explicit(scope),
+
                    },
                    Policy::Block => DefaultSeedingPolicy::Block,
                }
            }