@@ -23,6 +23,16 @@ impl ErrorKind {
2323 Self :: Conflict => 5 ,
2424 }
2525 }
26+
27+ pub fn label ( self ) -> & ' static str {
28+ match self {
29+ Self :: Internal => "internal" ,
30+ Self :: InvalidInput => "invalid-input" ,
31+ Self :: Git => "git" ,
32+ Self :: Cargo => "cargo" ,
33+ Self :: Conflict => "conflict" ,
34+ }
35+ }
2636}
2737
2838#[ derive( Debug , Error ) ]
@@ -40,6 +50,10 @@ impl CargoMonoError {
4050 }
4151 }
4252
53+ pub fn with_hint ( kind : ErrorKind , summary : impl AsRef < str > , hint : impl AsRef < str > ) -> Self {
54+ Self :: new ( kind, message_with_hint ( summary, hint) )
55+ }
56+
4357 pub fn internal ( message : impl Into < String > ) -> Self {
4458 Self :: new ( ErrorKind :: Internal , message)
4559 }
@@ -67,31 +81,51 @@ impl CargoMonoError {
6781
6882impl From < io:: Error > for CargoMonoError {
6983 fn from ( value : io:: Error ) -> Self {
70- Self :: internal ( format ! ( "I/O error: {value}" ) )
84+ Self :: with_hint (
85+ ErrorKind :: Internal ,
86+ format ! ( "I/O operation failed: {value}" ) ,
87+ "Verify paths and permissions, then retry the command." ,
88+ )
7189 }
7290}
7391
7492impl From < cargo_metadata:: Error > for CargoMonoError {
7593 fn from ( value : cargo_metadata:: Error ) -> Self {
76- Self :: cargo ( format ! ( "cargo metadata error: {value}" ) )
94+ Self :: with_hint (
95+ ErrorKind :: Cargo ,
96+ format ! ( "Failed to load workspace metadata via cargo: {value}" ) ,
97+ "Run `cargo metadata` in this workspace to reproduce and inspect the root cause." ,
98+ )
7799 }
78100}
79101
80102impl From < serde_json:: Error > for CargoMonoError {
81103 fn from ( value : serde_json:: Error ) -> Self {
82- Self :: internal ( format ! ( "JSON error: {value}" ) )
104+ Self :: with_hint (
105+ ErrorKind :: Internal ,
106+ format ! ( "Failed to parse JSON content: {value}" ) ,
107+ "Check the JSON payload for syntax issues near the reported location." ,
108+ )
83109 }
84110}
85111
86112impl From < semver:: Error > for CargoMonoError {
87113 fn from ( value : semver:: Error ) -> Self {
88- Self :: invalid_input ( format ! ( "Invalid semantic version: {value}" ) )
114+ Self :: with_hint (
115+ ErrorKind :: InvalidInput ,
116+ format ! ( "Invalid semantic version: {value}" ) ,
117+ "Use a valid SemVer value such as `1.2.3` or `1.2.3-rc.1`." ,
118+ )
89119 }
90120}
91121
92122impl From < toml_edit:: TomlError > for CargoMonoError {
93123 fn from ( value : toml_edit:: TomlError ) -> Self {
94- Self :: internal ( format ! ( "TOML error: {value}" ) )
124+ Self :: with_hint (
125+ ErrorKind :: Internal ,
126+ format ! ( "Failed to parse TOML document: {value}" ) ,
127+ "Check TOML syntax in Cargo manifests and retry." ,
128+ )
95129 }
96130}
97131
@@ -101,6 +135,52 @@ impl From<CargoMonoError> for io::Error {
101135 }
102136}
103137
104- pub fn with_context < E : fmt:: Display > ( kind : ErrorKind , context : & str , error : E ) -> CargoMonoError {
105- CargoMonoError :: new ( kind, format ! ( "{context}: {error}" ) )
138+ pub fn message_with_hint ( summary : impl AsRef < str > , hint : impl AsRef < str > ) -> String {
139+ format ! ( "{} Hint: {}" , summary. as_ref( ) , hint. as_ref( ) )
140+ }
141+
142+ pub fn with_context < E : fmt:: Display > (
143+ kind : ErrorKind ,
144+ context : & str ,
145+ error : E ,
146+ hint : & str ,
147+ ) -> CargoMonoError {
148+ CargoMonoError :: with_hint ( kind, format ! ( "{context}: {error}" ) , hint)
149+ }
150+
151+ #[ cfg( test) ]
152+ mod tests {
153+ use super :: { message_with_hint, CargoMonoError , ErrorKind } ;
154+
155+ #[ test]
156+ fn error_kind_labels_are_stable ( ) {
157+ assert_eq ! ( ErrorKind :: Internal . label( ) , "internal" ) ;
158+ assert_eq ! ( ErrorKind :: InvalidInput . label( ) , "invalid-input" ) ;
159+ assert_eq ! ( ErrorKind :: Git . label( ) , "git" ) ;
160+ assert_eq ! ( ErrorKind :: Cargo . label( ) , "cargo" ) ;
161+ assert_eq ! ( ErrorKind :: Conflict . label( ) , "conflict" ) ;
162+ }
163+
164+ #[ test]
165+ fn message_with_hint_uses_single_line_contract ( ) {
166+ let message = message_with_hint ( "Unable to read manifest." , "Check file permissions." ) ;
167+ assert_eq ! (
168+ message,
169+ "Unable to read manifest. Hint: Check file permissions."
170+ ) ;
171+ }
172+
173+ #[ test]
174+ fn with_hint_formats_error_message ( ) {
175+ let error = CargoMonoError :: with_hint (
176+ ErrorKind :: InvalidInput ,
177+ "Invalid package selector." ,
178+ "Run `cargo mono list`." ,
179+ ) ;
180+ assert_eq ! ( error. kind, ErrorKind :: InvalidInput ) ;
181+ assert_eq ! (
182+ error. message,
183+ "Invalid package selector. Hint: Run `cargo mono list`."
184+ ) ;
185+ }
106186}
0 commit comments