Skip to main content

git_policy_cli/
worktree_copy_error.rs

1//! What:
2//!  The one failure type of the linked-worktree ignored-state copy,
3//!  with the complete user-facing diagnostic in its message.
4//! Why:
5//!  Every copy step fails with a message that names the path it could not handle
6//!  and, when there is one, the operating system's cause.
7//!  The incumbent renders the cause after the message with a fixed separator,
8//!  so both wrappers print the same words.
9//!
10//! In TS you'd write (pseudocode):
11//! ```ts
12//! class WorktreeCopyError extends Error {
13//!   constructor(message: string, cause?: unknown) { super(`${message}${causeSuffix(cause)}`); }
14//! }
15//! ```
16
17/// What:
18///  The generic message the lifecycle reports for a failure that is not already a copy failure.
19/// Why:
20///   The incumbent's `asWorktreeCopyError` wraps unknown failures with this exact sentence.
21///
22/// In TS you'd write (pseudocode):
23/// ```ts
24/// 'cli-git: automatic ignored-state worktree copy failed.'
25/// ```
26pub const AUTOMATIC_FAILURE_MESSAGE: &str =
27    "cli-git: automatic ignored-state worktree copy failed.";
28
29/// What:
30///  One copy failure and its complete diagnostic.
31/// Why:
32///   Callers print the message unchanged; the lifecycle decides the exit status.
33///
34/// In TS you'd write (pseudocode):
35/// ```ts
36/// class WorktreeCopyError extends Error {}
37/// ```
38#[derive(Clone, Debug, Eq, PartialEq)]
39pub struct WorktreeCopyError(pub String);
40
41/// What:
42///  The diagnostic of one failed step: its message, then the cause when one exists.
43/// Why:
44///   A cause is appended exactly once, after the separator the incumbent uses.
45///
46/// In TS you'd write (pseudocode):
47/// ```ts
48/// `${message}${cause === undefined ? '' : ` Cause: ${caughtValueText(cause)}`}`
49/// ```
50pub fn failure_with_cause(
51    message: &str,
52    cause: Option<&dyn std::fmt::Display>,
53) -> WorktreeCopyError {
54    // `match` on the optional cause keeps the no-cause message free of any suffix.
55    match cause {
56        None => {
57            return WorktreeCopyError(String::from(message));
58        }
59        Some(text) => {
60            return WorktreeCopyError(format!("{message} Cause: {text}"));
61        }
62    }
63}
64
65/// What:
66///  A failure from the operating system or from a process, carried as its own text with no wrapper sentence.
67/// Why:
68///   Every step that does not own a wording of its own leaves the raw text to the nearest enclosing wrapper.
69///   Messages cli-git owns always start with `cli-git:`, so a raw text can be told apart without another flag.
70///
71/// In TS you'd write (pseudocode):
72/// ```ts
73/// error // a non-WorktreeCopyError propagates unchanged to the caller that wraps it
74/// ```
75pub fn raw_failure(cause: &dyn std::fmt::Display) -> WorktreeCopyError {
76    return WorktreeCopyError(cause.to_string());
77}
78
79/// What:
80///  Whether a failure is raw text that an enclosing wrapper still has to word.
81/// Why:
82///   The incumbent wraps only failures that are not already its own `WorktreeCopyError`, and cli-git's own messages always start with its prefix.
83///
84/// In TS you'd write (pseudocode):
85/// ```ts
86/// !(error instanceof WorktreeCopyError)
87/// ```
88pub fn is_raw_failure(error: &WorktreeCopyError) -> bool {
89    return !error.0.starts_with("cli-git:");
90}
91
92/// What:
93///  Words a raw failure with the given message, and leaves an owned failure as it is.
94/// Why:
95///   Each wrapping step names the input it was handling, and never hides a message cli-git already wrote.
96///
97/// In TS you'd write (pseudocode):
98/// ```ts
99/// error instanceof WorktreeCopyError ? error : new WorktreeCopyError(message, error)
100/// ```
101pub fn wrap_raw(error: WorktreeCopyError, message: &str) -> WorktreeCopyError {
102    // `if` keeps owned messages untouched and words only raw text.
103    if is_raw_failure(&error) {
104        return failure_with_cause(message, Some(&error.0));
105    }
106    return error;
107}
108
109/// What:
110///  The lifecycle's wording for a raw failure that reached the top of a copy.
111/// Why:
112///   The user sees the generic sentence once, with the underlying cause after it.
113///
114/// In TS you'd write (pseudocode):
115/// ```ts
116/// function asWorktreeCopyError(error: unknown): WorktreeCopyError;
117/// ```
118pub fn lifecycle_failure(error: WorktreeCopyError) -> WorktreeCopyError {
119    return wrap_raw(error, AUTOMATIC_FAILURE_MESSAGE);
120}
121
122/// Error message controls stay out of the release executable.
123#[cfg(test)]
124#[path = "worktree_copy_error_tests.rs"]
125mod tests;