Skip to main content

goud_engine/core/
error.rs

1//! Error handling infrastructure for GoudEngine.
2//!
3//! This module provides FFI-compatible error codes and error types that work
4//! consistently across Rust and all language bindings (C#, Python, etc.).
5//!
6//! # Error Code Ranges
7//!
8//! Error codes are organized into ranges by category:
9//!
10//! | Range     | Category   | Description                          |
11//! |-----------|------------|--------------------------------------|
12//! | 0         | Success    | Operation completed successfully     |
13//! | 1-99      | Context    | Initialization and context errors    |
14//! | 100-199   | Resource   | Asset and resource management errors |
15//! | 200-299   | Graphics   | Rendering and GPU errors             |
16//! | 300-399   | Entity     | ECS entity and component errors      |
17//! | 400-499   | Input      | Input handling errors                |
18//! | 500-599   | System     | Platform and system errors           |
19//! | 900-999   | Internal   | Unexpected internal errors           |
20//!
21//! # FFI Compatibility
22//!
23//! Error codes use `i32` for maximum C ABI compatibility. Negative values
24//! are reserved for future use (e.g., platform-specific errors).
25
26/// FFI-compatible error code type.
27///
28/// Uses `i32` for C ABI compatibility across all platforms.
29/// Negative values are reserved for future platform-specific errors.
30pub type GoudErrorCode = i32;
31
32// =============================================================================
33// Error Code Constants
34// =============================================================================
35
36/// Operation completed successfully.
37pub const SUCCESS: GoudErrorCode = 0;
38
39// -----------------------------------------------------------------------------
40// Context Errors (1-99): Engine initialization and context management
41// -----------------------------------------------------------------------------
42
43/// Base code for context/initialization errors.
44pub const CONTEXT_ERROR_BASE: GoudErrorCode = 1;
45
46/// Engine has not been initialized.
47pub const ERR_NOT_INITIALIZED: GoudErrorCode = 1;
48
49/// Engine has already been initialized.
50pub const ERR_ALREADY_INITIALIZED: GoudErrorCode = 2;
51
52/// Invalid engine context.
53pub const ERR_INVALID_CONTEXT: GoudErrorCode = 3;
54
55/// Engine context has been destroyed.
56pub const ERR_CONTEXT_DESTROYED: GoudErrorCode = 4;
57
58/// Engine initialization failed (generic).
59/// Specific error message available via `last_error_message()`.
60pub const ERR_INITIALIZATION_FAILED: GoudErrorCode = 10;
61
62// -----------------------------------------------------------------------------
63// Resource Errors (100-199): Asset loading and resource management
64// -----------------------------------------------------------------------------
65
66/// Base code for resource/asset errors.
67pub const RESOURCE_ERROR_BASE: GoudErrorCode = 100;
68
69/// Requested resource was not found.
70pub const ERR_RESOURCE_NOT_FOUND: GoudErrorCode = 100;
71
72/// Failed to load resource from source.
73pub const ERR_RESOURCE_LOAD_FAILED: GoudErrorCode = 101;
74
75/// Resource format is invalid or unsupported.
76pub const ERR_RESOURCE_INVALID_FORMAT: GoudErrorCode = 102;
77
78/// Resource with this identifier already exists.
79pub const ERR_RESOURCE_ALREADY_EXISTS: GoudErrorCode = 103;
80
81/// Handle is invalid (null or malformed).
82pub const ERR_INVALID_HANDLE: GoudErrorCode = 110;
83
84/// Handle refers to a resource that has been deallocated.
85pub const ERR_HANDLE_EXPIRED: GoudErrorCode = 111;
86
87/// Handle type does not match expected resource type.
88pub const ERR_HANDLE_TYPE_MISMATCH: GoudErrorCode = 112;
89
90// -----------------------------------------------------------------------------
91// Graphics Errors (200-299): Rendering and GPU operations
92// -----------------------------------------------------------------------------
93
94/// Base code for graphics/rendering errors.
95pub const GRAPHICS_ERROR_BASE: GoudErrorCode = 200;
96
97/// Shader compilation failed.
98pub const ERR_SHADER_COMPILATION_FAILED: GoudErrorCode = 200;
99
100/// Shader program linking failed.
101pub const ERR_SHADER_LINK_FAILED: GoudErrorCode = 201;
102
103/// Texture creation failed.
104pub const ERR_TEXTURE_CREATION_FAILED: GoudErrorCode = 210;
105
106/// Buffer creation failed.
107pub const ERR_BUFFER_CREATION_FAILED: GoudErrorCode = 211;
108
109/// Render target creation failed.
110pub const ERR_RENDER_TARGET_FAILED: GoudErrorCode = 220;
111
112/// Graphics backend not supported on this platform.
113pub const ERR_BACKEND_NOT_SUPPORTED: GoudErrorCode = 230;
114
115/// Draw call failed.
116pub const ERR_DRAW_CALL_FAILED: GoudErrorCode = 240;
117
118// -----------------------------------------------------------------------------
119// Entity Errors (300-399): ECS entity and component operations
120// -----------------------------------------------------------------------------
121
122/// Base code for ECS entity errors.
123pub const ENTITY_ERROR_BASE: GoudErrorCode = 300;
124
125/// Entity was not found.
126pub const ERR_ENTITY_NOT_FOUND: GoudErrorCode = 300;
127
128/// Entity already exists.
129pub const ERR_ENTITY_ALREADY_EXISTS: GoudErrorCode = 301;
130
131/// Component was not found on entity.
132pub const ERR_COMPONENT_NOT_FOUND: GoudErrorCode = 310;
133
134/// Component already exists on entity.
135pub const ERR_COMPONENT_ALREADY_EXISTS: GoudErrorCode = 311;
136
137/// Query execution failed.
138pub const ERR_QUERY_FAILED: GoudErrorCode = 320;
139
140// -----------------------------------------------------------------------------
141// Input Errors (400-499): Input handling
142// -----------------------------------------------------------------------------
143
144/// Base code for input handling errors.
145pub const INPUT_ERROR_BASE: GoudErrorCode = 400;
146
147/// Input device not found or disconnected.
148pub const ERR_INPUT_DEVICE_NOT_FOUND: GoudErrorCode = 400;
149
150/// Invalid input action name.
151pub const ERR_INVALID_INPUT_ACTION: GoudErrorCode = 401;
152
153// -----------------------------------------------------------------------------
154// System Errors (500-599): Platform and system operations
155// -----------------------------------------------------------------------------
156
157/// Base code for system/platform errors.
158pub const SYSTEM_ERROR_BASE: GoudErrorCode = 500;
159
160/// Window creation failed.
161pub const ERR_WINDOW_CREATION_FAILED: GoudErrorCode = 500;
162
163/// Audio system initialization failed.
164pub const ERR_AUDIO_INIT_FAILED: GoudErrorCode = 510;
165
166/// Physics system initialization failed.
167pub const ERR_PHYSICS_INIT_FAILED: GoudErrorCode = 520;
168
169/// Generic platform error.
170pub const ERR_PLATFORM_ERROR: GoudErrorCode = 530;
171
172// -----------------------------------------------------------------------------
173// Internal Errors (900-999): Unexpected internal errors
174// -----------------------------------------------------------------------------
175
176/// Base code for internal/unexpected errors.
177pub const INTERNAL_ERROR_BASE: GoudErrorCode = 900;
178
179/// Internal engine error (unexpected state).
180pub const ERR_INTERNAL_ERROR: GoudErrorCode = 900;
181
182/// Feature not yet implemented.
183pub const ERR_NOT_IMPLEMENTED: GoudErrorCode = 901;
184
185/// Invalid engine state.
186pub const ERR_INVALID_STATE: GoudErrorCode = 902;
187
188// =============================================================================
189// GoudError Enum
190// =============================================================================
191
192/// The main error type for GoudEngine.
193///
194/// This enum represents all possible errors that can occur within the engine.
195/// Each variant maps to a specific FFI-compatible error code, enabling consistent
196/// error handling across Rust and all language bindings.
197///
198/// # Error Categories
199///
200/// Errors are organized into categories:
201/// - **Context**: Engine initialization and context management (codes 1-99)
202/// - More categories will be added in subsequent steps
203///
204/// # FFI Compatibility
205///
206/// Use [`GoudError::error_code()`] to get the FFI-compatible error code for any error.
207/// This code can be safely passed across the FFI boundary.
208///
209/// # Example
210///
211/// ```
212/// use goud_engine::core::error::{GoudError, ERR_NOT_INITIALIZED};
213///
214/// let error = GoudError::NotInitialized;
215/// assert_eq!(error.error_code(), ERR_NOT_INITIALIZED);
216/// ```
217#[derive(Debug, Clone, PartialEq, Eq)]
218pub enum GoudError {
219    // -------------------------------------------------------------------------
220    // Context Errors (codes 1-99)
221    // -------------------------------------------------------------------------
222    /// Engine has not been initialized.
223    ///
224    /// This error occurs when attempting to use engine functionality before
225    /// calling the initialization function.
226    NotInitialized,
227
228    /// Engine has already been initialized.
229    ///
230    /// This error occurs when attempting to initialize the engine more than once.
231    /// The engine must be shut down before re-initialization.
232    AlreadyInitialized,
233
234    /// Invalid engine context.
235    ///
236    /// The provided engine context handle is invalid or corrupted.
237    InvalidContext,
238
239    /// Engine context has been destroyed.
240    ///
241    /// The engine context was previously valid but has since been destroyed.
242    /// Operations cannot be performed on a destroyed context.
243    ContextDestroyed,
244
245    /// Engine initialization failed with a specific reason.
246    ///
247    /// Contains a message describing why initialization failed.
248    /// Common causes include missing dependencies, invalid configuration,
249    /// or platform-specific issues.
250    InitializationFailed(String),
251
252    // -------------------------------------------------------------------------
253    // Resource Errors (codes 100-199)
254    // -------------------------------------------------------------------------
255    /// Requested resource was not found.
256    ///
257    /// This error occurs when attempting to access a resource (texture, shader,
258    /// audio file, etc.) that does not exist at the specified path or identifier.
259    /// The string contains the resource path or identifier that was not found.
260    ResourceNotFound(String),
261
262    /// Failed to load resource from source.
263    ///
264    /// This error occurs when a resource exists but could not be loaded due to
265    /// I/O errors, permission issues, or other loading failures.
266    /// The string contains details about the loading failure.
267    ResourceLoadFailed(String),
268
269    /// Resource format is invalid or unsupported.
270    ///
271    /// This error occurs when a resource file exists and was read successfully,
272    /// but the format is invalid, corrupted, or not supported by the engine.
273    /// The string contains details about the format issue.
274    ResourceInvalidFormat(String),
275
276    /// Resource with this identifier already exists.
277    ///
278    /// This error occurs when attempting to create or register a resource
279    /// with an identifier that is already in use.
280    /// The string contains the conflicting resource identifier.
281    ResourceAlreadyExists(String),
282
283    /// Handle is invalid (null or malformed).
284    ///
285    /// This error occurs when an operation is performed with a handle that
286    /// was never valid (null, zero, or otherwise malformed).
287    InvalidHandle,
288
289    /// Handle refers to a resource that has been deallocated.
290    ///
291    /// This error occurs when using a handle that was previously valid but
292    /// the underlying resource has been freed. This is a use-after-free attempt
293    /// that was safely caught by the generational handle system.
294    HandleExpired,
295
296    /// Handle type does not match expected resource type.
297    ///
298    /// This error occurs when a handle is passed to a function expecting a
299    /// different resource type (e.g., passing a texture handle to a shader function).
300    HandleTypeMismatch,
301
302    // -------------------------------------------------------------------------
303    // Graphics Errors (codes 200-299)
304    // -------------------------------------------------------------------------
305    /// Shader compilation failed.
306    ///
307    /// This error occurs when a vertex, fragment, or compute shader fails to compile.
308    /// The string contains the shader compiler error message, which typically includes
309    /// line numbers and error descriptions from the GPU driver.
310    ShaderCompilationFailed(String),
311
312    /// Shader program linking failed.
313    ///
314    /// This error occurs when compiled shaders fail to link into a program.
315    /// Common causes include mismatched inputs/outputs between shader stages,
316    /// missing uniforms, or exceeding GPU resource limits.
317    /// The string contains the linker error message.
318    ShaderLinkFailed(String),
319
320    /// Texture creation failed.
321    ///
322    /// This error occurs when the GPU fails to allocate or create a texture.
323    /// Common causes include insufficient GPU memory, unsupported texture format,
324    /// or dimensions exceeding GPU limits.
325    /// The string contains details about the failure.
326    TextureCreationFailed(String),
327
328    /// Buffer creation failed.
329    ///
330    /// This error occurs when the GPU fails to allocate a buffer (vertex, index,
331    /// uniform, or storage buffer). Common causes include insufficient GPU memory
332    /// or exceeding buffer size limits.
333    /// The string contains details about the failure.
334    BufferCreationFailed(String),
335
336    /// Render target creation failed.
337    ///
338    /// This error occurs when creating a framebuffer or render target fails.
339    /// Common causes include unsupported attachment formats, mismatched
340    /// attachment dimensions, or exceeding attachment limits.
341    /// The string contains details about the failure.
342    RenderTargetFailed(String),
343
344    /// Graphics backend not supported on this platform.
345    ///
346    /// This error occurs when the requested graphics API (OpenGL, Vulkan, Metal, etc.)
347    /// is not available on the current platform or the installed GPU driver
348    /// does not meet minimum version requirements.
349    /// The string contains the requested backend and available alternatives.
350    BackendNotSupported(String),
351
352    /// Draw call failed.
353    ///
354    /// This error occurs when a draw call fails during rendering.
355    /// This is typically a serious error indicating GPU state corruption,
356    /// invalid buffer bindings, or driver issues.
357    /// The string contains details about the failed draw call.
358    DrawCallFailed(String),
359
360    // -------------------------------------------------------------------------
361    // Entity Errors (codes 300-399)
362    // -------------------------------------------------------------------------
363    /// Entity was not found.
364    ///
365    /// This error occurs when attempting to access an entity that does not exist
366    /// in the world, either because it was never created or has been despawned.
367    EntityNotFound,
368
369    /// Entity already exists.
370    ///
371    /// This error occurs when attempting to create an entity with an ID that
372    /// is already in use. This typically indicates a logic error in entity
373    /// management.
374    EntityAlreadyExists,
375
376    /// Component was not found on entity.
377    ///
378    /// This error occurs when querying or accessing a component type that
379    /// the specified entity does not have attached.
380    ComponentNotFound,
381
382    /// Component already exists on entity.
383    ///
384    /// This error occurs when attempting to add a component type that the
385    /// entity already has. Use replace or update methods instead.
386    ComponentAlreadyExists,
387
388    /// Query execution failed.
389    ///
390    /// This error occurs when an ECS query fails to execute. Common causes
391    /// include conflicting access patterns (mutable + immutable on same component)
392    /// or invalid query parameters.
393    /// The string contains details about the query failure.
394    QueryFailed(String),
395
396    // -------------------------------------------------------------------------
397    // System Errors (codes 500-599)
398    // -------------------------------------------------------------------------
399    /// Window creation failed.
400    ///
401    /// This error occurs when the platform window system fails to create
402    /// a window. Common causes include missing display server, invalid
403    /// window parameters, or resource exhaustion.
404    /// The string contains details about the failure.
405    WindowCreationFailed(String),
406
407    /// Audio system initialization failed.
408    ///
409    /// This error occurs when the audio subsystem fails to initialize.
410    /// Common causes include missing audio devices, driver issues,
411    /// or unsupported audio formats.
412    /// The string contains details about the failure.
413    AudioInitFailed(String),
414
415    /// Physics system initialization failed.
416    ///
417    /// This error occurs when the physics engine fails to initialize.
418    /// Common causes include invalid physics configuration or
419    /// incompatible settings.
420    /// The string contains details about the failure.
421    PhysicsInitFailed(String),
422
423    /// Generic platform error.
424    ///
425    /// This error occurs for platform-specific failures that don't fit
426    /// into other categories. The string contains platform-specific
427    /// error details.
428    PlatformError(String),
429
430    // -------------------------------------------------------------------------
431    // Internal Errors (codes 900-999)
432    // -------------------------------------------------------------------------
433    /// Internal engine error.
434    ///
435    /// This error indicates an unexpected internal state or failure within
436    /// the engine. These errors typically indicate bugs in the engine itself
437    /// and should be reported.
438    /// The string contains details about the internal failure.
439    InternalError(String),
440
441    /// Feature not yet implemented.
442    ///
443    /// This error occurs when attempting to use a feature that has not
444    /// been implemented yet. This is primarily used during development
445    /// to mark incomplete functionality.
446    /// The string contains details about the unimplemented feature.
447    NotImplemented(String),
448
449    /// Invalid engine state.
450    ///
451    /// This error occurs when the engine is in an invalid or unexpected state.
452    /// This typically indicates a bug in state management or an invalid
453    /// sequence of operations.
454    /// The string contains details about the invalid state.
455    InvalidState(String),
456}
457
458impl GoudError {
459    /// Returns the FFI-compatible error code for this error.
460    ///
461    /// This method maps each error variant to its corresponding error code constant,
462    /// which can be safely passed across the FFI boundary to C#, Python, or other
463    /// language bindings.
464    ///
465    /// # Example
466    ///
467    /// ```
468    /// use goud_engine::core::error::{GoudError, ERR_NOT_INITIALIZED, ERR_INITIALIZATION_FAILED};
469    ///
470    /// assert_eq!(GoudError::NotInitialized.error_code(), ERR_NOT_INITIALIZED);
471    /// assert_eq!(
472    ///     GoudError::InitializationFailed("GPU not found".to_string()).error_code(),
473    ///     ERR_INITIALIZATION_FAILED
474    /// );
475    /// ```
476    #[inline]
477    pub const fn error_code(&self) -> GoudErrorCode {
478        match self {
479            // Context errors (1-99)
480            GoudError::NotInitialized => ERR_NOT_INITIALIZED,
481            GoudError::AlreadyInitialized => ERR_ALREADY_INITIALIZED,
482            GoudError::InvalidContext => ERR_INVALID_CONTEXT,
483            GoudError::ContextDestroyed => ERR_CONTEXT_DESTROYED,
484            GoudError::InitializationFailed(_) => ERR_INITIALIZATION_FAILED,
485
486            // Resource errors (100-199)
487            GoudError::ResourceNotFound(_) => ERR_RESOURCE_NOT_FOUND,
488            GoudError::ResourceLoadFailed(_) => ERR_RESOURCE_LOAD_FAILED,
489            GoudError::ResourceInvalidFormat(_) => ERR_RESOURCE_INVALID_FORMAT,
490            GoudError::ResourceAlreadyExists(_) => ERR_RESOURCE_ALREADY_EXISTS,
491            GoudError::InvalidHandle => ERR_INVALID_HANDLE,
492            GoudError::HandleExpired => ERR_HANDLE_EXPIRED,
493            GoudError::HandleTypeMismatch => ERR_HANDLE_TYPE_MISMATCH,
494
495            // Graphics errors (200-299)
496            GoudError::ShaderCompilationFailed(_) => ERR_SHADER_COMPILATION_FAILED,
497            GoudError::ShaderLinkFailed(_) => ERR_SHADER_LINK_FAILED,
498            GoudError::TextureCreationFailed(_) => ERR_TEXTURE_CREATION_FAILED,
499            GoudError::BufferCreationFailed(_) => ERR_BUFFER_CREATION_FAILED,
500            GoudError::RenderTargetFailed(_) => ERR_RENDER_TARGET_FAILED,
501            GoudError::BackendNotSupported(_) => ERR_BACKEND_NOT_SUPPORTED,
502            GoudError::DrawCallFailed(_) => ERR_DRAW_CALL_FAILED,
503
504            // Entity errors (300-399)
505            GoudError::EntityNotFound => ERR_ENTITY_NOT_FOUND,
506            GoudError::EntityAlreadyExists => ERR_ENTITY_ALREADY_EXISTS,
507            GoudError::ComponentNotFound => ERR_COMPONENT_NOT_FOUND,
508            GoudError::ComponentAlreadyExists => ERR_COMPONENT_ALREADY_EXISTS,
509            GoudError::QueryFailed(_) => ERR_QUERY_FAILED,
510
511            // System errors (500-599)
512            GoudError::WindowCreationFailed(_) => ERR_WINDOW_CREATION_FAILED,
513            GoudError::AudioInitFailed(_) => ERR_AUDIO_INIT_FAILED,
514            GoudError::PhysicsInitFailed(_) => ERR_PHYSICS_INIT_FAILED,
515            GoudError::PlatformError(_) => ERR_PLATFORM_ERROR,
516
517            // Internal errors (900-999)
518            GoudError::InternalError(_) => ERR_INTERNAL_ERROR,
519            GoudError::NotImplemented(_) => ERR_NOT_IMPLEMENTED,
520            GoudError::InvalidState(_) => ERR_INVALID_STATE,
521        }
522    }
523
524    /// Returns the error category as a static string.
525    ///
526    /// This is a convenience method that returns the category name for this error.
527    ///
528    /// # Example
529    ///
530    /// ```
531    /// use goud_engine::core::error::GoudError;
532    ///
533    /// assert_eq!(GoudError::NotInitialized.category(), "Context");
534    /// ```
535    #[inline]
536    pub const fn category(&self) -> &'static str {
537        error_category(self.error_code())
538    }
539
540    /// Returns the error message for errors that contain one, or a default description.
541    ///
542    /// For errors with associated string messages, this returns the message.
543    /// For errors without messages, this returns a default description.
544    ///
545    /// # Example
546    ///
547    /// ```
548    /// use goud_engine::core::error::GoudError;
549    ///
550    /// let error = GoudError::InitializationFailed("GPU not found".to_string());
551    /// assert_eq!(error.message(), "GPU not found");
552    ///
553    /// let error = GoudError::NotInitialized;
554    /// assert_eq!(error.message(), "Engine has not been initialized");
555    /// ```
556    pub fn message(&self) -> &str {
557        match self {
558            // Context errors
559            GoudError::NotInitialized => "Engine has not been initialized",
560            GoudError::AlreadyInitialized => "Engine has already been initialized",
561            GoudError::InvalidContext => "Invalid engine context",
562            GoudError::ContextDestroyed => "Engine context has been destroyed",
563            GoudError::InitializationFailed(msg) => msg,
564
565            // Resource errors
566            GoudError::ResourceNotFound(msg) => msg,
567            GoudError::ResourceLoadFailed(msg) => msg,
568            GoudError::ResourceInvalidFormat(msg) => msg,
569            GoudError::ResourceAlreadyExists(msg) => msg,
570            GoudError::InvalidHandle => "Invalid handle",
571            GoudError::HandleExpired => "Handle has expired",
572            GoudError::HandleTypeMismatch => "Handle type mismatch",
573
574            // Graphics errors
575            GoudError::ShaderCompilationFailed(msg) => msg,
576            GoudError::ShaderLinkFailed(msg) => msg,
577            GoudError::TextureCreationFailed(msg) => msg,
578            GoudError::BufferCreationFailed(msg) => msg,
579            GoudError::RenderTargetFailed(msg) => msg,
580            GoudError::BackendNotSupported(msg) => msg,
581            GoudError::DrawCallFailed(msg) => msg,
582
583            // Entity errors
584            GoudError::EntityNotFound => "Entity not found",
585            GoudError::EntityAlreadyExists => "Entity already exists",
586            GoudError::ComponentNotFound => "Component not found",
587            GoudError::ComponentAlreadyExists => "Component already exists",
588            GoudError::QueryFailed(msg) => msg,
589
590            // System errors
591            GoudError::WindowCreationFailed(msg) => msg,
592            GoudError::AudioInitFailed(msg) => msg,
593            GoudError::PhysicsInitFailed(msg) => msg,
594            GoudError::PlatformError(msg) => msg,
595
596            // Internal errors
597            GoudError::InternalError(msg) => msg,
598            GoudError::NotImplemented(msg) => msg,
599            GoudError::InvalidState(msg) => msg,
600        }
601    }
602}
603
604// =============================================================================
605// Standard Trait Implementations
606// =============================================================================
607
608impl std::fmt::Display for GoudError {
609    /// Formats the error for user-friendly display.
610    ///
611    /// Format: `"[GOUD-{code}] {category}: {message}"`
612    ///
613    /// # Example
614    ///
615    /// ```
616    /// use goud_engine::core::error::GoudError;
617    ///
618    /// let error = GoudError::NotInitialized;
619    /// let display = format!("{}", error);
620    /// assert!(display.contains("[GOUD-1]"));
621    /// assert!(display.contains("Context"));
622    /// ```
623    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
624        write!(
625            f,
626            "[GOUD-{}] {}: {}",
627            self.error_code(),
628            self.category(),
629            self.message()
630        )
631    }
632}
633
634impl std::error::Error for GoudError {
635    /// Returns the source of this error, if any.
636    ///
637    /// Currently, `GoudError` does not wrap other errors, so this always returns `None`.
638    /// Future versions may add error chaining for wrapped errors.
639    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
640        None
641    }
642}
643
644// =============================================================================
645// Conversion Implementations
646// =============================================================================
647
648impl From<std::io::Error> for GoudError {
649    /// Converts an I/O error into a `GoudError`.
650    ///
651    /// The I/O error is mapped to an appropriate `GoudError` variant based on its kind:
652    /// - `NotFound` -> `ResourceNotFound`
653    /// - `PermissionDenied` -> `ResourceLoadFailed`
654    /// - Other -> `ResourceLoadFailed` with the error message
655    ///
656    /// # Example
657    ///
658    /// ```
659    /// use goud_engine::core::error::GoudError;
660    /// use std::io;
661    ///
662    /// let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
663    /// let goud_error: GoudError = io_error.into();
664    /// assert!(matches!(goud_error, GoudError::ResourceNotFound(_)));
665    /// ```
666    fn from(error: std::io::Error) -> Self {
667        match error.kind() {
668            std::io::ErrorKind::NotFound => GoudError::ResourceNotFound(error.to_string()),
669            std::io::ErrorKind::PermissionDenied => {
670                GoudError::ResourceLoadFailed(format!("Permission denied: {}", error))
671            }
672            _ => GoudError::ResourceLoadFailed(error.to_string()),
673        }
674    }
675}
676
677impl From<String> for GoudError {
678    /// Converts a string into a `GoudError::InternalError`.
679    ///
680    /// This is a convenience conversion for creating internal errors from strings.
681    /// Use more specific error variants when the error category is known.
682    ///
683    /// # Example
684    ///
685    /// ```
686    /// use goud_engine::core::error::GoudError;
687    ///
688    /// let error: GoudError = "something went wrong".to_string().into();
689    /// assert!(matches!(error, GoudError::InternalError(_)));
690    /// ```
691    fn from(msg: String) -> Self {
692        GoudError::InternalError(msg)
693    }
694}
695
696impl From<&str> for GoudError {
697    /// Converts a string slice into a `GoudError::InternalError`.
698    ///
699    /// This is a convenience conversion for creating internal errors from string literals.
700    ///
701    /// # Example
702    ///
703    /// ```
704    /// use goud_engine::core::error::GoudError;
705    ///
706    /// let error: GoudError = "something went wrong".into();
707    /// assert!(matches!(error, GoudError::InternalError(_)));
708    /// ```
709    fn from(msg: &str) -> Self {
710        GoudError::InternalError(msg.to_string())
711    }
712}
713
714// =============================================================================
715// Result Type Alias
716// =============================================================================
717
718/// A specialized `Result` type for GoudEngine operations.
719///
720/// This type alias provides a convenient way to work with results that may
721/// contain a `GoudError`. It's the standard return type for fallible operations
722/// throughout the engine.
723///
724/// # Example
725///
726/// ```
727/// use goud_engine::core::error::{GoudResult, GoudError};
728///
729/// fn load_texture(path: &str) -> GoudResult<u32> {
730///     if path.is_empty() {
731///         return Err(GoudError::ResourceNotFound("Empty path".to_string()));
732///     }
733///     Ok(42) // texture id
734/// }
735///
736/// match load_texture("player.png") {
737///     Ok(id) => println!("Loaded texture with id: {}", id),
738///     Err(e) => println!("Failed to load: {}", e),
739/// }
740/// ```
741pub type GoudResult<T> = Result<T, GoudError>;
742
743// =============================================================================
744// Helper Functions
745// =============================================================================
746
747/// Returns the category name for an error code.
748///
749/// # Examples
750///
751/// ```
752/// use goud_engine::core::error::{error_category, SUCCESS, ERR_RESOURCE_NOT_FOUND};
753///
754/// assert_eq!(error_category(SUCCESS), "Success");
755/// assert_eq!(error_category(ERR_RESOURCE_NOT_FOUND), "Resource");
756/// ```
757#[inline]
758pub const fn error_category(code: GoudErrorCode) -> &'static str {
759    match code {
760        SUCCESS => "Success",
761        1..=99 => "Context",
762        100..=199 => "Resource",
763        200..=299 => "Graphics",
764        300..=399 => "Entity",
765        400..=499 => "Input",
766        500..=599 => "System",
767        900..=999 => "Internal",
768        _ => "Unknown",
769    }
770}
771
772/// Returns true if the error code indicates success.
773#[inline]
774pub const fn is_success(code: GoudErrorCode) -> bool {
775    code == SUCCESS
776}
777
778/// Returns true if the error code indicates an error.
779#[inline]
780pub const fn is_error(code: GoudErrorCode) -> bool {
781    code != SUCCESS
782}
783
784// =============================================================================
785// FFI Error Bridge
786// =============================================================================
787//
788// This section provides the infrastructure for passing errors across the FFI
789// boundary. It uses thread-local storage to store the last error, which can
790// then be retrieved by language bindings (C#, Python, etc.) after a function
791// call returns an error code.
792//
793// ## Usage Pattern
794//
795// 1. Rust function encounters an error
796// 2. Rust function calls `set_last_error(error)`
797// 3. Rust function returns error code via `GoudFFIResult`
798// 4. Language binding checks if `success` is false
799// 5. Language binding calls `goud_last_error_code()` and `goud_last_error_message()`
800// 6. Language binding calls `take_last_error()` to clear the error
801//
802// ## Thread Safety
803//
804// Each thread has its own error storage. Errors do not cross thread boundaries.
805// This matches the behavior of `errno` in C and is safe for multi-threaded use.
806
807use std::cell::RefCell;
808
809thread_local! {
810    /// Thread-local storage for the last error.
811    ///
812    /// Each thread has its own error storage, ensuring that errors from one
813    /// thread do not affect another. This is critical for thread-safe FFI.
814    static LAST_ERROR: RefCell<Option<GoudError>> = const { RefCell::new(None) };
815}
816
817/// Sets the last error for the current thread.
818///
819/// This function stores the error in thread-local storage where it can be
820/// retrieved by `last_error_code()` and `last_error_message()`.
821///
822/// # Thread Safety
823///
824/// The error is stored in thread-local storage and will not affect other threads.
825///
826/// # Example
827///
828/// ```
829/// use goud_engine::core::error::{GoudError, set_last_error, last_error_code, ERR_NOT_INITIALIZED};
830///
831/// set_last_error(GoudError::NotInitialized);
832/// assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
833/// ```
834pub fn set_last_error(error: GoudError) {
835    LAST_ERROR.with(|e| {
836        *e.borrow_mut() = Some(error);
837    });
838}
839
840/// Takes the last error from the current thread, clearing it.
841///
842/// This function removes the error from thread-local storage and returns it.
843/// Subsequent calls will return `None` until a new error is set.
844///
845/// # Thread Safety
846///
847/// Only affects the current thread's error storage.
848///
849/// # Example
850///
851/// ```
852/// use goud_engine::core::error::{GoudError, set_last_error, take_last_error};
853///
854/// set_last_error(GoudError::NotInitialized);
855/// let error = take_last_error();
856/// assert!(error.is_some());
857/// assert!(take_last_error().is_none()); // Cleared after take
858/// ```
859pub fn take_last_error() -> Option<GoudError> {
860    LAST_ERROR.with(|e| e.borrow_mut().take())
861}
862
863/// Gets the last error from the current thread without clearing it.
864///
865/// This function clones the error from thread-local storage. Use `take_last_error()`
866/// if you want to clear the error after retrieval.
867///
868/// # Thread Safety
869///
870/// Only accesses the current thread's error storage.
871///
872/// # Example
873///
874/// ```
875/// use goud_engine::core::error::{GoudError, set_last_error, get_last_error};
876///
877/// set_last_error(GoudError::NotInitialized);
878/// let error1 = get_last_error();
879/// let error2 = get_last_error();
880/// assert_eq!(error1, error2); // Error not cleared
881/// ```
882pub fn get_last_error() -> Option<GoudError> {
883    LAST_ERROR.with(|e| e.borrow().clone())
884}
885
886/// Returns the error code of the last error for the current thread.
887///
888/// Returns `SUCCESS` (0) if no error is set. This does not clear the error.
889///
890/// # Thread Safety
891///
892/// Only accesses the current thread's error storage.
893///
894/// # Example
895///
896/// ```
897/// use goud_engine::core::error::{set_last_error, last_error_code, clear_last_error, GoudError, SUCCESS, ERR_NOT_INITIALIZED};
898///
899/// clear_last_error();
900/// assert_eq!(last_error_code(), SUCCESS);
901///
902/// set_last_error(GoudError::NotInitialized);
903/// assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
904/// ```
905pub fn last_error_code() -> GoudErrorCode {
906    LAST_ERROR.with(|e| {
907        e.borrow()
908            .as_ref()
909            .map(|err| err.error_code())
910            .unwrap_or(SUCCESS)
911    })
912}
913
914/// Returns the error message of the last error for the current thread.
915///
916/// Returns `None` if no error is set. This does not clear the error.
917/// The returned string is a copy, safe to use across FFI.
918///
919/// # Thread Safety
920///
921/// Only accesses the current thread's error storage.
922///
923/// # Example
924///
925/// ```
926/// use goud_engine::core::error::{GoudError, set_last_error, last_error_message};
927///
928/// set_last_error(GoudError::InitializationFailed("GPU not found".to_string()));
929/// let msg = last_error_message();
930/// assert_eq!(msg, Some("GPU not found".to_string()));
931/// ```
932pub fn last_error_message() -> Option<String> {
933    LAST_ERROR.with(|e| e.borrow().as_ref().map(|err| err.message().to_string()))
934}
935
936/// Clears the last error for the current thread.
937///
938/// After calling this, `last_error_code()` will return `SUCCESS` and
939/// `last_error_message()` will return `None`.
940///
941/// # Thread Safety
942///
943/// Only affects the current thread's error storage.
944///
945/// # Example
946///
947/// ```
948/// use goud_engine::core::error::{GoudError, set_last_error, clear_last_error, last_error_code, SUCCESS};
949///
950/// set_last_error(GoudError::NotInitialized);
951/// clear_last_error();
952/// assert_eq!(last_error_code(), SUCCESS);
953/// ```
954pub fn clear_last_error() {
955    LAST_ERROR.with(|e| {
956        *e.borrow_mut() = None;
957    });
958}
959
960// =============================================================================
961// FFI Result Type
962// =============================================================================
963
964/// FFI-safe result type for returning success/failure status across the FFI boundary.
965///
966/// This struct is designed to be passed by value across FFI and provides both
967/// a boolean success flag and the error code for detailed error handling.
968///
969/// # Memory Layout
970///
971/// Uses `#[repr(C)]` for predictable memory layout across language boundaries.
972/// The struct is 8 bytes (4 bytes for code, 4 bytes for success with padding).
973///
974/// # Usage
975///
976/// ```
977/// use goud_engine::core::error::{GoudFFIResult, GoudError, SUCCESS};
978///
979/// // Success case
980/// let result = GoudFFIResult::success();
981/// assert!(result.success);
982/// assert_eq!(result.code, SUCCESS);
983///
984/// // Error case
985/// let result = GoudFFIResult::from_error(GoudError::NotInitialized);
986/// assert!(!result.success);
987/// ```
988#[repr(C)]
989#[derive(Clone, Copy, Debug, PartialEq, Eq)]
990pub struct GoudFFIResult {
991    /// The error code. `SUCCESS` (0) on success, error code on failure.
992    pub code: GoudErrorCode,
993    /// True if the operation succeeded, false otherwise.
994    pub success: bool,
995}
996
997impl GoudFFIResult {
998    /// Creates a successful result.
999    ///
1000    /// # Example
1001    ///
1002    /// ```
1003    /// use goud_engine::core::error::{GoudFFIResult, SUCCESS};
1004    ///
1005    /// let result = GoudFFIResult::success();
1006    /// assert!(result.success);
1007    /// assert_eq!(result.code, SUCCESS);
1008    /// ```
1009    #[inline]
1010    pub const fn success() -> Self {
1011        Self {
1012            code: SUCCESS,
1013            success: true,
1014        }
1015    }
1016
1017    /// Creates a result from an error code.
1018    ///
1019    /// # Example
1020    ///
1021    /// ```
1022    /// use goud_engine::core::error::{GoudFFIResult, ERR_NOT_INITIALIZED};
1023    ///
1024    /// let result = GoudFFIResult::from_code(ERR_NOT_INITIALIZED);
1025    /// assert!(!result.success);
1026    /// assert_eq!(result.code, ERR_NOT_INITIALIZED);
1027    /// ```
1028    #[inline]
1029    pub const fn from_code(code: GoudErrorCode) -> Self {
1030        Self {
1031            code,
1032            success: code == SUCCESS,
1033        }
1034    }
1035
1036    /// Creates a result from a `GoudError`.
1037    ///
1038    /// This also sets the thread-local last error for message retrieval.
1039    ///
1040    /// # Example
1041    ///
1042    /// ```
1043    /// use goud_engine::core::error::{GoudFFIResult, GoudError, ERR_NOT_INITIALIZED, last_error_message};
1044    ///
1045    /// let result = GoudFFIResult::from_error(GoudError::NotInitialized);
1046    /// assert!(!result.success);
1047    /// assert_eq!(result.code, ERR_NOT_INITIALIZED);
1048    /// ```
1049    #[inline]
1050    pub fn from_error(error: GoudError) -> Self {
1051        let code = error.error_code();
1052        set_last_error(error);
1053        Self {
1054            code,
1055            success: false,
1056        }
1057    }
1058
1059    /// Creates a result from a `GoudResult<T>`.
1060    ///
1061    /// On success, clears any previous error. On error, sets the last error.
1062    ///
1063    /// # Example
1064    ///
1065    /// ```
1066    /// use goud_engine::core::error::{GoudFFIResult, GoudResult, GoudError, SUCCESS, ERR_NOT_INITIALIZED};
1067    ///
1068    /// let ok_result: GoudResult<i32> = Ok(42);
1069    /// let ffi_result = GoudFFIResult::from_result(ok_result);
1070    /// assert!(ffi_result.success);
1071    ///
1072    /// let err_result: GoudResult<i32> = Err(GoudError::NotInitialized);
1073    /// let ffi_result = GoudFFIResult::from_result(err_result);
1074    /// assert!(!ffi_result.success);
1075    /// assert_eq!(ffi_result.code, ERR_NOT_INITIALIZED);
1076    /// ```
1077    #[inline]
1078    pub fn from_result<T>(result: GoudResult<T>) -> Self {
1079        match result {
1080            Ok(_) => {
1081                clear_last_error();
1082                Self::success()
1083            }
1084            Err(error) => Self::from_error(error),
1085        }
1086    }
1087
1088    /// Returns true if the result indicates success.
1089    #[inline]
1090    pub const fn is_success(&self) -> bool {
1091        self.success
1092    }
1093
1094    /// Returns true if the result indicates failure.
1095    #[inline]
1096    pub const fn is_error(&self) -> bool {
1097        !self.success
1098    }
1099}
1100
1101impl Default for GoudFFIResult {
1102    /// Default is success.
1103    fn default() -> Self {
1104        Self::success()
1105    }
1106}
1107
1108impl From<GoudError> for GoudFFIResult {
1109    fn from(error: GoudError) -> Self {
1110        Self::from_error(error)
1111    }
1112}
1113
1114impl<T> From<GoudResult<T>> for GoudFFIResult {
1115    fn from(result: GoudResult<T>) -> Self {
1116        Self::from_result(result)
1117    }
1118}
1119
1120// =============================================================================
1121// Tests
1122// =============================================================================
1123
1124#[cfg(test)]
1125mod tests {
1126    use super::*;
1127
1128    #[test]
1129    fn test_success_code_is_zero() {
1130        assert_eq!(SUCCESS, 0);
1131    }
1132
1133    #[test]
1134    fn test_error_code_ranges_are_non_overlapping() {
1135        // Verify base codes define distinct ranges
1136        assert!(CONTEXT_ERROR_BASE < RESOURCE_ERROR_BASE);
1137        assert!(RESOURCE_ERROR_BASE < GRAPHICS_ERROR_BASE);
1138        assert!(GRAPHICS_ERROR_BASE < ENTITY_ERROR_BASE);
1139        assert!(ENTITY_ERROR_BASE < INPUT_ERROR_BASE);
1140        assert!(INPUT_ERROR_BASE < SYSTEM_ERROR_BASE);
1141        assert!(SYSTEM_ERROR_BASE < INTERNAL_ERROR_BASE);
1142    }
1143
1144    #[test]
1145    fn test_error_category_classification() {
1146        assert_eq!(error_category(SUCCESS), "Success");
1147        assert_eq!(error_category(ERR_NOT_INITIALIZED), "Context");
1148        assert_eq!(error_category(ERR_RESOURCE_NOT_FOUND), "Resource");
1149        assert_eq!(error_category(ERR_SHADER_COMPILATION_FAILED), "Graphics");
1150        assert_eq!(error_category(ERR_ENTITY_NOT_FOUND), "Entity");
1151        assert_eq!(error_category(ERR_INPUT_DEVICE_NOT_FOUND), "Input");
1152        assert_eq!(error_category(ERR_WINDOW_CREATION_FAILED), "System");
1153        assert_eq!(error_category(ERR_INTERNAL_ERROR), "Internal");
1154    }
1155
1156    #[test]
1157    fn test_is_success_and_is_error() {
1158        assert!(is_success(SUCCESS));
1159        assert!(!is_error(SUCCESS));
1160
1161        assert!(!is_success(ERR_NOT_INITIALIZED));
1162        assert!(is_error(ERR_NOT_INITIALIZED));
1163
1164        assert!(!is_success(ERR_RESOURCE_NOT_FOUND));
1165        assert!(is_error(ERR_RESOURCE_NOT_FOUND));
1166    }
1167
1168    #[test]
1169    fn test_error_codes_within_category_bounds() {
1170        // Context errors should be in 1-99 range
1171        assert!(ERR_NOT_INITIALIZED >= 1 && ERR_NOT_INITIALIZED < 100);
1172        assert!(ERR_INITIALIZATION_FAILED >= 1 && ERR_INITIALIZATION_FAILED < 100);
1173
1174        // Resource errors should be in 100-199 range
1175        assert!(ERR_RESOURCE_NOT_FOUND >= 100 && ERR_RESOURCE_NOT_FOUND < 200);
1176        assert!(ERR_HANDLE_TYPE_MISMATCH >= 100 && ERR_HANDLE_TYPE_MISMATCH < 200);
1177
1178        // Graphics errors should be in 200-299 range
1179        assert!(ERR_SHADER_COMPILATION_FAILED >= 200 && ERR_SHADER_COMPILATION_FAILED < 300);
1180        assert!(ERR_DRAW_CALL_FAILED >= 200 && ERR_DRAW_CALL_FAILED < 300);
1181
1182        // Entity errors should be in 300-399 range
1183        assert!(ERR_ENTITY_NOT_FOUND >= 300 && ERR_ENTITY_NOT_FOUND < 400);
1184        assert!(ERR_QUERY_FAILED >= 300 && ERR_QUERY_FAILED < 400);
1185
1186        // Input errors should be in 400-499 range
1187        assert!(ERR_INPUT_DEVICE_NOT_FOUND >= 400 && ERR_INPUT_DEVICE_NOT_FOUND < 500);
1188
1189        // System errors should be in 500-599 range
1190        assert!(ERR_WINDOW_CREATION_FAILED >= 500 && ERR_WINDOW_CREATION_FAILED < 600);
1191        assert!(ERR_PLATFORM_ERROR >= 500 && ERR_PLATFORM_ERROR < 600);
1192
1193        // Internal errors should be in 900-999 range
1194        assert!(ERR_INTERNAL_ERROR >= 900 && ERR_INTERNAL_ERROR < 1000);
1195        assert!(ERR_INVALID_STATE >= 900 && ERR_INVALID_STATE < 1000);
1196    }
1197
1198    #[test]
1199    fn test_unknown_category_for_out_of_range() {
1200        assert_eq!(error_category(-1), "Unknown");
1201        assert_eq!(error_category(1000), "Unknown");
1202        assert_eq!(error_category(600), "Unknown");
1203    }
1204
1205    // =========================================================================
1206    // GoudError Context Variant Tests
1207    // =========================================================================
1208
1209    mod context_errors {
1210        use super::*;
1211
1212        #[test]
1213        fn test_not_initialized_error_code() {
1214            let error = GoudError::NotInitialized;
1215            assert_eq!(error.error_code(), ERR_NOT_INITIALIZED);
1216            assert_eq!(error.error_code(), 1);
1217        }
1218
1219        #[test]
1220        fn test_already_initialized_error_code() {
1221            let error = GoudError::AlreadyInitialized;
1222            assert_eq!(error.error_code(), ERR_ALREADY_INITIALIZED);
1223            assert_eq!(error.error_code(), 2);
1224        }
1225
1226        #[test]
1227        fn test_invalid_context_error_code() {
1228            let error = GoudError::InvalidContext;
1229            assert_eq!(error.error_code(), ERR_INVALID_CONTEXT);
1230            assert_eq!(error.error_code(), 3);
1231        }
1232
1233        #[test]
1234        fn test_context_destroyed_error_code() {
1235            let error = GoudError::ContextDestroyed;
1236            assert_eq!(error.error_code(), ERR_CONTEXT_DESTROYED);
1237            assert_eq!(error.error_code(), 4);
1238        }
1239
1240        #[test]
1241        fn test_initialization_failed_error_code() {
1242            let error = GoudError::InitializationFailed("GPU not found".to_string());
1243            assert_eq!(error.error_code(), ERR_INITIALIZATION_FAILED);
1244            assert_eq!(error.error_code(), 10);
1245
1246            // Different messages should have same error code
1247            let error2 = GoudError::InitializationFailed("Missing dependency".to_string());
1248            assert_eq!(error2.error_code(), ERR_INITIALIZATION_FAILED);
1249        }
1250
1251        #[test]
1252        fn test_all_context_errors_in_context_category() {
1253            let errors = [
1254                GoudError::NotInitialized,
1255                GoudError::AlreadyInitialized,
1256                GoudError::InvalidContext,
1257                GoudError::ContextDestroyed,
1258                GoudError::InitializationFailed("test".to_string()),
1259            ];
1260
1261            for error in errors {
1262                assert_eq!(
1263                    error.category(),
1264                    "Context",
1265                    "Error {:?} should be in Context category",
1266                    error
1267                );
1268            }
1269        }
1270
1271        #[test]
1272        fn test_context_error_codes_in_valid_range() {
1273            let errors = [
1274                GoudError::NotInitialized,
1275                GoudError::AlreadyInitialized,
1276                GoudError::InvalidContext,
1277                GoudError::ContextDestroyed,
1278                GoudError::InitializationFailed("test".to_string()),
1279            ];
1280
1281            for error in errors {
1282                let code = error.error_code();
1283                assert!(
1284                    code >= 1 && code < 100,
1285                    "Context error {:?} has code {} which is outside range 1-99",
1286                    error,
1287                    code
1288                );
1289            }
1290        }
1291
1292        #[test]
1293        fn test_goud_error_derives() {
1294            // Test Debug
1295            let error = GoudError::NotInitialized;
1296            let debug_str = format!("{:?}", error);
1297            assert!(debug_str.contains("NotInitialized"));
1298
1299            // Test Clone
1300            let cloned = error.clone();
1301            assert_eq!(error, cloned);
1302
1303            // Test PartialEq and Eq
1304            assert_eq!(GoudError::NotInitialized, GoudError::NotInitialized);
1305            assert_ne!(GoudError::NotInitialized, GoudError::AlreadyInitialized);
1306
1307            // Test equality with message content
1308            let err1 = GoudError::InitializationFailed("msg1".to_string());
1309            let err2 = GoudError::InitializationFailed("msg1".to_string());
1310            let err3 = GoudError::InitializationFailed("msg2".to_string());
1311            assert_eq!(err1, err2);
1312            assert_ne!(err1, err3);
1313        }
1314
1315        #[test]
1316        fn test_initialization_failed_preserves_message() {
1317            let message = "Failed to initialize OpenGL context: version 4.5 required";
1318            let error = GoudError::InitializationFailed(message.to_string());
1319
1320            // Verify we can pattern match and extract the message
1321            if let GoudError::InitializationFailed(msg) = error {
1322                assert_eq!(msg, message);
1323            } else {
1324                panic!("Expected InitializationFailed variant");
1325            }
1326        }
1327    }
1328
1329    // =========================================================================
1330    // GoudError Resource Variant Tests
1331    // =========================================================================
1332
1333    mod resource_errors {
1334        use super::*;
1335
1336        #[test]
1337        fn test_resource_not_found_error_code() {
1338            let error = GoudError::ResourceNotFound("textures/player.png".to_string());
1339            assert_eq!(error.error_code(), ERR_RESOURCE_NOT_FOUND);
1340            assert_eq!(error.error_code(), 100);
1341        }
1342
1343        #[test]
1344        fn test_resource_load_failed_error_code() {
1345            let error = GoudError::ResourceLoadFailed("I/O error reading file".to_string());
1346            assert_eq!(error.error_code(), ERR_RESOURCE_LOAD_FAILED);
1347            assert_eq!(error.error_code(), 101);
1348        }
1349
1350        #[test]
1351        fn test_resource_invalid_format_error_code() {
1352            let error = GoudError::ResourceInvalidFormat("Invalid PNG header".to_string());
1353            assert_eq!(error.error_code(), ERR_RESOURCE_INVALID_FORMAT);
1354            assert_eq!(error.error_code(), 102);
1355        }
1356
1357        #[test]
1358        fn test_resource_already_exists_error_code() {
1359            let error = GoudError::ResourceAlreadyExists("player_texture".to_string());
1360            assert_eq!(error.error_code(), ERR_RESOURCE_ALREADY_EXISTS);
1361            assert_eq!(error.error_code(), 103);
1362        }
1363
1364        #[test]
1365        fn test_invalid_handle_error_code() {
1366            let error = GoudError::InvalidHandle;
1367            assert_eq!(error.error_code(), ERR_INVALID_HANDLE);
1368            assert_eq!(error.error_code(), 110);
1369        }
1370
1371        #[test]
1372        fn test_handle_expired_error_code() {
1373            let error = GoudError::HandleExpired;
1374            assert_eq!(error.error_code(), ERR_HANDLE_EXPIRED);
1375            assert_eq!(error.error_code(), 111);
1376        }
1377
1378        #[test]
1379        fn test_handle_type_mismatch_error_code() {
1380            let error = GoudError::HandleTypeMismatch;
1381            assert_eq!(error.error_code(), ERR_HANDLE_TYPE_MISMATCH);
1382            assert_eq!(error.error_code(), 112);
1383        }
1384
1385        #[test]
1386        fn test_all_resource_errors_in_resource_category() {
1387            let errors: Vec<GoudError> = vec![
1388                GoudError::ResourceNotFound("test".to_string()),
1389                GoudError::ResourceLoadFailed("test".to_string()),
1390                GoudError::ResourceInvalidFormat("test".to_string()),
1391                GoudError::ResourceAlreadyExists("test".to_string()),
1392                GoudError::InvalidHandle,
1393                GoudError::HandleExpired,
1394                GoudError::HandleTypeMismatch,
1395            ];
1396
1397            for error in errors {
1398                assert_eq!(
1399                    error.category(),
1400                    "Resource",
1401                    "Error {:?} should be in Resource category",
1402                    error
1403                );
1404            }
1405        }
1406
1407        #[test]
1408        fn test_resource_error_codes_in_valid_range() {
1409            let errors: Vec<GoudError> = vec![
1410                GoudError::ResourceNotFound("test".to_string()),
1411                GoudError::ResourceLoadFailed("test".to_string()),
1412                GoudError::ResourceInvalidFormat("test".to_string()),
1413                GoudError::ResourceAlreadyExists("test".to_string()),
1414                GoudError::InvalidHandle,
1415                GoudError::HandleExpired,
1416                GoudError::HandleTypeMismatch,
1417            ];
1418
1419            for error in errors {
1420                let code = error.error_code();
1421                assert!(
1422                    code >= 100 && code < 200,
1423                    "Resource error {:?} has code {} which is outside range 100-199",
1424                    error,
1425                    code
1426                );
1427            }
1428        }
1429
1430        #[test]
1431        fn test_resource_errors_preserve_message() {
1432            // Test ResourceNotFound
1433            let path = "assets/textures/missing.png";
1434            if let GoudError::ResourceNotFound(msg) = GoudError::ResourceNotFound(path.to_string())
1435            {
1436                assert_eq!(msg, path);
1437            } else {
1438                panic!("Expected ResourceNotFound variant");
1439            }
1440
1441            // Test ResourceLoadFailed
1442            let reason = "Permission denied";
1443            if let GoudError::ResourceLoadFailed(msg) =
1444                GoudError::ResourceLoadFailed(reason.to_string())
1445            {
1446                assert_eq!(msg, reason);
1447            } else {
1448                panic!("Expected ResourceLoadFailed variant");
1449            }
1450
1451            // Test ResourceInvalidFormat
1452            let format_err = "Unsupported texture format: PVRTC";
1453            if let GoudError::ResourceInvalidFormat(msg) =
1454                GoudError::ResourceInvalidFormat(format_err.to_string())
1455            {
1456                assert_eq!(msg, format_err);
1457            } else {
1458                panic!("Expected ResourceInvalidFormat variant");
1459            }
1460
1461            // Test ResourceAlreadyExists
1462            let resource_id = "main_shader";
1463            if let GoudError::ResourceAlreadyExists(msg) =
1464                GoudError::ResourceAlreadyExists(resource_id.to_string())
1465            {
1466                assert_eq!(msg, resource_id);
1467            } else {
1468                panic!("Expected ResourceAlreadyExists variant");
1469            }
1470        }
1471
1472        #[test]
1473        fn test_resource_error_equality() {
1474            // Same variant, same message
1475            let err1 = GoudError::ResourceNotFound("file.txt".to_string());
1476            let err2 = GoudError::ResourceNotFound("file.txt".to_string());
1477            assert_eq!(err1, err2);
1478
1479            // Same variant, different message
1480            let err3 = GoudError::ResourceNotFound("other.txt".to_string());
1481            assert_ne!(err1, err3);
1482
1483            // Different variants
1484            let err4 = GoudError::ResourceLoadFailed("file.txt".to_string());
1485            assert_ne!(err1, err4);
1486
1487            // Handle errors (no message)
1488            assert_eq!(GoudError::InvalidHandle, GoudError::InvalidHandle);
1489            assert_ne!(GoudError::InvalidHandle, GoudError::HandleExpired);
1490            assert_ne!(GoudError::HandleExpired, GoudError::HandleTypeMismatch);
1491        }
1492
1493        #[test]
1494        fn test_resource_error_debug_format() {
1495            let error = GoudError::ResourceNotFound("test.png".to_string());
1496            let debug_str = format!("{:?}", error);
1497            assert!(debug_str.contains("ResourceNotFound"));
1498            assert!(debug_str.contains("test.png"));
1499
1500            let handle_error = GoudError::InvalidHandle;
1501            let debug_str = format!("{:?}", handle_error);
1502            assert!(debug_str.contains("InvalidHandle"));
1503        }
1504
1505        #[test]
1506        fn test_handle_error_codes_are_distinct() {
1507            // Verify handle-related error codes are properly separated
1508            assert_ne!(ERR_INVALID_HANDLE, ERR_HANDLE_EXPIRED);
1509            assert_ne!(ERR_HANDLE_EXPIRED, ERR_HANDLE_TYPE_MISMATCH);
1510            assert_ne!(ERR_INVALID_HANDLE, ERR_HANDLE_TYPE_MISMATCH);
1511
1512            // All should be in the 110+ range (gap from 103)
1513            assert!(ERR_INVALID_HANDLE >= 110);
1514            assert!(ERR_HANDLE_EXPIRED >= 110);
1515            assert!(ERR_HANDLE_TYPE_MISMATCH >= 110);
1516        }
1517    }
1518
1519    // =========================================================================
1520    // GoudError Graphics Variant Tests
1521    // =========================================================================
1522
1523    mod graphics_errors {
1524        use super::*;
1525
1526        #[test]
1527        fn test_shader_compilation_failed_error_code() {
1528            let error = GoudError::ShaderCompilationFailed(
1529                "ERROR: 0:15: 'vec3' : undeclared identifier".to_string(),
1530            );
1531            assert_eq!(error.error_code(), ERR_SHADER_COMPILATION_FAILED);
1532            assert_eq!(error.error_code(), 200);
1533        }
1534
1535        #[test]
1536        fn test_shader_link_failed_error_code() {
1537            let error = GoudError::ShaderLinkFailed(
1538                "ERROR: Varying variable 'vTexCoord' not written".to_string(),
1539            );
1540            assert_eq!(error.error_code(), ERR_SHADER_LINK_FAILED);
1541            assert_eq!(error.error_code(), 201);
1542        }
1543
1544        #[test]
1545        fn test_texture_creation_failed_error_code() {
1546            let error =
1547                GoudError::TextureCreationFailed("GL_OUT_OF_MEMORY: 4096x4096 RGBA8".to_string());
1548            assert_eq!(error.error_code(), ERR_TEXTURE_CREATION_FAILED);
1549            assert_eq!(error.error_code(), 210);
1550        }
1551
1552        #[test]
1553        fn test_buffer_creation_failed_error_code() {
1554            let error = GoudError::BufferCreationFailed(
1555                "Failed to allocate 256MB vertex buffer".to_string(),
1556            );
1557            assert_eq!(error.error_code(), ERR_BUFFER_CREATION_FAILED);
1558            assert_eq!(error.error_code(), 211);
1559        }
1560
1561        #[test]
1562        fn test_render_target_failed_error_code() {
1563            let error =
1564                GoudError::RenderTargetFailed("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT".to_string());
1565            assert_eq!(error.error_code(), ERR_RENDER_TARGET_FAILED);
1566            assert_eq!(error.error_code(), 220);
1567        }
1568
1569        #[test]
1570        fn test_backend_not_supported_error_code() {
1571            let error =
1572                GoudError::BackendNotSupported("Vulkan 1.2 required, found 1.0".to_string());
1573            assert_eq!(error.error_code(), ERR_BACKEND_NOT_SUPPORTED);
1574            assert_eq!(error.error_code(), 230);
1575        }
1576
1577        #[test]
1578        fn test_draw_call_failed_error_code() {
1579            let error = GoudError::DrawCallFailed(
1580                "glDrawElements failed: GL_INVALID_OPERATION".to_string(),
1581            );
1582            assert_eq!(error.error_code(), ERR_DRAW_CALL_FAILED);
1583            assert_eq!(error.error_code(), 240);
1584        }
1585
1586        #[test]
1587        fn test_all_graphics_errors_in_graphics_category() {
1588            let errors: Vec<GoudError> = vec![
1589                GoudError::ShaderCompilationFailed("test".to_string()),
1590                GoudError::ShaderLinkFailed("test".to_string()),
1591                GoudError::TextureCreationFailed("test".to_string()),
1592                GoudError::BufferCreationFailed("test".to_string()),
1593                GoudError::RenderTargetFailed("test".to_string()),
1594                GoudError::BackendNotSupported("test".to_string()),
1595                GoudError::DrawCallFailed("test".to_string()),
1596            ];
1597
1598            for error in errors {
1599                assert_eq!(
1600                    error.category(),
1601                    "Graphics",
1602                    "Error {:?} should be in Graphics category",
1603                    error
1604                );
1605            }
1606        }
1607
1608        #[test]
1609        fn test_graphics_error_codes_in_valid_range() {
1610            let errors: Vec<GoudError> = vec![
1611                GoudError::ShaderCompilationFailed("test".to_string()),
1612                GoudError::ShaderLinkFailed("test".to_string()),
1613                GoudError::TextureCreationFailed("test".to_string()),
1614                GoudError::BufferCreationFailed("test".to_string()),
1615                GoudError::RenderTargetFailed("test".to_string()),
1616                GoudError::BackendNotSupported("test".to_string()),
1617                GoudError::DrawCallFailed("test".to_string()),
1618            ];
1619
1620            for error in errors {
1621                let code = error.error_code();
1622                assert!(
1623                    code >= 200 && code < 300,
1624                    "Graphics error {:?} has code {} which is outside range 200-299",
1625                    error,
1626                    code
1627                );
1628            }
1629        }
1630
1631        #[test]
1632        fn test_graphics_errors_preserve_message() {
1633            // Test ShaderCompilationFailed with line number info
1634            let shader_err = "ERROR: 0:42: 'sampler2D' : syntax error";
1635            if let GoudError::ShaderCompilationFailed(msg) =
1636                GoudError::ShaderCompilationFailed(shader_err.to_string())
1637            {
1638                assert_eq!(msg, shader_err);
1639                assert!(msg.contains("42")); // Verify line number preserved
1640            } else {
1641                panic!("Expected ShaderCompilationFailed variant");
1642            }
1643
1644            // Test BackendNotSupported with version info
1645            let backend_err = "Metal not available on Linux; available: OpenGL 4.5, Vulkan 1.2";
1646            if let GoudError::BackendNotSupported(msg) =
1647                GoudError::BackendNotSupported(backend_err.to_string())
1648            {
1649                assert_eq!(msg, backend_err);
1650            } else {
1651                panic!("Expected BackendNotSupported variant");
1652            }
1653        }
1654
1655        #[test]
1656        fn test_graphics_error_equality() {
1657            // Same variant, same message
1658            let err1 = GoudError::ShaderCompilationFailed("error line 10".to_string());
1659            let err2 = GoudError::ShaderCompilationFailed("error line 10".to_string());
1660            assert_eq!(err1, err2);
1661
1662            // Same variant, different message
1663            let err3 = GoudError::ShaderCompilationFailed("error line 20".to_string());
1664            assert_ne!(err1, err3);
1665
1666            // Different variants with similar messages
1667            let err4 = GoudError::ShaderLinkFailed("error line 10".to_string());
1668            assert_ne!(err1, err4);
1669
1670            // Different graphics error types
1671            assert_ne!(
1672                GoudError::TextureCreationFailed("fail".to_string()),
1673                GoudError::BufferCreationFailed("fail".to_string())
1674            );
1675        }
1676
1677        #[test]
1678        fn test_graphics_error_debug_format() {
1679            let error = GoudError::ShaderCompilationFailed("syntax error at line 5".to_string());
1680            let debug_str = format!("{:?}", error);
1681            assert!(debug_str.contains("ShaderCompilationFailed"));
1682            assert!(debug_str.contains("syntax error at line 5"));
1683
1684            let error2 = GoudError::DrawCallFailed("invalid state".to_string());
1685            let debug_str2 = format!("{:?}", error2);
1686            assert!(debug_str2.contains("DrawCallFailed"));
1687            assert!(debug_str2.contains("invalid state"));
1688        }
1689
1690        #[test]
1691        fn test_graphics_error_codes_are_distinct() {
1692            // Verify all graphics error codes are unique
1693            let codes = vec![
1694                ERR_SHADER_COMPILATION_FAILED,
1695                ERR_SHADER_LINK_FAILED,
1696                ERR_TEXTURE_CREATION_FAILED,
1697                ERR_BUFFER_CREATION_FAILED,
1698                ERR_RENDER_TARGET_FAILED,
1699                ERR_BACKEND_NOT_SUPPORTED,
1700                ERR_DRAW_CALL_FAILED,
1701            ];
1702
1703            // Check all codes are distinct
1704            for (i, code1) in codes.iter().enumerate() {
1705                for (j, code2) in codes.iter().enumerate() {
1706                    if i != j {
1707                        assert_ne!(
1708                            code1, code2,
1709                            "Error codes at index {} and {} should be different",
1710                            i, j
1711                        );
1712                    }
1713                }
1714            }
1715        }
1716
1717        #[test]
1718        fn test_graphics_error_code_gaps_for_future_expansion() {
1719            // Verify there are gaps between error codes for future additions
1720            // Shader errors: 200-209
1721            assert!(ERR_SHADER_COMPILATION_FAILED == 200);
1722            assert!(ERR_SHADER_LINK_FAILED == 201);
1723
1724            // Texture/buffer errors: 210-219
1725            assert!(ERR_TEXTURE_CREATION_FAILED == 210);
1726            assert!(ERR_BUFFER_CREATION_FAILED == 211);
1727
1728            // Render target errors: 220-229
1729            assert!(ERR_RENDER_TARGET_FAILED == 220);
1730
1731            // Backend errors: 230-239
1732            assert!(ERR_BACKEND_NOT_SUPPORTED == 230);
1733
1734            // Draw call errors: 240-249
1735            assert!(ERR_DRAW_CALL_FAILED == 240);
1736        }
1737    }
1738
1739    // =========================================================================
1740    // GoudError Entity Variant Tests
1741    // =========================================================================
1742
1743    mod entity_errors {
1744        use super::*;
1745
1746        #[test]
1747        fn test_entity_not_found_error_code() {
1748            let error = GoudError::EntityNotFound;
1749            assert_eq!(error.error_code(), ERR_ENTITY_NOT_FOUND);
1750            assert_eq!(error.error_code(), 300);
1751        }
1752
1753        #[test]
1754        fn test_entity_already_exists_error_code() {
1755            let error = GoudError::EntityAlreadyExists;
1756            assert_eq!(error.error_code(), ERR_ENTITY_ALREADY_EXISTS);
1757            assert_eq!(error.error_code(), 301);
1758        }
1759
1760        #[test]
1761        fn test_component_not_found_error_code() {
1762            let error = GoudError::ComponentNotFound;
1763            assert_eq!(error.error_code(), ERR_COMPONENT_NOT_FOUND);
1764            assert_eq!(error.error_code(), 310);
1765        }
1766
1767        #[test]
1768        fn test_component_already_exists_error_code() {
1769            let error = GoudError::ComponentAlreadyExists;
1770            assert_eq!(error.error_code(), ERR_COMPONENT_ALREADY_EXISTS);
1771            assert_eq!(error.error_code(), 311);
1772        }
1773
1774        #[test]
1775        fn test_query_failed_error_code() {
1776            let error = GoudError::QueryFailed("conflicting access on Position".to_string());
1777            assert_eq!(error.error_code(), ERR_QUERY_FAILED);
1778            assert_eq!(error.error_code(), 320);
1779        }
1780
1781        #[test]
1782        fn test_all_entity_errors_in_entity_category() {
1783            let errors: Vec<GoudError> = vec![
1784                GoudError::EntityNotFound,
1785                GoudError::EntityAlreadyExists,
1786                GoudError::ComponentNotFound,
1787                GoudError::ComponentAlreadyExists,
1788                GoudError::QueryFailed("test".to_string()),
1789            ];
1790
1791            for error in errors {
1792                assert_eq!(
1793                    error.category(),
1794                    "Entity",
1795                    "Error {:?} should be in Entity category",
1796                    error
1797                );
1798            }
1799        }
1800
1801        #[test]
1802        fn test_entity_error_codes_in_valid_range() {
1803            let errors: Vec<GoudError> = vec![
1804                GoudError::EntityNotFound,
1805                GoudError::EntityAlreadyExists,
1806                GoudError::ComponentNotFound,
1807                GoudError::ComponentAlreadyExists,
1808                GoudError::QueryFailed("test".to_string()),
1809            ];
1810
1811            for error in errors {
1812                let code = error.error_code();
1813                assert!(
1814                    code >= 300 && code < 400,
1815                    "Entity error {:?} has code {} which is outside range 300-399",
1816                    error,
1817                    code
1818                );
1819            }
1820        }
1821
1822        #[test]
1823        fn test_query_failed_preserves_message() {
1824            let query_err = "Conflicting access: &mut Position and &Position on same entity";
1825            if let GoudError::QueryFailed(msg) = GoudError::QueryFailed(query_err.to_string()) {
1826                assert_eq!(msg, query_err);
1827            } else {
1828                panic!("Expected QueryFailed variant");
1829            }
1830        }
1831
1832        #[test]
1833        fn test_entity_error_equality() {
1834            // Unit variants are equal
1835            assert_eq!(GoudError::EntityNotFound, GoudError::EntityNotFound);
1836            assert_eq!(GoudError::ComponentNotFound, GoudError::ComponentNotFound);
1837
1838            // Different unit variants are not equal
1839            assert_ne!(GoudError::EntityNotFound, GoudError::EntityAlreadyExists);
1840            assert_ne!(
1841                GoudError::ComponentNotFound,
1842                GoudError::ComponentAlreadyExists
1843            );
1844
1845            // QueryFailed with same message
1846            let err1 = GoudError::QueryFailed("error".to_string());
1847            let err2 = GoudError::QueryFailed("error".to_string());
1848            assert_eq!(err1, err2);
1849
1850            // QueryFailed with different message
1851            let err3 = GoudError::QueryFailed("different".to_string());
1852            assert_ne!(err1, err3);
1853        }
1854
1855        #[test]
1856        fn test_entity_error_debug_format() {
1857            let error = GoudError::EntityNotFound;
1858            let debug_str = format!("{:?}", error);
1859            assert!(debug_str.contains("EntityNotFound"));
1860
1861            let error2 = GoudError::QueryFailed("access conflict".to_string());
1862            let debug_str2 = format!("{:?}", error2);
1863            assert!(debug_str2.contains("QueryFailed"));
1864            assert!(debug_str2.contains("access conflict"));
1865        }
1866
1867        #[test]
1868        fn test_entity_error_codes_are_distinct() {
1869            let codes = vec![
1870                ERR_ENTITY_NOT_FOUND,
1871                ERR_ENTITY_ALREADY_EXISTS,
1872                ERR_COMPONENT_NOT_FOUND,
1873                ERR_COMPONENT_ALREADY_EXISTS,
1874                ERR_QUERY_FAILED,
1875            ];
1876
1877            for (i, code1) in codes.iter().enumerate() {
1878                for (j, code2) in codes.iter().enumerate() {
1879                    if i != j {
1880                        assert_ne!(
1881                            code1, code2,
1882                            "Error codes at index {} and {} should be different",
1883                            i, j
1884                        );
1885                    }
1886                }
1887            }
1888        }
1889
1890        #[test]
1891        fn test_entity_error_code_gaps_for_future_expansion() {
1892            // Entity errors: 300-309
1893            assert!(ERR_ENTITY_NOT_FOUND == 300);
1894            assert!(ERR_ENTITY_ALREADY_EXISTS == 301);
1895
1896            // Component errors: 310-319
1897            assert!(ERR_COMPONENT_NOT_FOUND == 310);
1898            assert!(ERR_COMPONENT_ALREADY_EXISTS == 311);
1899
1900            // Query errors: 320-329
1901            assert!(ERR_QUERY_FAILED == 320);
1902        }
1903    }
1904
1905    // =========================================================================
1906    // GoudError System Variant Tests
1907    // =========================================================================
1908
1909    mod system_errors {
1910        use super::*;
1911
1912        #[test]
1913        fn test_window_creation_failed_error_code() {
1914            let error = GoudError::WindowCreationFailed("No display server found".to_string());
1915            assert_eq!(error.error_code(), ERR_WINDOW_CREATION_FAILED);
1916            assert_eq!(error.error_code(), 500);
1917        }
1918
1919        #[test]
1920        fn test_audio_init_failed_error_code() {
1921            let error = GoudError::AudioInitFailed("No audio devices found".to_string());
1922            assert_eq!(error.error_code(), ERR_AUDIO_INIT_FAILED);
1923            assert_eq!(error.error_code(), 510);
1924        }
1925
1926        #[test]
1927        fn test_physics_init_failed_error_code() {
1928            let error = GoudError::PhysicsInitFailed("Invalid gravity configuration".to_string());
1929            assert_eq!(error.error_code(), ERR_PHYSICS_INIT_FAILED);
1930            assert_eq!(error.error_code(), 520);
1931        }
1932
1933        #[test]
1934        fn test_platform_error_error_code() {
1935            let error =
1936                GoudError::PlatformError("macOS: Failed to acquire Metal device".to_string());
1937            assert_eq!(error.error_code(), ERR_PLATFORM_ERROR);
1938            assert_eq!(error.error_code(), 530);
1939        }
1940
1941        #[test]
1942        fn test_all_system_errors_in_system_category() {
1943            let errors: Vec<GoudError> = vec![
1944                GoudError::WindowCreationFailed("test".to_string()),
1945                GoudError::AudioInitFailed("test".to_string()),
1946                GoudError::PhysicsInitFailed("test".to_string()),
1947                GoudError::PlatformError("test".to_string()),
1948            ];
1949
1950            for error in errors {
1951                assert_eq!(
1952                    error.category(),
1953                    "System",
1954                    "Error {:?} should be in System category",
1955                    error
1956                );
1957            }
1958        }
1959
1960        #[test]
1961        fn test_system_error_codes_in_valid_range() {
1962            let errors: Vec<GoudError> = vec![
1963                GoudError::WindowCreationFailed("test".to_string()),
1964                GoudError::AudioInitFailed("test".to_string()),
1965                GoudError::PhysicsInitFailed("test".to_string()),
1966                GoudError::PlatformError("test".to_string()),
1967            ];
1968
1969            for error in errors {
1970                let code = error.error_code();
1971                assert!(
1972                    code >= 500 && code < 600,
1973                    "System error {:?} has code {} which is outside range 500-599",
1974                    error,
1975                    code
1976                );
1977            }
1978        }
1979
1980        #[test]
1981        fn test_system_errors_preserve_message() {
1982            // Test WindowCreationFailed
1983            let window_err = "Failed to create GLFW window: 800x600";
1984            if let GoudError::WindowCreationFailed(msg) =
1985                GoudError::WindowCreationFailed(window_err.to_string())
1986            {
1987                assert_eq!(msg, window_err);
1988            } else {
1989                panic!("Expected WindowCreationFailed variant");
1990            }
1991
1992            // Test AudioInitFailed
1993            let audio_err = "ALSA: Unable to open default audio device";
1994            if let GoudError::AudioInitFailed(msg) =
1995                GoudError::AudioInitFailed(audio_err.to_string())
1996            {
1997                assert_eq!(msg, audio_err);
1998            } else {
1999                panic!("Expected AudioInitFailed variant");
2000            }
2001
2002            // Test PhysicsInitFailed
2003            let physics_err = "Box2D: Invalid world bounds";
2004            if let GoudError::PhysicsInitFailed(msg) =
2005                GoudError::PhysicsInitFailed(physics_err.to_string())
2006            {
2007                assert_eq!(msg, physics_err);
2008            } else {
2009                panic!("Expected PhysicsInitFailed variant");
2010            }
2011
2012            // Test PlatformError
2013            let platform_err = "Linux: X11 display connection failed";
2014            if let GoudError::PlatformError(msg) =
2015                GoudError::PlatformError(platform_err.to_string())
2016            {
2017                assert_eq!(msg, platform_err);
2018            } else {
2019                panic!("Expected PlatformError variant");
2020            }
2021        }
2022
2023        #[test]
2024        fn test_system_error_equality() {
2025            // Same variant, same message
2026            let err1 = GoudError::WindowCreationFailed("error".to_string());
2027            let err2 = GoudError::WindowCreationFailed("error".to_string());
2028            assert_eq!(err1, err2);
2029
2030            // Same variant, different message
2031            let err3 = GoudError::WindowCreationFailed("different".to_string());
2032            assert_ne!(err1, err3);
2033
2034            // Different variants
2035            let err4 = GoudError::AudioInitFailed("error".to_string());
2036            assert_ne!(err1, err4);
2037        }
2038
2039        #[test]
2040        fn test_system_error_debug_format() {
2041            let error = GoudError::WindowCreationFailed("GLFW error 65543".to_string());
2042            let debug_str = format!("{:?}", error);
2043            assert!(debug_str.contains("WindowCreationFailed"));
2044            assert!(debug_str.contains("GLFW error 65543"));
2045
2046            let error2 = GoudError::PlatformError("Win32 error".to_string());
2047            let debug_str2 = format!("{:?}", error2);
2048            assert!(debug_str2.contains("PlatformError"));
2049            assert!(debug_str2.contains("Win32 error"));
2050        }
2051
2052        #[test]
2053        fn test_system_error_codes_are_distinct() {
2054            let codes = vec![
2055                ERR_WINDOW_CREATION_FAILED,
2056                ERR_AUDIO_INIT_FAILED,
2057                ERR_PHYSICS_INIT_FAILED,
2058                ERR_PLATFORM_ERROR,
2059            ];
2060
2061            for (i, code1) in codes.iter().enumerate() {
2062                for (j, code2) in codes.iter().enumerate() {
2063                    if i != j {
2064                        assert_ne!(
2065                            code1, code2,
2066                            "Error codes at index {} and {} should be different",
2067                            i, j
2068                        );
2069                    }
2070                }
2071            }
2072        }
2073
2074        #[test]
2075        fn test_system_error_code_gaps_for_future_expansion() {
2076            // Window errors: 500-509
2077            assert!(ERR_WINDOW_CREATION_FAILED == 500);
2078
2079            // Audio errors: 510-519
2080            assert!(ERR_AUDIO_INIT_FAILED == 510);
2081
2082            // Physics errors: 520-529
2083            assert!(ERR_PHYSICS_INIT_FAILED == 520);
2084
2085            // Platform errors: 530-539
2086            assert!(ERR_PLATFORM_ERROR == 530);
2087        }
2088    }
2089
2090    // =========================================================================
2091    // GoudError Internal Variant Tests
2092    // =========================================================================
2093
2094    mod internal_errors {
2095        use super::*;
2096
2097        #[test]
2098        fn test_internal_error_error_code() {
2099            let error =
2100                GoudError::InternalError("Unexpected null pointer in render queue".to_string());
2101            assert_eq!(error.error_code(), ERR_INTERNAL_ERROR);
2102            assert_eq!(error.error_code(), 900);
2103        }
2104
2105        #[test]
2106        fn test_not_implemented_error_code() {
2107            let error = GoudError::NotImplemented("Vulkan backend".to_string());
2108            assert_eq!(error.error_code(), ERR_NOT_IMPLEMENTED);
2109            assert_eq!(error.error_code(), 901);
2110        }
2111
2112        #[test]
2113        fn test_invalid_state_error_code() {
2114            let error = GoudError::InvalidState("Renderer called after shutdown".to_string());
2115            assert_eq!(error.error_code(), ERR_INVALID_STATE);
2116            assert_eq!(error.error_code(), 902);
2117        }
2118
2119        #[test]
2120        fn test_all_internal_errors_in_internal_category() {
2121            let errors: Vec<GoudError> = vec![
2122                GoudError::InternalError("test".to_string()),
2123                GoudError::NotImplemented("test".to_string()),
2124                GoudError::InvalidState("test".to_string()),
2125            ];
2126
2127            for error in errors {
2128                assert_eq!(
2129                    error.category(),
2130                    "Internal",
2131                    "Error {:?} should be in Internal category",
2132                    error
2133                );
2134            }
2135        }
2136
2137        #[test]
2138        fn test_internal_error_codes_in_valid_range() {
2139            let errors: Vec<GoudError> = vec![
2140                GoudError::InternalError("test".to_string()),
2141                GoudError::NotImplemented("test".to_string()),
2142                GoudError::InvalidState("test".to_string()),
2143            ];
2144
2145            for error in errors {
2146                let code = error.error_code();
2147                assert!(
2148                    code >= 900 && code < 1000,
2149                    "Internal error {:?} has code {} which is outside range 900-999",
2150                    error,
2151                    code
2152                );
2153            }
2154        }
2155
2156        #[test]
2157        fn test_internal_errors_preserve_message() {
2158            // Test InternalError
2159            let internal_err = "FATAL: Inconsistent component storage state";
2160            if let GoudError::InternalError(msg) =
2161                GoudError::InternalError(internal_err.to_string())
2162            {
2163                assert_eq!(msg, internal_err);
2164            } else {
2165                panic!("Expected InternalError variant");
2166            }
2167
2168            // Test NotImplemented
2169            let not_impl_err = "Feature 'ray tracing' is not yet implemented";
2170            if let GoudError::NotImplemented(msg) =
2171                GoudError::NotImplemented(not_impl_err.to_string())
2172            {
2173                assert_eq!(msg, not_impl_err);
2174            } else {
2175                panic!("Expected NotImplemented variant");
2176            }
2177
2178            // Test InvalidState
2179            let invalid_state_err = "Cannot add components while iterating";
2180            if let GoudError::InvalidState(msg) =
2181                GoudError::InvalidState(invalid_state_err.to_string())
2182            {
2183                assert_eq!(msg, invalid_state_err);
2184            } else {
2185                panic!("Expected InvalidState variant");
2186            }
2187        }
2188
2189        #[test]
2190        fn test_internal_error_equality() {
2191            // Same variant, same message
2192            let err1 = GoudError::InternalError("bug".to_string());
2193            let err2 = GoudError::InternalError("bug".to_string());
2194            assert_eq!(err1, err2);
2195
2196            // Same variant, different message
2197            let err3 = GoudError::InternalError("different bug".to_string());
2198            assert_ne!(err1, err3);
2199
2200            // Different variants
2201            let err4 = GoudError::NotImplemented("bug".to_string());
2202            assert_ne!(err1, err4);
2203
2204            let err5 = GoudError::InvalidState("bug".to_string());
2205            assert_ne!(err1, err5);
2206            assert_ne!(err4, err5);
2207        }
2208
2209        #[test]
2210        fn test_internal_error_debug_format() {
2211            let error = GoudError::InternalError("assertion failed".to_string());
2212            let debug_str = format!("{:?}", error);
2213            assert!(debug_str.contains("InternalError"));
2214            assert!(debug_str.contains("assertion failed"));
2215
2216            let error2 = GoudError::NotImplemented("3D audio".to_string());
2217            let debug_str2 = format!("{:?}", error2);
2218            assert!(debug_str2.contains("NotImplemented"));
2219            assert!(debug_str2.contains("3D audio"));
2220
2221            let error3 = GoudError::InvalidState("already running".to_string());
2222            let debug_str3 = format!("{:?}", error3);
2223            assert!(debug_str3.contains("InvalidState"));
2224            assert!(debug_str3.contains("already running"));
2225        }
2226
2227        #[test]
2228        fn test_internal_error_codes_are_distinct() {
2229            let codes = vec![ERR_INTERNAL_ERROR, ERR_NOT_IMPLEMENTED, ERR_INVALID_STATE];
2230
2231            for (i, code1) in codes.iter().enumerate() {
2232                for (j, code2) in codes.iter().enumerate() {
2233                    if i != j {
2234                        assert_ne!(
2235                            code1, code2,
2236                            "Error codes at index {} and {} should be different",
2237                            i, j
2238                        );
2239                    }
2240                }
2241            }
2242        }
2243
2244        #[test]
2245        fn test_internal_error_code_ordering() {
2246            // Internal errors should be consecutive starting at 900
2247            assert_eq!(ERR_INTERNAL_ERROR, 900);
2248            assert_eq!(ERR_NOT_IMPLEMENTED, 901);
2249            assert_eq!(ERR_INVALID_STATE, 902);
2250        }
2251    }
2252
2253    // =========================================================================
2254    // Trait and Conversion Tests
2255    // =========================================================================
2256
2257    mod traits {
2258        use super::*;
2259        use std::error::Error;
2260
2261        #[test]
2262        fn test_display_format_context_errors() {
2263            let error = GoudError::NotInitialized;
2264            let display = format!("{}", error);
2265            assert_eq!(display, "[GOUD-1] Context: Engine has not been initialized");
2266
2267            let error = GoudError::InitializationFailed("GPU not found".to_string());
2268            let display = format!("{}", error);
2269            assert_eq!(display, "[GOUD-10] Context: GPU not found");
2270        }
2271
2272        #[test]
2273        fn test_display_format_resource_errors() {
2274            let error = GoudError::ResourceNotFound("textures/player.png".to_string());
2275            let display = format!("{}", error);
2276            assert_eq!(display, "[GOUD-100] Resource: textures/player.png");
2277
2278            let error = GoudError::InvalidHandle;
2279            let display = format!("{}", error);
2280            assert_eq!(display, "[GOUD-110] Resource: Invalid handle");
2281        }
2282
2283        #[test]
2284        fn test_display_format_graphics_errors() {
2285            let error = GoudError::ShaderCompilationFailed("syntax error at line 42".to_string());
2286            let display = format!("{}", error);
2287            assert_eq!(display, "[GOUD-200] Graphics: syntax error at line 42");
2288        }
2289
2290        #[test]
2291        fn test_display_format_entity_errors() {
2292            let error = GoudError::EntityNotFound;
2293            let display = format!("{}", error);
2294            assert_eq!(display, "[GOUD-300] Entity: Entity not found");
2295
2296            let error = GoudError::QueryFailed("conflicting access".to_string());
2297            let display = format!("{}", error);
2298            assert_eq!(display, "[GOUD-320] Entity: conflicting access");
2299        }
2300
2301        #[test]
2302        fn test_display_format_system_errors() {
2303            let error = GoudError::WindowCreationFailed("no display".to_string());
2304            let display = format!("{}", error);
2305            assert_eq!(display, "[GOUD-500] System: no display");
2306        }
2307
2308        #[test]
2309        fn test_display_format_internal_errors() {
2310            let error = GoudError::InternalError("unexpected state".to_string());
2311            let display = format!("{}", error);
2312            assert_eq!(display, "[GOUD-900] Internal: unexpected state");
2313
2314            let error = GoudError::NotImplemented("feature X".to_string());
2315            let display = format!("{}", error);
2316            assert_eq!(display, "[GOUD-901] Internal: feature X");
2317        }
2318
2319        #[test]
2320        fn test_error_trait_implementation() {
2321            let error = GoudError::NotInitialized;
2322
2323            // Verify Error trait is implemented
2324            let error_ref: &dyn Error = &error;
2325            assert!(error_ref.source().is_none());
2326
2327            // Verify Display works through Error trait
2328            let display = format!("{}", error_ref);
2329            assert!(display.contains("GOUD-1"));
2330        }
2331
2332        #[test]
2333        fn test_message_method() {
2334            // Unit variants return default messages
2335            assert_eq!(
2336                GoudError::NotInitialized.message(),
2337                "Engine has not been initialized"
2338            );
2339            assert_eq!(GoudError::InvalidHandle.message(), "Invalid handle");
2340            assert_eq!(GoudError::EntityNotFound.message(), "Entity not found");
2341
2342            // Variants with messages return the message
2343            let error = GoudError::InitializationFailed("custom message".to_string());
2344            assert_eq!(error.message(), "custom message");
2345
2346            let error = GoudError::ResourceNotFound("path/to/file".to_string());
2347            assert_eq!(error.message(), "path/to/file");
2348        }
2349
2350        #[test]
2351        fn test_from_io_error_not_found() {
2352            let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
2353            let goud_error: GoudError = io_error.into();
2354
2355            assert!(matches!(goud_error, GoudError::ResourceNotFound(_)));
2356            assert_eq!(goud_error.error_code(), ERR_RESOURCE_NOT_FOUND);
2357        }
2358
2359        #[test]
2360        fn test_from_io_error_permission_denied() {
2361            let io_error =
2362                std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
2363            let goud_error: GoudError = io_error.into();
2364
2365            assert!(matches!(goud_error, GoudError::ResourceLoadFailed(_)));
2366            assert_eq!(goud_error.error_code(), ERR_RESOURCE_LOAD_FAILED);
2367            assert!(goud_error.message().contains("Permission denied"));
2368        }
2369
2370        #[test]
2371        fn test_from_io_error_other() {
2372            let io_error =
2373                std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "network error");
2374            let goud_error: GoudError = io_error.into();
2375
2376            assert!(matches!(goud_error, GoudError::ResourceLoadFailed(_)));
2377            assert_eq!(goud_error.error_code(), ERR_RESOURCE_LOAD_FAILED);
2378        }
2379
2380        #[test]
2381        fn test_from_string() {
2382            let msg = "something went wrong".to_string();
2383            let error: GoudError = msg.into();
2384
2385            assert!(matches!(error, GoudError::InternalError(_)));
2386            assert_eq!(error.message(), "something went wrong");
2387            assert_eq!(error.error_code(), ERR_INTERNAL_ERROR);
2388        }
2389
2390        #[test]
2391        fn test_from_str() {
2392            let error: GoudError = "oops".into();
2393
2394            assert!(matches!(error, GoudError::InternalError(_)));
2395            assert_eq!(error.message(), "oops");
2396            assert_eq!(error.error_code(), ERR_INTERNAL_ERROR);
2397        }
2398
2399        #[test]
2400        fn test_goud_result_ok() {
2401            fn might_fail() -> GoudResult<i32> {
2402                Ok(42)
2403            }
2404
2405            let result = might_fail();
2406            assert!(result.is_ok());
2407            assert_eq!(result.unwrap(), 42);
2408        }
2409
2410        #[test]
2411        fn test_goud_result_err() {
2412            fn always_fails() -> GoudResult<i32> {
2413                Err(GoudError::NotInitialized)
2414            }
2415
2416            let result = always_fails();
2417            assert!(result.is_err());
2418            let error = result.unwrap_err();
2419            assert_eq!(error, GoudError::NotInitialized);
2420        }
2421
2422        #[test]
2423        fn test_goud_result_with_question_mark() {
2424            fn inner() -> GoudResult<i32> {
2425                Err(GoudError::ResourceNotFound("missing.txt".to_string()))
2426            }
2427
2428            fn outer() -> GoudResult<i32> {
2429                let value = inner()?;
2430                Ok(value + 1)
2431            }
2432
2433            let result = outer();
2434            assert!(result.is_err());
2435            if let Err(GoudError::ResourceNotFound(msg)) = result {
2436                assert_eq!(msg, "missing.txt");
2437            } else {
2438                panic!("Expected ResourceNotFound error");
2439            }
2440        }
2441
2442        #[test]
2443        fn test_error_can_be_boxed() {
2444            // Verify GoudError can be used as Box<dyn Error>
2445            let error: Box<dyn Error> = Box::new(GoudError::NotInitialized);
2446            let display = format!("{}", error);
2447            assert!(display.contains("GOUD-1"));
2448        }
2449
2450        #[test]
2451        fn test_display_versus_debug() {
2452            let error = GoudError::InitializationFailed("test message".to_string());
2453
2454            let display = format!("{}", error);
2455            let debug = format!("{:?}", error);
2456
2457            // Display is user-friendly with format
2458            assert!(display.contains("[GOUD-10]"));
2459            assert!(display.contains("Context"));
2460            assert!(display.contains("test message"));
2461
2462            // Debug is Rust-style
2463            assert!(debug.contains("InitializationFailed"));
2464            assert!(debug.contains("test message"));
2465
2466            // They should be different
2467            assert_ne!(display, debug);
2468        }
2469    }
2470
2471    // =========================================================================
2472    // FFI Error Bridge Tests
2473    // =========================================================================
2474
2475    mod ffi {
2476        use super::*;
2477        use std::sync::{Arc, Barrier};
2478        use std::thread;
2479
2480        /// Helper to ensure clean state for each test
2481        fn with_clean_error_state<F, R>(f: F) -> R
2482        where
2483            F: FnOnce() -> R,
2484        {
2485            clear_last_error();
2486            let result = f();
2487            clear_last_error();
2488            result
2489        }
2490
2491        #[test]
2492        fn test_set_and_get_last_error() {
2493            with_clean_error_state(|| {
2494                set_last_error(GoudError::NotInitialized);
2495                let error = get_last_error();
2496                assert!(error.is_some());
2497                assert_eq!(error.unwrap(), GoudError::NotInitialized);
2498            });
2499        }
2500
2501        #[test]
2502        fn test_get_does_not_clear_error() {
2503            with_clean_error_state(|| {
2504                set_last_error(GoudError::NotInitialized);
2505
2506                // Call get_last_error multiple times
2507                let error1 = get_last_error();
2508                let error2 = get_last_error();
2509                let error3 = get_last_error();
2510
2511                // All should return the same error
2512                assert_eq!(error1, error2);
2513                assert_eq!(error2, error3);
2514                assert!(error1.is_some());
2515            });
2516        }
2517
2518        #[test]
2519        fn test_take_clears_error() {
2520            with_clean_error_state(|| {
2521                set_last_error(GoudError::NotInitialized);
2522
2523                // First take should return the error
2524                let error1 = take_last_error();
2525                assert!(error1.is_some());
2526                assert_eq!(error1.unwrap(), GoudError::NotInitialized);
2527
2528                // Subsequent takes should return None
2529                let error2 = take_last_error();
2530                assert!(error2.is_none());
2531
2532                let error3 = take_last_error();
2533                assert!(error3.is_none());
2534            });
2535        }
2536
2537        #[test]
2538        fn test_last_error_code_no_error() {
2539            with_clean_error_state(|| {
2540                // No error set, should return SUCCESS
2541                assert_eq!(last_error_code(), SUCCESS);
2542            });
2543        }
2544
2545        #[test]
2546        fn test_last_error_code_with_error() {
2547            with_clean_error_state(|| {
2548                set_last_error(GoudError::NotInitialized);
2549                assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
2550
2551                set_last_error(GoudError::ResourceNotFound("test".to_string()));
2552                assert_eq!(last_error_code(), ERR_RESOURCE_NOT_FOUND);
2553
2554                set_last_error(GoudError::ShaderCompilationFailed("error".to_string()));
2555                assert_eq!(last_error_code(), ERR_SHADER_COMPILATION_FAILED);
2556            });
2557        }
2558
2559        #[test]
2560        fn test_last_error_message_no_error() {
2561            with_clean_error_state(|| {
2562                // No error set, should return None
2563                assert!(last_error_message().is_none());
2564            });
2565        }
2566
2567        #[test]
2568        fn test_last_error_message_with_error() {
2569            with_clean_error_state(|| {
2570                // Error without custom message
2571                set_last_error(GoudError::NotInitialized);
2572                let msg = last_error_message();
2573                assert!(msg.is_some());
2574                assert_eq!(msg.unwrap(), "Engine has not been initialized");
2575
2576                // Error with custom message
2577                set_last_error(GoudError::InitializationFailed("GPU not found".to_string()));
2578                let msg = last_error_message();
2579                assert!(msg.is_some());
2580                assert_eq!(msg.unwrap(), "GPU not found");
2581            });
2582        }
2583
2584        #[test]
2585        fn test_clear_last_error() {
2586            with_clean_error_state(|| {
2587                set_last_error(GoudError::NotInitialized);
2588                assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
2589
2590                clear_last_error();
2591
2592                assert_eq!(last_error_code(), SUCCESS);
2593                assert!(last_error_message().is_none());
2594                assert!(get_last_error().is_none());
2595            });
2596        }
2597
2598        #[test]
2599        fn test_overwrite_error() {
2600            with_clean_error_state(|| {
2601                // Set first error
2602                set_last_error(GoudError::NotInitialized);
2603                assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
2604
2605                // Overwrite with second error
2606                set_last_error(GoudError::ResourceNotFound("file.txt".to_string()));
2607                assert_eq!(last_error_code(), ERR_RESOURCE_NOT_FOUND);
2608
2609                // First error should be gone
2610                let error = take_last_error();
2611                assert!(matches!(error, Some(GoudError::ResourceNotFound(_))));
2612            });
2613        }
2614
2615        #[test]
2616        fn test_thread_isolation() {
2617            // This test verifies that errors set in one thread don't affect other threads
2618            let barrier = Arc::new(Barrier::new(2));
2619            let barrier_clone = Arc::clone(&barrier);
2620
2621            let handle = thread::spawn(move || {
2622                // Clear any existing error in this thread
2623                clear_last_error();
2624
2625                // Wait for main thread to set its error
2626                barrier_clone.wait();
2627
2628                // This thread should have no error (thread-local storage)
2629                assert_eq!(
2630                    last_error_code(),
2631                    SUCCESS,
2632                    "Thread should have no error from main thread"
2633                );
2634
2635                // Set a different error in this thread
2636                set_last_error(GoudError::ResourceNotFound("thread_file.txt".to_string()));
2637                assert_eq!(last_error_code(), ERR_RESOURCE_NOT_FOUND);
2638
2639                // Wait for main thread to check its error
2640                barrier_clone.wait();
2641            });
2642
2643            with_clean_error_state(|| {
2644                // Set error in main thread
2645                set_last_error(GoudError::NotInitialized);
2646                assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
2647
2648                // Signal spawned thread to check
2649                barrier.wait();
2650
2651                // Wait for spawned thread to set its error
2652                barrier.wait();
2653
2654                // Main thread should still have its original error (unaffected by spawned thread)
2655                assert_eq!(
2656                    last_error_code(),
2657                    ERR_NOT_INITIALIZED,
2658                    "Main thread error should not be affected by spawned thread"
2659                );
2660            });
2661
2662            handle.join().unwrap();
2663        }
2664
2665        #[test]
2666        fn test_multiple_threads_independent_errors() {
2667            use std::sync::atomic::{AtomicUsize, Ordering};
2668
2669            const THREAD_COUNT: usize = 4;
2670            let success_count = Arc::new(AtomicUsize::new(0));
2671
2672            let handles: Vec<_> = (0..THREAD_COUNT)
2673                .map(|i| {
2674                    let success_count = Arc::clone(&success_count);
2675                    thread::spawn(move || {
2676                        clear_last_error();
2677
2678                        // Each thread sets a different error
2679                        let error = match i {
2680                            0 => GoudError::NotInitialized,
2681                            1 => GoudError::AlreadyInitialized,
2682                            2 => GoudError::InvalidContext,
2683                            _ => GoudError::ContextDestroyed,
2684                        };
2685                        let expected_code = error.error_code();
2686                        set_last_error(error);
2687
2688                        // Verify own error
2689                        if last_error_code() == expected_code {
2690                            success_count.fetch_add(1, Ordering::SeqCst);
2691                        }
2692
2693                        clear_last_error();
2694                    })
2695                })
2696                .collect();
2697
2698            for handle in handles {
2699                handle.join().unwrap();
2700            }
2701
2702            assert_eq!(
2703                success_count.load(Ordering::SeqCst),
2704                THREAD_COUNT,
2705                "All threads should have set and verified their own errors"
2706            );
2707        }
2708
2709        // =====================================================================
2710        // GoudFFIResult Tests
2711        // =====================================================================
2712
2713        #[test]
2714        fn test_ffi_result_success() {
2715            let result = GoudFFIResult::success();
2716            assert!(result.success);
2717            assert!(result.is_success());
2718            assert!(!result.is_error());
2719            assert_eq!(result.code, SUCCESS);
2720        }
2721
2722        #[test]
2723        fn test_ffi_result_from_code_success() {
2724            let result = GoudFFIResult::from_code(SUCCESS);
2725            assert!(result.success);
2726            assert_eq!(result.code, SUCCESS);
2727        }
2728
2729        #[test]
2730        fn test_ffi_result_from_code_error() {
2731            let result = GoudFFIResult::from_code(ERR_NOT_INITIALIZED);
2732            assert!(!result.success);
2733            assert!(result.is_error());
2734            assert!(!result.is_success());
2735            assert_eq!(result.code, ERR_NOT_INITIALIZED);
2736        }
2737
2738        #[test]
2739        fn test_ffi_result_from_error() {
2740            with_clean_error_state(|| {
2741                let result = GoudFFIResult::from_error(GoudError::NotInitialized);
2742                assert!(!result.success);
2743                assert_eq!(result.code, ERR_NOT_INITIALIZED);
2744
2745                // Should have set last error
2746                assert_eq!(last_error_code(), ERR_NOT_INITIALIZED);
2747            });
2748        }
2749
2750        #[test]
2751        fn test_ffi_result_from_error_with_message() {
2752            with_clean_error_state(|| {
2753                let error = GoudError::InitializationFailed("Custom error message".to_string());
2754                let result = GoudFFIResult::from_error(error);
2755
2756                assert!(!result.success);
2757                assert_eq!(result.code, ERR_INITIALIZATION_FAILED);
2758
2759                // Message should be retrievable
2760                let msg = last_error_message();
2761                assert_eq!(msg, Some("Custom error message".to_string()));
2762            });
2763        }
2764
2765        #[test]
2766        fn test_ffi_result_from_result_ok() {
2767            with_clean_error_state(|| {
2768                // Set an error first to verify it gets cleared
2769                set_last_error(GoudError::NotInitialized);
2770
2771                let result: GoudResult<i32> = Ok(42);
2772                let ffi_result = GoudFFIResult::from_result(result);
2773
2774                assert!(ffi_result.success);
2775                assert_eq!(ffi_result.code, SUCCESS);
2776
2777                // Error should be cleared
2778                assert_eq!(last_error_code(), SUCCESS);
2779            });
2780        }
2781
2782        #[test]
2783        fn test_ffi_result_from_result_err() {
2784            with_clean_error_state(|| {
2785                let result: GoudResult<i32> =
2786                    Err(GoudError::ResourceNotFound("test.png".to_string()));
2787                let ffi_result = GoudFFIResult::from_result(result);
2788
2789                assert!(!ffi_result.success);
2790                assert_eq!(ffi_result.code, ERR_RESOURCE_NOT_FOUND);
2791
2792                // Error should be set
2793                assert_eq!(last_error_code(), ERR_RESOURCE_NOT_FOUND);
2794                assert_eq!(last_error_message(), Some("test.png".to_string()));
2795            });
2796        }
2797
2798        #[test]
2799        fn test_ffi_result_default() {
2800            let result = GoudFFIResult::default();
2801            assert!(result.success);
2802            assert_eq!(result.code, SUCCESS);
2803        }
2804
2805        #[test]
2806        fn test_ffi_result_from_goud_error() {
2807            with_clean_error_state(|| {
2808                let ffi_result: GoudFFIResult = GoudError::EntityNotFound.into();
2809                assert!(!ffi_result.success);
2810                assert_eq!(ffi_result.code, ERR_ENTITY_NOT_FOUND);
2811            });
2812        }
2813
2814        #[test]
2815        fn test_ffi_result_from_goud_result() {
2816            with_clean_error_state(|| {
2817                let ok: GoudResult<String> = Ok("hello".to_string());
2818                let ffi_result: GoudFFIResult = ok.into();
2819                assert!(ffi_result.success);
2820
2821                let err: GoudResult<String> = Err(GoudError::InvalidHandle);
2822                let ffi_result: GoudFFIResult = err.into();
2823                assert!(!ffi_result.success);
2824                assert_eq!(ffi_result.code, ERR_INVALID_HANDLE);
2825            });
2826        }
2827
2828        #[test]
2829        fn test_ffi_result_derive_traits() {
2830            // Test Clone
2831            let result1 = GoudFFIResult::from_code(ERR_NOT_INITIALIZED);
2832            let result2 = result1;
2833            assert_eq!(result1, result2);
2834
2835            // Test Copy (implicit in the above)
2836
2837            // Test Debug
2838            let debug_str = format!("{:?}", result1);
2839            assert!(debug_str.contains("GoudFFIResult"));
2840            assert!(debug_str.contains("code"));
2841            assert!(debug_str.contains("success"));
2842
2843            // Test PartialEq and Eq
2844            assert_eq!(GoudFFIResult::success(), GoudFFIResult::success());
2845            assert_ne!(
2846                GoudFFIResult::success(),
2847                GoudFFIResult::from_code(ERR_NOT_INITIALIZED)
2848            );
2849        }
2850
2851        #[test]
2852        fn test_ffi_result_repr_c() {
2853            // Verify size and alignment are reasonable for FFI
2854            use std::mem::{align_of, size_of};
2855
2856            // GoudFFIResult should have predictable size
2857            // i32 (4 bytes) + bool (1 byte) + padding = typically 8 bytes on most platforms
2858            let size = size_of::<GoudFFIResult>();
2859            assert!(size >= 5, "GoudFFIResult should be at least 5 bytes");
2860            assert!(size <= 8, "GoudFFIResult should be at most 8 bytes");
2861
2862            // Alignment should be at least 4 bytes (for i32)
2863            let align = align_of::<GoudFFIResult>();
2864            assert!(
2865                align >= 4,
2866                "GoudFFIResult should have at least 4-byte alignment"
2867            );
2868        }
2869
2870        #[test]
2871        fn test_ffi_workflow_simulation() {
2872            // Simulate a typical FFI workflow:
2873            // 1. Rust function is called
2874            // 2. Function returns error result
2875            // 3. C# retrieves error code and message
2876            // 4. C# clears the error
2877
2878            with_clean_error_state(|| {
2879                // Simulate Rust function that fails
2880                fn rust_ffi_function() -> GoudFFIResult {
2881                    let result: GoudResult<()> = Err(GoudError::ShaderCompilationFailed(
2882                        "ERROR: 0:15: 'vec3' : undeclared identifier".to_string(),
2883                    ));
2884                    GoudFFIResult::from_result(result)
2885                }
2886
2887                // Call the function
2888                let ffi_result = rust_ffi_function();
2889
2890                // C# side would check result
2891                assert!(!ffi_result.success);
2892                assert_eq!(ffi_result.code, ERR_SHADER_COMPILATION_FAILED);
2893
2894                // C# side would get detailed error info
2895                let code = last_error_code();
2896                let message = last_error_message();
2897
2898                assert_eq!(code, ERR_SHADER_COMPILATION_FAILED);
2899                assert!(message.is_some());
2900                assert!(message.unwrap().contains("vec3"));
2901
2902                // C# side would clear the error
2903                clear_last_error();
2904
2905                // Verify clean state
2906                assert_eq!(last_error_code(), SUCCESS);
2907                assert!(last_error_message().is_none());
2908            });
2909        }
2910
2911        #[test]
2912        fn test_ffi_success_workflow_simulation() {
2913            // Simulate a successful FFI call
2914
2915            with_clean_error_state(|| {
2916                // Simulate Rust function that succeeds
2917                fn rust_ffi_function() -> GoudFFIResult {
2918                    let result: GoudResult<()> = Ok(());
2919                    GoudFFIResult::from_result(result)
2920                }
2921
2922                // Call the function
2923                let ffi_result = rust_ffi_function();
2924
2925                // C# side checks result
2926                assert!(ffi_result.success);
2927                assert_eq!(ffi_result.code, SUCCESS);
2928
2929                // No error should be set
2930                assert_eq!(last_error_code(), SUCCESS);
2931                assert!(last_error_message().is_none());
2932            });
2933        }
2934    }
2935}