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