StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.
| 1 | //! Error types for StratoUI framework |
| 2 | |
| 3 | use std::collections::HashMap; |
| 4 | use thiserror::Error; |
| 5 | |
| 6 | /// Context information for errors to aid in debugging |
| 7 | #[derive(Debug, Clone)] |
| 8 | pub struct ErrorContext { |
| 9 | /// Operation that was being performed when the error occurred |
| 10 | pub operation: String, |
| 11 | /// Component or module where the error occurred |
| 12 | pub component: String, |
| 13 | /// Additional contextual data |
| 14 | pub metadata: HashMap<String, String>, |
| 15 | /// Stack trace or call path if available |
| 16 | pub call_path: Option<String>, |
| 17 | } |
| 18 | |
| 19 | impl ErrorContext { |
| 20 | /// Create a new error context |
| 21 | pub fn new(operation: impl Into<String>, component: impl Into<String>) -> Self { |
| 22 | Self { |
| 23 | operation: operation.into(), |
| 24 | component: component.into(), |
| 25 | metadata: HashMap::new(), |
| 26 | call_path: None, |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | /// Add metadata to the context |
| 31 | pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self { |
| 32 | self.metadata.insert(key.into(), value.into()); |
| 33 | self |
| 34 | } |
| 35 | |
| 36 | /// Add call path information |
| 37 | pub fn with_call_path(mut self, path: impl Into<String>) -> Self { |
| 38 | self.call_path = Some(path.into()); |
| 39 | self |
| 40 | } |
| 41 | |
| 42 | /// Format context for logging |
| 43 | pub fn format_for_log(&self) -> String { |
| 44 | let mut parts = vec![ |
| 45 | format!("operation={}", self.operation), |
| 46 | format!("component={}", self.component), |
| 47 | ]; |
| 48 | |
| 49 | if !self.metadata.is_empty() { |
| 50 | let metadata_str = self |
| 51 | .metadata |
| 52 | .iter() |
| 53 | .map(|(k, v)| format!("{}={}", k, v)) |
| 54 | .collect::<Vec<_>>() |
| 55 | .join(", "); |
| 56 | parts.push(format!("metadata=[{}]", metadata_str)); |
| 57 | } |
| 58 | |
| 59 | if let Some(ref path) = self.call_path { |
| 60 | parts.push(format!("call_path={}", path)); |
| 61 | } |
| 62 | |
| 63 | parts.join(", ") |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | /// Main error type for StratoUI operations |
| 68 | #[derive(Debug, Error)] |
| 69 | pub enum StratoError { |
| 70 | #[error("Platform error: {message}")] |
| 71 | Platform { |
| 72 | message: String, |
| 73 | context: Option<ErrorContext>, |
| 74 | }, |
| 75 | |
| 76 | #[error("Renderer error: {message}")] |
| 77 | Renderer { |
| 78 | message: String, |
| 79 | context: Option<ErrorContext>, |
| 80 | }, |
| 81 | |
| 82 | #[error("Widget error: {message}")] |
| 83 | Widget { |
| 84 | message: String, |
| 85 | context: Option<ErrorContext>, |
| 86 | }, |
| 87 | |
| 88 | #[error("State management error: {message}")] |
| 89 | State { |
| 90 | message: String, |
| 91 | context: Option<ErrorContext>, |
| 92 | }, |
| 93 | |
| 94 | #[error("Layout calculation error: {message}")] |
| 95 | Layout { |
| 96 | message: String, |
| 97 | context: Option<ErrorContext>, |
| 98 | }, |
| 99 | |
| 100 | #[error("Initialization error: {message}")] |
| 101 | Initialization { |
| 102 | message: String, |
| 103 | context: Option<ErrorContext>, |
| 104 | }, |
| 105 | |
| 106 | #[error("Configuration error: {message}")] |
| 107 | Configuration { |
| 108 | message: String, |
| 109 | context: Option<ErrorContext>, |
| 110 | }, |
| 111 | |
| 112 | #[error("IO error: {0}")] |
| 113 | Io(#[from] std::io::Error), |
| 114 | |
| 115 | #[error("Not implemented: {message}")] |
| 116 | NotImplemented { |
| 117 | message: String, |
| 118 | context: Option<ErrorContext>, |
| 119 | }, |
| 120 | |
| 121 | #[error("Plugin error: {message}")] |
| 122 | PluginError { |
| 123 | message: String, |
| 124 | context: Option<ErrorContext>, |
| 125 | }, |
| 126 | |
| 127 | #[error("Other error: {message}")] |
| 128 | Other { |
| 129 | message: String, |
| 130 | context: Option<ErrorContext>, |
| 131 | }, |
| 132 | } |
| 133 | |
| 134 | impl StratoError { |
| 135 | /// Create a platform error with context |
| 136 | pub fn platform_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 137 | Self::Platform { |
| 138 | message: msg.into(), |
| 139 | context: Some(context), |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | /// Create a renderer error with context |
| 144 | pub fn renderer_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 145 | Self::Renderer { |
| 146 | message: msg.into(), |
| 147 | context: Some(context), |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | /// Create a widget error with context |
| 152 | pub fn widget_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 153 | Self::Widget { |
| 154 | message: msg.into(), |
| 155 | context: Some(context), |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | /// Create a state error with context |
| 160 | pub fn state_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 161 | Self::State { |
| 162 | message: msg.into(), |
| 163 | context: Some(context), |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | /// Create a layout error with context |
| 168 | pub fn layout_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 169 | Self::Layout { |
| 170 | message: msg.into(), |
| 171 | context: Some(context), |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | /// Create an initialization error with context |
| 176 | pub fn initialization_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 177 | Self::Initialization { |
| 178 | message: msg.into(), |
| 179 | context: Some(context), |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | /// Create a configuration error with context |
| 184 | pub fn configuration_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 185 | Self::Configuration { |
| 186 | message: msg.into(), |
| 187 | context: Some(context), |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | /// Create a plugin error with context |
| 192 | pub fn plugin_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 193 | Self::PluginError { |
| 194 | message: msg.into(), |
| 195 | context: Some(context), |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | /// Create an other error with context |
| 200 | pub fn other_with_context<S: Into<String>>(msg: S, context: ErrorContext) -> Self { |
| 201 | Self::Other { |
| 202 | message: msg.into(), |
| 203 | context: Some(context), |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | /// Create a platform error from a string |
| 208 | pub fn platform<S: Into<String>>(msg: S) -> Self { |
| 209 | Self::Platform { |
| 210 | message: msg.into(), |
| 211 | context: None, |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | /// Create a renderer error from a string |
| 216 | pub fn renderer<S: Into<String>>(msg: S) -> Self { |
| 217 | Self::Renderer { |
| 218 | message: msg.into(), |
| 219 | context: None, |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | /// Create a widget error from a string |
| 224 | pub fn widget<S: Into<String>>(msg: S) -> Self { |
| 225 | Self::Widget { |
| 226 | message: msg.into(), |
| 227 | context: None, |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | /// Create a state error from a string |
| 232 | pub fn state<S: Into<String>>(msg: S) -> Self { |
| 233 | Self::State { |
| 234 | message: msg.into(), |
| 235 | context: None, |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | /// Create a layout error from a string |
| 240 | pub fn layout<S: Into<String>>(msg: S) -> Self { |
| 241 | Self::Layout { |
| 242 | message: msg.into(), |
| 243 | context: None, |
| 244 | } |
| 245 | } |
| 246 | |
| 247 | /// Create an initialization error from a string |
| 248 | pub fn initialization<S: Into<String>>(msg: S) -> Self { |
| 249 | Self::Initialization { |
| 250 | message: msg.into(), |
| 251 | context: None, |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | /// Create a configuration error from a string |
| 256 | pub fn configuration<S: Into<String>>(msg: S) -> Self { |
| 257 | Self::Configuration { |
| 258 | message: msg.into(), |
| 259 | context: None, |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | /// Create a not implemented error from a string |
| 264 | pub fn not_implemented<S: Into<String>>(msg: S) -> Self { |
| 265 | Self::NotImplemented { |
| 266 | message: msg.into(), |
| 267 | context: None, |
| 268 | } |
| 269 | } |
| 270 | |
| 271 | /// Create a plugin error from a string |
| 272 | pub fn plugin<S: Into<String>>(msg: S) -> Self { |
| 273 | Self::PluginError { |
| 274 | message: msg.into(), |
| 275 | context: None, |
| 276 | } |
| 277 | } |
| 278 | |
| 279 | /// Create an other error from a string |
| 280 | pub fn other<S: Into<String>>(msg: S) -> Self { |
| 281 | Self::Other { |
| 282 | message: msg.into(), |
| 283 | context: None, |
| 284 | } |
| 285 | } |
| 286 | |
| 287 | /// Get the error context if available |
| 288 | pub fn context(&self) -> Option<&ErrorContext> { |
| 289 | match self { |
| 290 | Self::Platform { context, .. } |
| 291 | | Self::Renderer { context, .. } |
| 292 | | Self::Widget { context, .. } |
| 293 | | Self::State { context, .. } |
| 294 | | Self::Layout { context, .. } |
| 295 | | Self::Initialization { context, .. } |
| 296 | | Self::Configuration { context, .. } |
| 297 | | Self::NotImplemented { context, .. } |
| 298 | | Self::PluginError { context, .. } |
| 299 | | Self::Other { context, .. } => context.as_ref(), |
| 300 | Self::Io(_) => None, |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | /// Format error with context for logging |
| 305 | pub fn format_for_log(&self) -> String { |
| 306 | let base_msg = self.to_string(); |
| 307 | if let Some(context) = self.context() { |
| 308 | format!("{} [{}]", base_msg, context.format_for_log()) |
| 309 | } else { |
| 310 | base_msg |
| 311 | } |
| 312 | } |
| 313 | } |
| 314 | |
| 315 | /// Result type alias for StratoUI operations |
| 316 | pub type Result<T> = std::result::Result<T, StratoError>; |
| 317 | |
| 318 | /// Alternative result type alias for backward compatibility |
| 319 | pub type StratoResult<T> = Result<T>; |
| 320 | |
| 321 | // ============================================================================= |
| 322 | // Taffy Layout Engine Error Types |
| 323 | // ============================================================================= |
| 324 | |
| 325 | /// Layout errors from Taffy engine. |
| 326 | /// |
| 327 | /// These errors are NON-RECOVERABLE without a fallback layout. |
| 328 | /// The `TaffyLayoutManager` will attempt to use cached layouts when these occur. |
| 329 | /// |
| 330 | /// # Error Recovery |
| 331 | /// |
| 332 | /// - If `last_valid_layout` exists: use cached layout (log warning) |
| 333 | /// - If no cache: propagate error to caller (log error) |
| 334 | #[derive(Debug, Clone, thiserror::Error)] |
| 335 | pub enum TaffyLayoutError { |
| 336 | /// Window/container size is invalid (zero, negative, or infinite). |
| 337 | #[error("Invalid window size: {width}x{height}")] |
| 338 | InvalidWindowSize { width: f32, height: f32 }, |
| 339 | |
| 340 | /// Taffy computation failed internally. |
| 341 | #[error("Layout computation failed: {reason}")] |
| 342 | ComputationFailed { reason: String }, |
| 343 | |
| 344 | /// The layout tree structure is corrupted. |
| 345 | #[error("Layout tree is corrupted")] |
| 346 | CorruptedTree, |
| 347 | |
| 348 | /// Error from Taffy library itself. |
| 349 | #[error("Taffy error: {0}")] |
| 350 | TaffyError(String), |
| 351 | |
| 352 | /// Failed to build node for widget. |
| 353 | #[error("Failed to build layout node for widget")] |
| 354 | NodeBuildFailed, |
| 355 | } |
| 356 | |
| 357 | /// Rendering errors from Taffy-based layout. |
| 358 | /// |
| 359 | /// These errors are RECOVERABLE - skip the widget and continue rendering. |
| 360 | /// |
| 361 | /// # Recovery Strategy |
| 362 | /// |
| 363 | /// Log warning, skip rendering the problematic widget, continue with siblings. |
| 364 | #[derive(Debug, thiserror::Error)] |
| 365 | pub enum TaffyRenderError { |
| 366 | /// Viewport coordinates are invalid after validation. |
| 367 | #[error("Invalid viewport: {0:?}")] |
| 368 | InvalidViewport(crate::validated_rect::ValidatedRect), |
| 369 | |
| 370 | /// GPU/rendering backend error. |
| 371 | #[error("GPU error: {0}")] |
| 372 | GpuError(String), |
| 373 | |
| 374 | /// Required resource (texture, font, etc.) not found. |
| 375 | #[error("Missing resource: {resource_id}")] |
| 376 | MissingResource { resource_id: String }, |
| 377 | } |
| 378 | |
| 379 | /// Validation errors caught before layout computation. |
| 380 | /// |
| 381 | /// These are PREVENTIVE errors - they indicate invalid widget configuration |
| 382 | /// and should be caught during development/testing. |
| 383 | /// |
| 384 | /// # Common Causes |
| 385 | /// |
| 386 | /// - Negative gaps or padding |
| 387 | /// - Invalid button dimensions |
| 388 | /// - NaN/Infinite values in style properties |
| 389 | #[derive(Debug, Clone, thiserror::Error)] |
| 390 | pub enum TaffyValidationError { |
| 391 | /// Gap value is negative. |
| 392 | #[error("Negative gap value: {0}")] |
| 393 | NegativeGap(f32), |
| 394 | |
| 395 | /// Padding contains invalid values. |
| 396 | #[error("Invalid padding configuration")] |
| 397 | InvalidPadding, |
| 398 | |
| 399 | /// A value is not finite (NaN or Infinity). |
| 400 | #[error("Non-finite value in layout configuration")] |
| 401 | NonFiniteValue, |
| 402 | |
| 403 | /// Width or height is negative. |
| 404 | #[error("Negative dimension: width={width}, height={height}")] |
| 405 | NegativeDimension { width: f32, height: f32 }, |
| 406 | |
| 407 | /// Button size is invalid (zero or negative). |
| 408 | #[error("Invalid button size: {width}x{height}")] |
| 409 | InvalidButtonSize { width: f32, height: f32 }, |
| 410 | |
| 411 | /// Border radius is invalid. |
| 412 | #[error("Invalid border radius: {0}")] |
| 413 | InvalidBorderRadius(f32), |
| 414 | } |
| 415 | |
| 416 | impl From<taffy::TaffyError> for TaffyLayoutError { |
| 417 | fn from(err: taffy::TaffyError) -> Self { |
| 418 | TaffyLayoutError::TaffyError(format!("{:?}", err)) |
| 419 | } |
| 420 | } |
| 421 | |
| 422 | /// Result type for Taffy layout operations. |
| 423 | pub type TaffyLayoutResult<T> = std::result::Result<T, TaffyLayoutError>; |
| 424 | |
| 425 | /// Result type for Taffy render operations. |
| 426 | pub type TaffyRenderResult<T> = std::result::Result<T, TaffyRenderError>; |
| 427 | |
| 428 | /// Result type for Taffy validation operations. |
| 429 | pub type TaffyValidationResult<T> = std::result::Result<T, TaffyValidationError>; |
| 430 |