Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-core/src/error.rs
StratoSDK / crates / strato-core / src / error.rs
1//! Error types for StratoUI framework
2 
3use std::collections::HashMap;
4use thiserror::Error;
5 
6/// Context information for errors to aid in debugging
7#[derive(Debug, Clone)]
8pub 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 
19impl 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)]
69pub 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 
134impl 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
316pub type Result<T> = std::result::Result<T, StratoError>;
317 
318/// Alternative result type alias for backward compatibility
319pub 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)]
335pub 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)]
365pub 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)]
390pub 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 
416impl 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.
423pub type TaffyLayoutResult<T> = std::result::Result<T, TaffyLayoutError>;
424 
425/// Result type for Taffy render operations.
426pub type TaffyRenderResult<T> = std::result::Result<T, TaffyRenderError>;
427 
428/// Result type for Taffy validation operations.
429pub type TaffyValidationResult<T> = std::result::Result<T, TaffyValidationError>;
430