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}