Skip to main content

didbot_policy/
language.rs

1//! The language a policy's predicate is written in — an open union, the same
2//! shape `bot.did.registration`'s `actor` union already uses and the same
3//! shape this crate gave [`SubjectKind`](crate::subject::SubjectKind):
4//! [`Known`](Language::Known) plus [`Unknown`](Language::Unknown).
5//!
6//! The owner's framing: *"an open union of known types. This includes at
7//! least like... regex, and cedar? And then people who want something
8//! different need to patch the binary and write a new policy type."* This is
9//! that union. It names nothing about how a wrapper record spells its
10//! language — `plan/policy.md` leaves the wrapper's shape unsettled — only
11//! which languages a build can turn into a working
12//! [`Evaluator`](crate::outcome::Evaluator).
13//!
14//! # What a build does with a language it does not have
15//!
16//! It holds the policy and refuses what it covers; it does not fail to
17//! parse the record. A loader resolving a wrapper's declared language to a
18//! [`Predicate`](crate::policy::Predicate) turns [`Language::Unknown`] into
19//! [`Predicate::Unusable`](crate::policy::Predicate::Unusable) — that
20//! variant's doc comment names an unknown language as its motivating case —
21//! never into a parse failure for the record as a whole. The rest of the
22//! record (its applicability) is still known-good data even when its
23//! language is not, and `Predicate::Unusable` is what lets the tree deny
24//! exactly that applicability instead of everything or nothing.
25
26/// A policy language, known to this build or not.
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub enum Language {
29    /// A language this workspace names — see [`KnownLanguage`].
30    Known(KnownLanguage),
31    /// A language this build does not recognize, carrying the string the
32    /// record named it with. Never coerced into a [`KnownLanguage`] and
33    /// never silently ignored — see this module's doc comment for what a
34    /// loader does with it.
35    Unknown(String),
36}
37
38/// The evaluator languages this workspace names. Naming a member here is
39/// naming a sibling crate that implements
40/// [`Evaluator`](crate::outcome::Evaluator) for it — a decision for whoever
41/// maintains this workspace, not something a policy record can request into
42/// existence — but naming one ahead of the crate that implements it is
43/// exactly what an open union is for: it makes that crate's later arrival an
44/// addition to this list, not a change to its shape.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
46pub enum KnownLanguage {
47    /// `didbot-policy-regex`: a deterministic in-process regular-expression
48    /// evaluator, linear-time and safe to run with no deadline.
49    Regex,
50    /// `didbot-policy-cedar`: an authorization-specific, schema-validated
51    /// and analysable language, deny (`forbid`)-only in this system since
52    /// policies here never permit.
53    Cedar,
54}
55
56impl Language {
57    /// Parse a policy record's `language` string into this open union.
58    ///
59    /// Case-sensitive, matching exactly the names a loader compares a
60    /// record's `language` field against (see
61    /// `didbot-policy-records::compile::Language::name`). A name this build
62    /// does not recognize parses to [`Language::Unknown`], never an error:
63    /// this function only classifies a name, it does not judge whether the
64    /// resulting language has a working evaluator behind it. See this
65    /// module's doc comment for what a loader does with the result.
66    #[must_use]
67    pub fn parse(name: &str) -> Language {
68        match name {
69            "regex" => Language::Known(KnownLanguage::Regex),
70            "cedar" => Language::Known(KnownLanguage::Cedar),
71            other => Language::Unknown(other.to_owned()),
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn regex_parses_to_the_known_regex_language() {
82        assert_eq!(
83            Language::parse("regex"),
84            Language::Known(KnownLanguage::Regex)
85        );
86    }
87
88    #[test]
89    fn cedar_parses_to_the_known_cedar_language() {
90        assert_eq!(
91            Language::parse("cedar"),
92            Language::Known(KnownLanguage::Cedar)
93        );
94    }
95
96    #[test]
97    fn an_unrecognized_name_parses_as_unknown() {
98        assert_eq!(
99            Language::parse("nobody-ships-this"),
100            Language::Unknown("nobody-ships-this".to_owned())
101        );
102    }
103}