git_policy_cli/policy/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 crate::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 // Content policies that check what is committed, pushed or named, and fix nothing, run at the
139 // same points: a manifest edited and committed, a landed commit, a push, and `check`.
140 PolicyId::CatalogSpecifiers | PolicyId::ForbiddenStrings => {
141 return &[
142 Trigger::PreForward,
143 Trigger::PostCommit,
144 Trigger::ManualPush,
145 Trigger::DirectCheck,
146 ];
147 }
148 }
149}
150
151/// What:
152/// Whether a policy declares a trigger.
153/// `bool` is true or false.
154/// Why:
155/// The stage asks this once per policy before anything else.
156///
157/// In TS you'd write (pseudocode):
158/// ```ts
159/// const runs = policy.triggers.includes(trigger);
160/// ```
161pub fn policy_runs_on(id: PolicyId, trigger: Trigger) -> bool {
162 // `.contains(&trigger)` borrows the trigger for the lookup.
163 return policy_triggers(id).contains(&trigger);
164}
165
166/// What:
167/// Whether this executable implements the lifecycle a trigger belongs to.
168/// Why:
169/// Every lifecycle is ported: the exhaustive match below answers true for each variant, and a new
170/// variant fails to compile until it is answered here.
171/// The engine still checks this before running a lifecycle, so a future unported variant gets a typed
172/// "unavailable" instead of running zero policies and looking clean.
173///
174/// In TS you'd write (pseudocode):
175/// ```ts
176/// const ported = (trigger: Trigger): boolean => true;
177/// ```
178pub fn trigger_is_ported(trigger: Trigger) -> bool {
179 match trigger {
180 Trigger::PreForward
181 | Trigger::PostCommit
182 | Trigger::ManualPush
183 | Trigger::DirectCheck
184 | Trigger::DirectFix => {
185 return true;
186 }
187 }
188}
189
190/// Trigger sets,
191/// spellings and the ported set stay out of the release executable.
192#[cfg(test)]
193#[path = "policy_trigger_tests.rs"]
194mod tests;