radicle_fetch/
policy.rs

1use std::collections::HashSet;
2
3use radicle::crypto::PublicKey;
4use radicle::node::policy::config::Config;
5use radicle::node::policy::store::Read;
6use radicle::prelude::RepoId;
7
8pub use radicle::node::policy::{Policy, Scope, SeedingPolicy};
9
10#[derive(Clone, Debug)]
11pub enum Allowed {
12    All,
13    Followed { remotes: HashSet<PublicKey> },
14}
15
16impl Allowed {
17    pub fn from_config(rid: RepoId, config: &Config<Read>) -> Result<Self, error::Policy> {
18        let entry = config
19            .seed_policy(&rid)
20            .map_err(|err| error::Policy::FailedPolicy { rid, err })?;
21        match entry.policy {
22            SeedingPolicy::Block => {
23                log::error!(target: "fetch", "Attempted to fetch non-seeded repo {rid}");
24                Err(error::Policy::BlockedPolicy { rid })
25            }
26            SeedingPolicy::Allow { scope: Scope::All } => Ok(Self::All),
27            SeedingPolicy::Allow {
28                scope: Scope::Followed,
29            } => {
30                let nodes = config
31                    .follow_policies()
32                    .map_err(|err| error::Policy::FailedNodes { rid, err })?;
33
34                let mut followed = HashSet::new();
35
36                for node in nodes {
37                    let node = match node {
38                        Ok(policy) => policy,
39                        Err(err) => {
40                            log::error!(target: "fetch", "Failed to read follow policy for {rid}: {err}");
41                            continue;
42                        }
43                    };
44
45                    if node.policy == Policy::Allow {
46                        followed.insert(node.nid);
47                    }
48                }
49
50                Ok(Allowed::Followed { remotes: followed })
51            }
52        }
53    }
54}
55
56/// A set of [`PublicKey`]s to ignore when fetching from a remote.
57#[derive(Clone, Debug)]
58pub struct BlockList(HashSet<PublicKey>);
59
60impl FromIterator<PublicKey> for BlockList {
61    fn from_iter<T: IntoIterator<Item = PublicKey>>(iter: T) -> Self {
62        Self(iter.into_iter().collect())
63    }
64}
65
66impl Extend<PublicKey> for BlockList {
67    fn extend<T: IntoIterator<Item = PublicKey>>(&mut self, iter: T) {
68        self.0.extend(iter)
69    }
70}
71
72impl BlockList {
73    pub fn is_blocked(&self, key: &PublicKey) -> bool {
74        self.0.contains(key)
75    }
76
77    pub fn from_config(config: &Config<Read>) -> Result<BlockList, error::Blocked> {
78        let mut blocked = HashSet::new();
79
80        for entry in config.follow_policies()? {
81            let entry = match entry {
82                Ok(entry) => entry,
83                Err(err) => {
84                    log::error!(target: "fetch", "Failed to read follow policy: {err}");
85                    continue;
86                }
87            };
88
89            if entry.policy == Policy::Block {
90                blocked.insert(entry.nid);
91            }
92        }
93
94        Ok(BlockList(blocked))
95    }
96}
97
98pub mod error {
99    use radicle::node::policy;
100    use radicle::prelude::RepoId;
101    use radicle::storage;
102    use thiserror::Error;
103
104    #[derive(Debug, Error)]
105    #[error(transparent)]
106    pub struct Blocked(#[from] policy::config::Error);
107
108    #[derive(Debug, Error)]
109    pub enum Policy {
110        #[error("failed to find policy for {rid}")]
111        FailedPolicy {
112            rid: RepoId,
113            #[source]
114            err: policy::store::Error,
115        },
116        #[error("cannot fetch {rid} as it is not seeded")]
117        BlockedPolicy { rid: RepoId },
118        #[error("failed to get followed nodes for {rid}")]
119        FailedNodes {
120            rid: RepoId,
121            #[source]
122            err: policy::store::Error,
123        },
124
125        #[error(transparent)]
126        Storage(#[from] storage::Error),
127
128        #[error(transparent)]
129        Git(#[from] radicle::git::raw::Error),
130
131        #[error(transparent)]
132        Refs(#[from] storage::refs::Error),
133    }
134}