radicle/node/policy/
config.rs

1use core::fmt;
2use std::collections::HashSet;
3use std::ops;
4
5use log::error;
6use thiserror::Error;
7
8use crate::crypto::PublicKey;
9use crate::prelude::RepoId;
10use crate::storage::{Namespaces, ReadRepository as _, ReadStorage, RepositoryError};
11
12pub use crate::node::policy::store;
13pub use crate::node::policy::store::Error;
14pub use crate::node::policy::store::Store;
15pub use crate::node::policy::{Alias, FollowPolicy, Policy, Scope, SeedPolicy, SeedingPolicy};
16
17#[derive(Debug, Error)]
18pub enum NamespacesError {
19    #[error("failed to find policy for {rid}")]
20    FailedPolicy {
21        rid: RepoId,
22        #[source]
23        err: Error,
24    },
25    #[error("cannot fetch {rid} as it is not seeded")]
26    BlockedPolicy { rid: RepoId },
27    #[error("failed to get node policies for {rid}")]
28    FailedNodes {
29        rid: RepoId,
30        #[source]
31        err: Error,
32    },
33    #[error("failed to get delegates for {rid}")]
34    FailedDelegates {
35        rid: RepoId,
36        #[source]
37        err: RepositoryError,
38    },
39    #[error(transparent)]
40    Git(#[from] crate::git::raw::Error),
41    #[error("could not find any followed nodes for {rid}")]
42    NoFollowed { rid: RepoId },
43}
44
45/// Policies configuration.
46pub struct Config<T> {
47    /// Default policy, if a policy for a specific node or repository was not found.
48    policy: SeedingPolicy,
49    /// Underlying configuration store.
50    store: Store<T>,
51}
52
53// N.b. deriving `Debug` will require `T: Debug` so we manually
54// implement it here.
55impl<T> fmt::Debug for Config<T> {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.debug_struct("Config")
58            .field("policy", &self.policy)
59            .field("store", &self.store)
60            .finish()
61    }
62}
63
64impl<T> Config<T> {
65    /// Create a new policy configuration.
66    pub fn new(policy: SeedingPolicy, store: Store<T>) -> Self {
67        Self { policy, store }
68    }
69
70    /// Check if a repository is seeded.
71    pub fn is_seeding(&self, rid: &RepoId) -> Result<bool, Error> {
72        self.seed_policy(rid).map(|entry| entry.policy.is_allow())
73    }
74
75    /// Get a repository's seeding information.
76    /// Returns the default policy if the repo isn't found.
77    pub fn seed_policy(&self, rid: &RepoId) -> Result<SeedPolicy, Error> {
78        Ok(self.store.seed_policy(rid)?.unwrap_or(SeedPolicy {
79            rid: *rid,
80            policy: self.policy,
81        }))
82    }
83
84    pub fn namespaces_for<S>(
85        &self,
86        storage: &S,
87        rid: &RepoId,
88    ) -> Result<Namespaces, NamespacesError>
89    where
90        S: ReadStorage,
91    {
92        use NamespacesError::*;
93
94        let entry = self
95            .seed_policy(rid)
96            .map_err(|err| FailedPolicy { rid: *rid, err })?;
97        match entry.policy {
98            SeedingPolicy::Block => {
99                error!(target: "service", "Attempted to fetch untracked repo {rid}");
100                Err(NamespacesError::BlockedPolicy { rid: *rid })
101            }
102            SeedingPolicy::Allow { scope: Scope::All } => Ok(Namespaces::All),
103            SeedingPolicy::Allow {
104                scope: Scope::Followed,
105            } => {
106                let nodes = self
107                    .follow_policies()
108                    .map_err(|err| FailedNodes { rid: *rid, err })?;
109
110                let mut followed: HashSet<_> = HashSet::new();
111                for node in nodes {
112                    let node = match node {
113                        Ok(node) => node,
114                        Err(err) => {
115                            log::warn!(target: "service", "Failed to read follow policy: {err}");
116                            continue;
117                        }
118                    };
119                    if node.policy == Policy::Allow {
120                        followed.insert(node.nid);
121                    }
122                }
123
124                if let Ok(repo) = storage.repository(*rid) {
125                    let delegates = repo
126                        .delegates()
127                        .map_err(|err| FailedDelegates { rid: *rid, err })?
128                        .map(PublicKey::from);
129                    followed.extend(delegates);
130                };
131                if followed.is_empty() {
132                    // Nb. returning All here because the
133                    // fetching logic will correctly determine
134                    // followed and delegate remotes.
135                    Ok(Namespaces::All)
136                } else {
137                    Ok(Namespaces::Followed(followed))
138                }
139            }
140        }
141    }
142}
143
144impl<T> ops::Deref for Config<T> {
145    type Target = Store<T>;
146
147    fn deref(&self) -> &Self::Target {
148        &self.store
149    }
150}
151
152impl<T> ops::DerefMut for Config<T> {
153    fn deref_mut(&mut self) -> &mut Self::Target {
154        &mut self.store
155    }
156}