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#[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}