Skip to main content

git_policy_cli/
unported.rs

1//! What:
2//!  The work of the installed cli-git that this executable does not do yet,
3//!  as one
4//!       typed list,
5//!  and the notice printed when a command needs any of it.
6//! Why:
7//!  A command that needs unported work must stop with exit status 2 and say what is
8//!      missing.
9//!  Forwarding it instead would run the command without the protection the
10//!      installed wrapper gives it,
11//!  and nobody would see that it happened.
12//!
13//! In TS you'd write (pseudocode):
14//! ```ts
15//! // process.stderr.write(unportedNotice({ kind: 'commit-transaction' }, 'commit')); process.exitCode = 2;
16//! ```
17
18/// What:
19///  `use` brings names from sibling files into this file;
20///  `super::` means "the parent
21///       module",
22///  where every sibling file of this crate is declared.
23/// Why:
24///   A refusal can name a policy,
25///  a lifecycle trigger,
26///  or an engine "unavailable" answer.
27///
28/// In TS you'd write (pseudocode):
29/// ```ts
30/// import { triggerName } from './policy_trigger.ts';
31/// ```
32use super::policy_engine::Unavailable;
33use super::policy_registry::{PolicyId, policy_descriptor};
34use super::policy_trigger::{Trigger, trigger_name};
35/// What:
36///  `PathBuf` is an owned filesystem path of raw bytes.
37///  Sibling the reader might
38///       expect:
39///  `String`,
40///  which must be valid UTF-8.
41/// Why:
42///   A refusal about leftover state names the directory that holds it.
43///
44/// In TS you'd write (pseudocode):
45/// ```ts
46/// // string, but byte-preserving.
47/// ```
48use std::path::PathBuf;
49
50/// What:
51///  One piece of unported work.
52///  An `enum` is a closed set of named alternatives;
53///  some
54///       carry a value.
55///  `#[derive(...)]` asks the compiler to generate copying,
56///  debug
57///       printing and `==`.
58/// Why:
59///   Every refusal of this executable is one of these,
60///  so the list of what stops a
61///       command is in one place and a test can name the exact reason.
62///
63/// In TS you'd write (pseudocode):
64/// ```ts
65/// type Unported =
66///   | { kind: 'transaction-recovery'; directory: string }
67///   | { kind: 'lifecycle'; trigger: PolicyTrigger }
68///   | { kind: 'policy-needs'; policy: PolicyId; needs: string };
69/// ```
70#[derive(Clone, Debug, Eq, PartialEq)]
71pub enum Unported {
72    /// Recovering,
73    ///  or waiting for,
74    ///  the commit transactions recorded in this directory.
75    TransactionRecovery(PathBuf),
76    /// The policy lifecycle of this trigger.
77    Lifecycle(Trigger),
78    /// Something one policy needs before it can be evaluated.
79    PolicyNeeds {
80        /// The policy that could not be evaluated.
81        policy: PolicyId,
82        /// What it needs,
83        ///  in words.
84        needs: &'static str,
85    },
86}
87
88/// What:
89///  Turn the engine's "unavailable" answer into the refusal it stands for.
90/// Why:
91///   The engine reports what it could not evaluate;
92///  the wrapper refuses with one
93///       vocabulary whether the gap was found by the engine or before it.
94///
95/// In TS you'd write (pseudocode):
96/// ```ts
97/// function unportedFromUnavailable(unavailable: Unavailable): Unported;
98/// ```
99pub fn unported_from_unavailable(unavailable: Unavailable) -> Unported {
100    // `match` picks one arm per variant and binds the fields each one carries.
101    match unavailable {
102        Unavailable::Lifecycle(trigger) => return Unported::Lifecycle(trigger),
103        Unavailable::Policy { policy, needs } => return Unported::PolicyNeeds { policy, needs },
104    }
105}
106
107/// What:
108///  Name the missing work as the subject of the sentence "... is not implemented".
109///       `&Unported` borrows the reason;
110///  `String` is owned UTF-8 text.
111/// Why:
112///   The person reading the notice must learn which protection is missing,
113///  in plain
114///       words,
115///  and what they can do when there is a way to avoid needing it.
116///
117/// In TS you'd write (pseudocode):
118/// ```ts
119/// function unportedSubject(what: Unported): string;
120/// ```
121fn unported_subject(what: &Unported) -> String {
122    match what {
123        // `format!` builds owned text; `.display()` shows a path, replacing bytes that are not UTF-8.
124        Unported::TransactionRecovery(directory) => {
125            return format!(
126                "recovering or waiting for the commit transactions recorded in {}",
127                directory.display()
128            );
129        }
130        // `*trigger` copies the small trigger value out of the borrowed reason.
131        Unported::Lifecycle(trigger) => {
132            return format!("the {} policy lifecycle", trigger_name(*trigger));
133        }
134        Unported::PolicyNeeds { policy, needs } => {
135            return format!(
136                "{needs}, which the policy {} needs,",
137                policy_descriptor(*policy).name
138            );
139        }
140    }
141}
142
143/// What:
144///  The complete,
145///  line-terminated notice for a command stopped by unported work.
146///       `&str` borrows the command words as the caller typed them.
147/// Why:
148///   One sentence shape for every refusal:
149///  what is missing,
150///  that the command did not
151///       run,
152///  and where the complete implementation is.
153///
154/// In TS you'd write (pseudocode):
155/// ```ts
156/// function unportedNotice(what: Unported, command: string): string;
157/// ```
158pub fn unported_notice(what: &Unported, command: &str) -> String {
159    return format!(
160        "cli-git: {} is not implemented in this native development executable, so git \
161         {command} was not run. Run it with the installed cli-git.\n",
162        unported_subject(what)
163    );
164}
165
166/// The wording of every refusal stays out of the release executable.
167#[cfg(test)]
168#[path = "unported_tests.rs"]
169mod tests;