Skip to main content

git_policy_cli/
policy_trigger.rs

1//! What:
2//!  The lifecycle points at which policies run,
3//!  which shipped policy runs at which
4//!       point,
5//!  and which points this executable can serve.
6//! Why:
7//!  A policy runs only for the triggers it declares.
8//!  A trigger whose lifecycle is not
9//!      ported must be answered as "unavailable",
10//!  never as an empty result that would read
11//!      as "checked and clean".
12//!
13//! In TS you'd write (pseudocode):
14//! ```ts
15//! // type PolicyTrigger = 'pre-forward' | 'post-commit' | 'manual-push' | 'direct-check' | 'direct-fix';
16//! ```
17
18/// What:
19///  `use` brings a name from a sibling file into this file;
20///  `super::` means "the
21///       parent module",
22///  where every sibling file of this crate is declared.
23/// Why:
24///   The trigger table is keyed by the registry's typed policy identity.
25///
26/// In TS you'd write (pseudocode):
27/// ```ts
28/// import { PolicyId } from './policy_registry.ts';
29/// ```
30use super::policy_registry::PolicyId;
31
32/// What:
33///  One lifecycle point.
34///  An `enum` is a closed set of named alternatives.
35///       `#[derive(...)]` asks the compiler to generate copying (`Clone`,
36///  `Copy`),
37///  debug
38///       printing (`Debug`) and `==` (`Eq`,
39///  `PartialEq`).
40/// Why:
41///   The set is fixed by the event format,
42///  where each event names its trigger.
43///
44/// In TS you'd write (pseudocode):
45/// ```ts
46/// type PolicyTrigger = 'pre-forward' | 'post-commit' | 'manual-push' | 'direct-check' | 'direct-fix';
47/// ```
48#[derive(Clone, Copy, Debug, Eq, PartialEq)]
49pub enum Trigger {
50    /// Before a wrapped Git command is forwarded.
51    PreForward,
52    /// After a commit landed,
53    ///  against the landed commit.
54    PostCommit,
55    /// Before a manual `git push`,
56    ///  against newly published content.
57    ManualPush,
58    /// `git cli-git check`.
59    DirectCheck,
60    /// `git cli-git fix`.
61    DirectFix,
62}
63
64/// What:
65///  The wire spelling of a trigger.
66///  `&'static str` is text baked into the program
67///       for its whole run (sibling `String` would be an owned copy).
68/// Why:
69///   Events print exactly the spellings the incumbent emitted.
70///
71/// In TS you'd write (pseudocode):
72/// ```ts
73/// function triggerName(trigger: PolicyTrigger): string { return trigger; }
74/// ```
75pub fn trigger_name(trigger: Trigger) -> &'static str {
76    // `match` picks one arm per variant; the compiler refuses the code if one is forgotten.
77    match trigger {
78        Trigger::PreForward => return "pre-forward",
79        Trigger::PostCommit => return "post-commit",
80        Trigger::ManualPush => return "manual-push",
81        Trigger::DirectCheck => return "direct-check",
82        Trigger::DirectFix => return "direct-fix",
83    }
84}
85
86/// What:
87///  Every trigger.
88///  `&[Trigger]` is a borrowed,
89///  read-only list (siblings:
90///  `Vec<T>`,
91///       owned and growable;
92///  `[T; N]`,
93///  fixed length in the type).
94/// Why:
95///   Two shipped policies check content at every lifecycle point.
96///
97/// In TS you'd write (pseudocode):
98/// ```ts
99/// const EVERY_TRIGGER = ['pre-forward', 'post-commit', 'manual-push', 'direct-check', 'direct-fix'] as const;
100/// ```
101const EVERY_TRIGGER: &[Trigger] = &[
102    Trigger::PreForward,
103    Trigger::PostCommit,
104    Trigger::ManualPush,
105    Trigger::DirectCheck,
106    Trigger::DirectFix,
107];
108
109/// What:
110///  The triggers a shipped policy declares,
111///  copied from the incumbent definitions.
112/// Why:
113///   The engine skips a policy at every other lifecycle point,
114///  exactly as the
115///       incumbent's stage did.
116///
117/// In TS you'd write (pseudocode):
118/// ```ts
119/// function policyTriggers(id: PolicyId): readonly PolicyTrigger[];
120/// ```
121pub fn policy_triggers(id: PolicyId) -> &'static [Trigger] {
122    // `|` joins patterns that share one arm; `&[...]` is a list baked into the program.
123    match id {
124        PolicyId::RequireRoot | PolicyId::ForbiddenRootContext => {
125            return &[Trigger::PreForward, Trigger::DirectCheck];
126        }
127        PolicyId::LinkedWorktreeOnly | PolicyId::BranchWorktreeOnly | PolicyId::AddExplicit => {
128            return &[Trigger::PreForward];
129        }
130        PolicyId::FinalNewline | PolicyId::MarkdownAutofix => return EVERY_TRIGGER,
131        PolicyId::DependentVersionBump => {
132            return &[
133                Trigger::PreForward,
134                Trigger::DirectCheck,
135                Trigger::DirectFix,
136            ];
137        }
138        PolicyId::ForbiddenStrings => {
139            return &[
140                Trigger::PreForward,
141                Trigger::PostCommit,
142                Trigger::ManualPush,
143                Trigger::DirectCheck,
144            ];
145        }
146    }
147}
148
149/// What:
150///  Whether a policy declares a trigger.
151///  `bool` is true or false.
152/// Why:
153///   The stage asks this once per policy before anything else.
154///
155/// In TS you'd write (pseudocode):
156/// ```ts
157/// const runs = policy.triggers.includes(trigger);
158/// ```
159pub fn policy_runs_on(id: PolicyId, trigger: Trigger) -> bool {
160    // `.contains(&trigger)` borrows the trigger for the lookup.
161    return policy_triggers(id).contains(&trigger);
162}
163
164/// What:
165///  Whether this executable implements the lifecycle a trigger belongs to.
166/// Why:
167///  Every lifecycle is ported: the exhaustive match below answers true for each variant, and a new
168///  variant fails to compile until it is answered here.
169///  The engine still checks this before running a lifecycle, so a future unported variant gets a typed
170///  "unavailable" instead of running zero policies and looking clean.
171///
172/// In TS you'd write (pseudocode):
173/// ```ts
174/// const ported = (trigger: Trigger): boolean => true;
175/// ```
176pub fn trigger_is_ported(trigger: Trigger) -> bool {
177    match trigger {
178        Trigger::PreForward
179        | Trigger::PostCommit
180        | Trigger::ManualPush
181        | Trigger::DirectCheck
182        | Trigger::DirectFix => {
183            return true;
184        }
185    }
186}
187
188/// Trigger sets,
189///  spellings and the ported set stay out of the release executable.
190#[cfg(test)]
191#[path = "policy_trigger_tests.rs"]
192mod tests;