Skip to main content

ephemeris_forge/
refusal.rs

1use crate::{Lockout, RepoRef, Report};
2
3/// A reason a plan cannot be applied.
4#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
5pub enum Refusal {
6    /// The caller lacks the admin permission that applying settings needs.
7    #[error("administering {repository} requires the admin permission, which the token lacks")]
8    AdminRequired {
9        /// The repository the caller does not administer.
10        repository: RepoRef,
11    },
12    /// The plan would remove grants that give the caller its own access.
13    #[error(
14        "applying would remove the caller's own access through {}, so the configuration must \
15         declare them",
16        lockouts.iter().map(ToString::to_string).collect::<Vec<_>>().join(", ")
17    )]
18    Lockout {
19        /// The grants the plan would remove.
20        lockouts: Vec<Lockout>,
21    },
22    /// The forge reports an archived repository.
23    #[error("the repository is archived, so apply refuses to write anything")]
24    Archived,
25}
26
27impl Report {
28    /// The first reason the plan cannot be applied to `repository`, if any.
29    #[must_use]
30    pub fn refusal(&self, repository: &RepoRef) -> Option<Refusal> {
31        if self.admin == Some(false) {
32            return Some(Refusal::AdminRequired {
33                repository: repository.clone(),
34            });
35        }
36        if !self.lockouts.is_empty() {
37            return Some(Refusal::Lockout {
38                lockouts: self.lockouts.clone(),
39            });
40        }
41        self.archived.then_some(Refusal::Archived)
42    }
43
44    /// The report lines for every change, one per changed field.
45    pub fn lines(&self) -> impl Iterator<Item = String> + '_ {
46        self.changes
47            .iter()
48            .flat_map(|(resource, change)| change.lines(resource))
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    fn repository() -> RepoRef {
57        "owner/name".parse().expect("valid")
58    }
59
60    #[test]
61    fn a_clean_report_has_no_refusal() {
62        assert_eq!(Report::default().refusal(&repository()), None);
63    }
64
65    #[test]
66    fn a_missing_admin_permission_comes_first() {
67        let report = Report {
68            admin: Some(false),
69            archived: true,
70            ..Report::default()
71        };
72        assert_eq!(
73            report.refusal(&repository()),
74            Some(Refusal::AdminRequired {
75                repository: repository()
76            })
77        );
78    }
79
80    #[test]
81    fn an_archived_repository_is_refused() {
82        let report = Report {
83            archived: true,
84            ..Report::default()
85        };
86        assert_eq!(report.refusal(&repository()), Some(Refusal::Archived));
87    }
88}