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;