Skip to main content

didbot_scope/
action.rs

1//! `action=` values, and the set a scope carries.
2
3use std::collections::BTreeSet;
4use std::fmt;
5
6use crate::ScopeParseError;
7
8/// One action a `repo:` scope's `action=` query may name. `account:` has its
9/// own, in [`crate::AccountAction`].
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub enum Action {
12    /// `repo:`'s "may create a new record".
13    Create,
14    /// `repo:`'s "may update an existing record".
15    Update,
16    /// `repo:`'s "may delete a record".
17    Delete,
18    /// `manage`, which the permission spec does not give `repo:`. Every
19    /// write asks for one of the other three, so a scope naming only this
20    /// one grants no write.
21    Manage,
22}
23
24impl Action {
25    fn as_str(self) -> &'static str {
26        match self {
27            Action::Create => "create",
28            Action::Update => "update",
29            Action::Delete => "delete",
30            Action::Manage => "manage",
31        }
32    }
33
34    pub(crate) fn parse(s: &str) -> Result<Self, ScopeParseError> {
35        match s {
36            "create" => Ok(Action::Create),
37            "update" => Ok(Action::Update),
38            "delete" => Ok(Action::Delete),
39            "manage" => Ok(Action::Manage),
40            other => Err(ScopeParseError::UnknownAction(other.to_owned())),
41        }
42    }
43}
44
45/// The set of actions a `repo:` scope grants.
46///
47/// `All` is distinct from an explicit enumeration of every action: it is
48/// what an atom with no `action=` query means, and it is what containment
49/// treats as a genuine superset rather than an enumeration that happens to
50/// be complete, so a ceiling can grant `All` once and still contain a
51/// profile that later adds a fourth action this grammar does not know about
52/// yet — which an enumerated `{create, update, delete}` could not.
53#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
54pub enum ActionSet {
55    /// No `action=` query — every action this scope kind defines.
56    All,
57    /// An explicit `action=` list.
58    Only(BTreeSet<Action>),
59}
60
61impl ActionSet {
62    pub(crate) fn contains(&self, other: &ActionSet) -> bool {
63        match (self, other) {
64            (ActionSet::All, _) => true,
65            (ActionSet::Only(_), ActionSet::All) => false,
66            (ActionSet::Only(a), ActionSet::Only(b)) => b.is_subset(a),
67        }
68    }
69
70    pub(crate) fn intersect(&self, other: &ActionSet) -> Option<ActionSet> {
71        match (self, other) {
72            (ActionSet::All, x) | (x, ActionSet::All) => Some(x.clone()),
73            (ActionSet::Only(a), ActionSet::Only(b)) => {
74                let both: BTreeSet<Action> = a.intersection(b).copied().collect();
75                if both.is_empty() {
76                    None
77                } else {
78                    Some(ActionSet::Only(both))
79                }
80            }
81        }
82    }
83}
84
85impl fmt::Display for ActionSet {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            ActionSet::All => Ok(()),
89            ActionSet::Only(actions) => {
90                let mut sep = '?';
91                for action in actions {
92                    write!(f, "{sep}action={}", action.as_str())?;
93                    sep = '&';
94                }
95                Ok(())
96            }
97        }
98    }
99}