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}