radicle: Introduce `radicle::node::config::Scope`
The `SeedingPolicy` type now requires a `Scope`, however existing configurations allow it to not be specified. Introduce a `radicle::node::config::Scope` type that allows for an optional `policy::Scope`. The default value resolves to `policy::Scope::All`. This preserves backwards compatibility for existing configurations.
This commit is contained in:
parent
306d0afbfe
commit
cee3659ed5
|
|
@ -72,6 +72,7 @@ pretty_assertions = { workspace = true }
|
||||||
qcheck = { workspace = true }
|
qcheck = { workspace = true }
|
||||||
qcheck-macros = { workspace = true }
|
qcheck-macros = { workspace = true }
|
||||||
radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] }
|
radicle-cob = { workspace = true, features = ["stable-commit-ids", "test"] }
|
||||||
|
radicle-core = {workspace = true, features = ["qcheck"] }
|
||||||
radicle-crypto = { workspace = true, features = ["test"] }
|
radicle-crypto = { workspace = true, features = ["test"] }
|
||||||
radicle-git-metadata = { workspace = true }
|
radicle-git-metadata = { workspace = true }
|
||||||
tempfile = { workspace = true }
|
tempfile = { workspace = true }
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,11 @@ use serde::{Deserialize, Serialize};
|
||||||
use serde_json as json;
|
use serde_json as json;
|
||||||
|
|
||||||
use crate::node;
|
use crate::node;
|
||||||
use crate::node::policy::{Scope, SeedingPolicy};
|
use crate::node::policy::SeedingPolicy;
|
||||||
use crate::node::{Address, Alias, NodeId};
|
use crate::node::{Address, Alias, NodeId};
|
||||||
|
|
||||||
|
use super::policy;
|
||||||
|
|
||||||
/// Peer-to-peer protocol version.
|
/// Peer-to-peer protocol version.
|
||||||
pub type ProtocolVersion = u8;
|
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.
|
/// Default seeding policy. Applies when no repository policies for the given repo are found.
|
||||||
#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[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))]
|
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||||
pub enum DefaultSeedingPolicy {
|
pub enum DefaultSeedingPolicy {
|
||||||
/// Allow seeding.
|
/// Allow seeding.
|
||||||
Allow {
|
Allow {
|
||||||
/// Seeding scope.
|
/// Seeding scope.
|
||||||
#[serde(default)]
|
#[serde(skip_serializing_if = "Scope::is_implicit")]
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
},
|
},
|
||||||
/// Block seeding.
|
/// Block seeding.
|
||||||
|
|
@ -378,6 +380,53 @@ pub enum DefaultSeedingPolicy {
|
||||||
Block,
|
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 {
|
impl DefaultSeedingPolicy {
|
||||||
/// Is this an "allow" policy.
|
/// Is this an "allow" policy.
|
||||||
pub fn is_allow(&self) -> bool {
|
pub fn is_allow(&self) -> bool {
|
||||||
|
|
@ -386,7 +435,16 @@ impl DefaultSeedingPolicy {
|
||||||
|
|
||||||
/// Seed everything from anyone.
|
/// Seed everything from anyone.
|
||||||
pub fn permissive() -> Self {
|
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 {
|
fn from(policy: DefaultSeedingPolicy) -> Self {
|
||||||
match policy {
|
match policy {
|
||||||
DefaultSeedingPolicy::Block => Self::Block,
|
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)]
|
#[cfg(test)]
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use super::{DefaultSeedingPolicy, Scope};
|
||||||
|
use crate::node::policy;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn partial() {
|
fn partial() {
|
||||||
use super::Config;
|
use super::Config;
|
||||||
|
|
@ -683,4 +747,85 @@ mod test {
|
||||||
);
|
);
|
||||||
assert_eq!(config.limits.connection.outbound.0, 1337);
|
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()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,9 @@ impl Config {
|
||||||
) {
|
) {
|
||||||
log::warn!(target: "radicle", "Overwriting `seedingPolicy` configuration");
|
log::warn!(target: "radicle", "Overwriting `seedingPolicy` configuration");
|
||||||
cfg.node.seeding_policy = match policy {
|
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,
|
Policy::Block => DefaultSeedingPolicy::Block,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue