glitcher_api/
builtin.rs

1//! Builtin Plugin Provider Interface
2//!
3//! Provides platform-agnostic interface for accessing builtin plugins.
4//! Builtins are distributed as WASM files in mods/builtin/ directory.
5//!
6//! ## Design Philosophy
7//!
8//! - **Single Distribution Mechanism**: Builtin and user plugins use same format (WASM)
9//! - **Platform Agnostic**: Trait allows different implementations (Desktop/Headless/WebUI)
10//! - **On-Demand Loading**: Builtins loaded via DefaultSetup, not at startup
11//! - **No Special Cases**: Builtins treated like any other plugin
12//!
13//! ## Usage
14//!
15//! ```rust,ignore
16//! use glitcher_api::builtin::BuiltinProvider;
17//!
18//! // Desktop implementation
19//! let provider = DesktopBuiltinProvider::new("mods/builtin");
20//!
21//! // List available builtins
22//! for builtin_id in provider.list_builtins() {
23//!     println!("Found builtin: {}", builtin_id);
24//! }
25//!
26//! // Load a specific builtin
27//! let wasm_bytes = provider.load_builtin("chromatic-aberration")?;
28//! plugin_controller.load_plugin("chromatic-aberration", &wasm_bytes)?;
29//! ```
30
31/// Builtin plugin provider interface
32///
33/// Platforms implement this trait to provide access to builtin plugins.
34/// Builtins are WASM plugins distributed in a platform-specific way:
35/// - Desktop: mods/builtin/ directory
36/// - WebUI: Bundled in web app assets
37/// - Headless: Embedded via include_bytes!
38pub trait BuiltinProvider: Send + Sync {
39    /// List all available builtin plugin IDs
40    ///
41    /// Returns IDs in no particular order.
42    /// Example: `["chromatic-aberration", "pixelate", "flash"]`
43    fn list_builtins(&self) -> Vec<String>;
44
45    /// Load a builtin plugin by ID
46    ///
47    /// # Arguments
48    /// * `id` - Builtin plugin ID (e.g., "chromatic-aberration")
49    ///
50    /// # Returns
51    /// * `Ok(bytes)` - WASM bytes for the builtin plugin
52    /// * `Err(String)` - Error if builtin not found or load failed
53    ///
54    /// # Example
55    /// ```rust,ignore
56    /// let wasm = provider.load_builtin("chromatic-aberration")?;
57    /// assert!(!wasm.is_empty());
58    /// ```
59    fn load_builtin(&self, id: &str) -> Result<Vec<u8>, String>;
60
61    /// Check if a builtin exists
62    ///
63    /// Default implementation scans the list, but platforms may override
64    /// for more efficient checking.
65    fn has_builtin(&self, id: &str) -> bool {
66        self.list_builtins().iter().any(|b| b == id)
67    }
68
69    /// Get builtin metadata without loading WASM
70    ///
71    /// Optional optimization: Return manifest without full WASM parse.
72    /// Default implementation returns None (requires full load).
73    fn get_builtin_metadata(&self, _id: &str) -> Option<BuiltinMetadata> {
74        None
75    }
76}
77
78/// Lightweight builtin metadata (without loading WASM)
79///
80/// For UI display and filtering before full plugin load.
81#[derive(Debug, Clone)]
82pub struct BuiltinMetadata {
83    pub id: String,
84    pub display_name: String,
85    pub version: String,
86    pub author: String,
87    pub description: String,
88    pub category: String, // Simplified for lightweight access
89    pub tags: Vec<String>,
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    struct MockBuiltinProvider {
97        builtins: Vec<String>,
98    }
99
100    impl BuiltinProvider for MockBuiltinProvider {
101        fn list_builtins(&self) -> Vec<String> {
102            self.builtins.clone()
103        }
104
105        fn load_builtin(&self, id: &str) -> Result<Vec<u8>, String> {
106            if self.has_builtin(id) {
107                Ok(vec![0, 1, 2, 3]) // Dummy WASM bytes
108            } else {
109                Err(format!("Builtin not found: {}", id))
110            }
111        }
112    }
113
114    #[test]
115    fn test_builtin_provider_trait() {
116        let provider = MockBuiltinProvider {
117            builtins: vec!["test1".to_string(), "test2".to_string()],
118        };
119
120        assert_eq!(provider.list_builtins().len(), 2);
121        assert!(provider.has_builtin("test1"));
122        assert!(!provider.has_builtin("test3"));
123    }
124
125    #[test]
126    fn test_load_builtin() {
127        let provider = MockBuiltinProvider {
128            builtins: vec!["test".to_string()],
129        };
130
131        let result = provider.load_builtin("test");
132        assert!(result.is_ok());
133
134        let result_not_found = provider.load_builtin("nonexistent");
135        assert!(result_not_found.is_err());
136    }
137}