@@ -28,38 +28,132 @@ pub type BoxedError = Box<dyn std::error::Error + Send + Sync + 'static>;
2828/// Structured error type for the Envision framework.
2929///
3030/// Represents the different categories of errors that can occur when
31- /// using Envision. Each variant provides context about the failure mode.
31+ /// using Envision. Each variant provides structured context about the
32+ /// failure mode, enabling callers to match on specific fields.
3233///
3334/// # Example
3435///
3536/// ```rust
3637/// use envision::error::EnvisionError;
3738///
38- /// let err = EnvisionError::Config("invalid theme name".into());
39- /// assert_eq!(err.to_string(), "configuration error: invalid theme name");
39+ /// let err = EnvisionError::config("theme", "invalid theme name");
40+ /// assert_eq!(
41+ /// err.to_string(),
42+ /// "configuration error: field `theme`: invalid theme name"
43+ /// );
4044/// ```
4145#[ derive( Debug ) ]
4246pub enum EnvisionError {
4347 /// An I/O error occurred (terminal, file system, etc.).
4448 Io ( std:: io:: Error ) ,
4549
4650 /// A rendering error occurred.
47- Render ( String ) ,
51+ Render {
52+ /// The component that failed to render.
53+ component : & ' static str ,
54+ /// Details about the rendering failure.
55+ detail : String ,
56+ } ,
4857
4958 /// A configuration error occurred.
50- Config ( String ) ,
59+ Config {
60+ /// The configuration field that caused the error.
61+ field : String ,
62+ /// The reason the configuration is invalid.
63+ reason : String ,
64+ } ,
5165
5266 /// A subscription error occurred.
53- Subscription ( String ) ,
67+ Subscription {
68+ /// The type of subscription that failed.
69+ subscription_type : & ' static str ,
70+ /// Details about the subscription failure.
71+ detail : String ,
72+ } ,
73+ }
74+
75+ impl EnvisionError {
76+ /// Creates a rendering error.
77+ ///
78+ /// # Example
79+ ///
80+ /// ```rust
81+ /// use envision::error::EnvisionError;
82+ ///
83+ /// let err = EnvisionError::render("ProgressBar", "width must be positive");
84+ /// assert_eq!(
85+ /// err.to_string(),
86+ /// "render error: component `ProgressBar`: width must be positive"
87+ /// );
88+ /// ```
89+ pub fn render ( component : & ' static str , detail : impl Into < String > ) -> Self {
90+ EnvisionError :: Render {
91+ component,
92+ detail : detail. into ( ) ,
93+ }
94+ }
95+
96+ /// Creates a configuration error.
97+ ///
98+ /// # Example
99+ ///
100+ /// ```rust
101+ /// use envision::error::EnvisionError;
102+ ///
103+ /// let err = EnvisionError::config("theme", "unknown theme name");
104+ /// assert_eq!(
105+ /// err.to_string(),
106+ /// "configuration error: field `theme`: unknown theme name"
107+ /// );
108+ /// ```
109+ pub fn config ( field : impl Into < String > , reason : impl Into < String > ) -> Self {
110+ EnvisionError :: Config {
111+ field : field. into ( ) ,
112+ reason : reason. into ( ) ,
113+ }
114+ }
115+
116+ /// Creates a subscription error.
117+ ///
118+ /// # Example
119+ ///
120+ /// ```rust
121+ /// use envision::error::EnvisionError;
122+ ///
123+ /// let err = EnvisionError::subscription("tick", "interval too small");
124+ /// assert_eq!(
125+ /// err.to_string(),
126+ /// "subscription error: type `tick`: interval too small"
127+ /// );
128+ /// ```
129+ pub fn subscription ( subscription_type : & ' static str , detail : impl Into < String > ) -> Self {
130+ EnvisionError :: Subscription {
131+ subscription_type,
132+ detail : detail. into ( ) ,
133+ }
134+ }
54135}
55136
56137impl fmt:: Display for EnvisionError {
57138 fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
58139 match self {
59140 EnvisionError :: Io ( err) => write ! ( f, "I/O error: {}" , err) ,
60- EnvisionError :: Render ( msg) => write ! ( f, "render error: {}" , msg) ,
61- EnvisionError :: Config ( msg) => write ! ( f, "configuration error: {}" , msg) ,
62- EnvisionError :: Subscription ( msg) => write ! ( f, "subscription error: {}" , msg) ,
141+ EnvisionError :: Render { component, detail } => {
142+ write ! ( f, "render error: component `{}`: {}" , component, detail)
143+ }
144+ EnvisionError :: Config { field, reason } => {
145+ write ! ( f, "configuration error: field `{}`: {}" , field, reason)
146+ }
147+ EnvisionError :: Subscription {
148+ subscription_type,
149+ detail,
150+ } => {
151+ write ! (
152+ f,
153+ "subscription error: type `{}`: {}" ,
154+ subscription_type, detail
155+ )
156+ }
63157 }
64158 }
65159}
@@ -68,9 +162,9 @@ impl std::error::Error for EnvisionError {
68162 fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
69163 match self {
70164 EnvisionError :: Io ( err) => Some ( err) ,
71- EnvisionError :: Render ( _ ) | EnvisionError :: Config ( _ ) | EnvisionError :: Subscription ( _ ) => {
72- None
73- }
165+ EnvisionError :: Render { .. }
166+ | EnvisionError :: Config { .. }
167+ | EnvisionError :: Subscription { .. } => None ,
74168 }
75169 }
76170}
@@ -94,22 +188,28 @@ mod tests {
94188
95189 #[ test]
96190 fn render_error_display ( ) {
97- let err = EnvisionError :: Render ( "failed to draw widget" . into ( ) ) ;
98- assert_eq ! ( err. to_string( ) , "render error: failed to draw widget" ) ;
191+ let err = EnvisionError :: render ( "ProgressBar" , "failed to draw widget" ) ;
192+ assert_eq ! (
193+ err. to_string( ) ,
194+ "render error: component `ProgressBar`: failed to draw widget"
195+ ) ;
99196 }
100197
101198 #[ test]
102199 fn config_error_display ( ) {
103- let err = EnvisionError :: Config ( "invalid theme name" . into ( ) ) ;
104- assert_eq ! ( err. to_string( ) , "configuration error: invalid theme name" ) ;
200+ let err = EnvisionError :: config ( "theme" , "invalid theme name" ) ;
201+ assert_eq ! (
202+ err. to_string( ) ,
203+ "configuration error: field `theme`: invalid theme name"
204+ ) ;
105205 }
106206
107207 #[ test]
108208 fn subscription_error_display ( ) {
109- let err = EnvisionError :: Subscription ( "tick interval too small" . into ( ) ) ;
209+ let err = EnvisionError :: subscription ( "tick" , " interval too small") ;
110210 assert_eq ! (
111211 err. to_string( ) ,
112- "subscription error: tick interval too small"
212+ "subscription error: type ` tick`: interval too small"
113213 ) ;
114214 }
115215
@@ -129,25 +229,25 @@ mod tests {
129229
130230 #[ test]
131231 fn render_error_no_source ( ) {
132- let err = EnvisionError :: Render ( " bad render". into ( ) ) ;
232+ let err = EnvisionError :: render ( "Widget" , " bad render") ;
133233 assert ! ( std:: error:: Error :: source( & err) . is_none( ) ) ;
134234 }
135235
136236 #[ test]
137237 fn config_error_no_source ( ) {
138- let err = EnvisionError :: Config ( " bad config". into ( ) ) ;
238+ let err = EnvisionError :: config ( "key" , " bad config") ;
139239 assert ! ( std:: error:: Error :: source( & err) . is_none( ) ) ;
140240 }
141241
142242 #[ test]
143243 fn subscription_error_no_source ( ) {
144- let err = EnvisionError :: Subscription ( " bad sub". into ( ) ) ;
244+ let err = EnvisionError :: subscription ( "tick" , " bad sub") ;
145245 assert ! ( std:: error:: Error :: source( & err) . is_none( ) ) ;
146246 }
147247
148248 #[ test]
149249 fn debug_format ( ) {
150- let err = EnvisionError :: Config ( " test". into ( ) ) ;
250+ let err = EnvisionError :: config ( "key" , " test") ;
151251 let debug = format ! ( "{:?}" , err) ;
152252 assert ! ( debug. contains( "Config" ) ) ;
153253 assert ! ( debug. contains( "test" ) ) ;
@@ -160,4 +260,43 @@ mod tests {
160260 }
161261 assert ! ( returns_boxed( ) . is_err( ) ) ;
162262 }
263+
264+ #[ test]
265+ fn render_error_fields_accessible ( ) {
266+ let err = EnvisionError :: render ( "Table" , "column overflow" ) ;
267+ match err {
268+ EnvisionError :: Render { component, detail } => {
269+ assert_eq ! ( component, "Table" ) ;
270+ assert_eq ! ( detail, "column overflow" ) ;
271+ }
272+ _ => panic ! ( "expected Render variant" ) ,
273+ }
274+ }
275+
276+ #[ test]
277+ fn config_error_fields_accessible ( ) {
278+ let err = EnvisionError :: config ( "tick_rate" , "must be positive" ) ;
279+ match err {
280+ EnvisionError :: Config { field, reason } => {
281+ assert_eq ! ( field, "tick_rate" ) ;
282+ assert_eq ! ( reason, "must be positive" ) ;
283+ }
284+ _ => panic ! ( "expected Config variant" ) ,
285+ }
286+ }
287+
288+ #[ test]
289+ fn subscription_error_fields_accessible ( ) {
290+ let err = EnvisionError :: subscription ( "interval" , "already running" ) ;
291+ match err {
292+ EnvisionError :: Subscription {
293+ subscription_type,
294+ detail,
295+ } => {
296+ assert_eq ! ( subscription_type, "interval" ) ;
297+ assert_eq ! ( detail, "already running" ) ;
298+ }
299+ _ => panic ! ( "expected Subscription variant" ) ,
300+ }
301+ }
163302}
0 commit comments