git_policy_cli/config_error.rs
1//! What:
2//! The one failure type for rejected `cli-git.config.jsonc` content or files.
3//! Why:
4//! Callers render a single configuration diagnostic without parsing message text.
5//!
6//! In TS you'd write (pseudocode):
7//! ```ts
8//! class ConfigError extends Error {}
9//! ```
10
11/// What:
12/// `ConfigError` owns the explanation of one rejected configuration.
13/// `String` is a heap-allocated,
14/// growable UTF-8 buffer this struct owns.
15/// Sibling the reader might expect:
16/// `&str`,
17/// a borrowed view that owns nothing.
18/// Why:
19/// The error outlives the parsed document and the temporary formatted text,
20/// so a borrowed `&str` would dangle;
21/// `String` keeps the bytes alive.
22///
23/// In TS you'd write (pseudocode):
24/// ```ts
25/// class ConfigError extends Error { readonly message: string }
26/// ```
27#[derive(Clone, Debug, Eq, PartialEq)]
28pub struct ConfigError {
29 /// Complete diagnostic naming the affected key or file and the accepted values.
30 pub message: String,
31}
32
33/// What:
34/// `impl ConfigError { ... }` attaches functions to the struct,
35/// like class statics.
36/// Why:
37/// Validation sites keep their condition next to its user-facing explanation.
38///
39/// In TS you'd write (pseudocode):
40/// ```ts
41/// new ConfigError(message);
42/// ```
43impl ConfigError {
44 /// What:
45 /// Build an error from borrowed text.
46 /// `&str` lends the caller's bytes;
47 /// `String::from` copies them into storage the error owns.
48 /// Why:
49 /// Messages are usually `format!` temporaries that end with the caller's statement.
50 ///
51 /// In TS you'd write (pseudocode):
52 /// ```ts
53 /// constructor(message: string) { super(message); }
54 /// ```
55 pub fn new(message: &str) -> ConfigError {
56 // What: `return ConfigError { message: ... };` builds the struct and hands it back.
57 // Why: Rust never copies a borrowed string into an owning field implicitly.
58 //
59 // In TS you'd write (pseudocode):
60 // ```ts
61 // return { message };
62 // ```
63 return ConfigError {
64 message: String::from(message),
65 };
66 }
67}
68
69/// What:
70/// `impl std::fmt::Display for ConfigError` supplies Rust's "print me" interface.
71/// `::` walks module paths,
72/// like `std.fmt.Display` would in TS.
73/// Why:
74/// The executable writes the message with ordinary formatting,
75/// not field access.
76///
77/// In TS you'd write (pseudocode):
78/// ```ts
79/// toString(): string { return this.message; }
80/// ```
81impl std::fmt::Display for ConfigError {
82 /// What:
83 /// `&self` borrows this error read-only;
84 /// `&mut` lends the formatter for writing;
85 /// `'_` is an unnamed lifetime the compiler fills in.
86 /// Why:
87 /// Formatting writes into the caller's buffer without allocating another string.
88 ///
89 /// In TS you'd write (pseudocode):
90 /// ```ts
91 /// format(out: Writer): void { out.write(this.message); }
92 /// ```
93 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
94 // `.as_str()` lends the owned message as a borrowed view for the writer.
95 return formatter.write_str(self.message.as_str());
96 }
97}
98
99/// What:
100/// An empty `impl` marks the type as a standard error value.
101/// Why:
102/// Generic error handling can carry it without converting it to a bare string.
103///
104/// In TS you'd write (pseudocode):
105/// ```ts
106/// // `extends Error` already provides this in TypeScript.
107/// ```
108impl std::error::Error for ConfigError {}
109
110/// Message controls stay out of the release executable.
111#[cfg(test)]
112#[path = "config_error_tests.rs"]
113mod tests;