Skip to main content

forge_kit/
metadata.rs

1//! High-performance metadata manager for ForgeScript functions, enums, and events.
2//!
3//! This module provides:
4//! - Fast function lookup using a prefix trie
5//! - WASM-compatible by default (no filesystem dependencies)
6//! - Optional caching support for native platforms
7//! - Robust error handling with no panics
8//! - Concurrent access with DashMap
9
10use crate::types::{Event, Function};
11use dashmap::DashMap;
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::sync::Arc;
15
16// ============================================================================
17// Core Types
18// ============================================================================
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct EventField {
22    pub name: String,
23    #[serde(default)]
24    pub description: String,
25}
26
27/// Source configuration for fetching metadata
28#[derive(Debug, Clone)]
29pub struct MetadataSource {
30    pub extension: String,
31    pub functions_url: Option<String>,
32    pub enums_url: Option<String>,
33    pub events_url: Option<String>,
34}
35
36impl MetadataSource {
37    /// Create a new metadata source
38    pub fn new(extension: impl Into<String>) -> Self {
39        Self {
40            extension: extension.into(),
41            functions_url: None,
42            enums_url: None,
43            events_url: None,
44        }
45    }
46
47    /// Set functions URL
48    pub fn with_functions(mut self, url: impl Into<String>) -> Self {
49        self.functions_url = Some(url.into());
50        self
51    }
52
53    /// Set enums URL
54    pub fn with_enums(mut self, url: impl Into<String>) -> Self {
55        self.enums_url = Some(url.into());
56        self
57    }
58
59    /// Set events URL
60    pub fn with_events(mut self, url: impl Into<String>) -> Self {
61        self.events_url = Some(url.into());
62        self
63    }
64}
65
66// ============================================================================
67// Error Types
68// ============================================================================
69
70#[derive(Debug, Clone)]
71pub enum MetadataError {
72    NetworkError(String),
73    ParseError(String),
74    NotFound(String),
75    InvalidData(String),
76    CacheError(String),
77}
78
79impl std::fmt::Display for MetadataError {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            Self::NetworkError(e) => write!(f, "Network error: {}", e),
83            Self::ParseError(e) => write!(f, "Parse error: {}", e),
84            Self::NotFound(e) => write!(f, "Not found: {}", e),
85            Self::InvalidData(e) => write!(f, "Invalid data: {}", e),
86            Self::CacheError(e) => write!(f, "Cache error: {}", e),
87        }
88    }
89}
90
91impl std::error::Error for MetadataError {}
92
93pub type Result<T> = std::result::Result<T, MetadataError>;
94
95// ============================================================================
96// Fast Trie for Function Lookup
97// ============================================================================
98
99#[derive(Default)]
100struct TrieNode {
101    children: HashMap<char, Box<TrieNode>>,
102    value: Option<Arc<Function>>,
103}
104
105/// High-performance prefix trie for function lookup
106#[derive(Default)]
107pub struct FunctionTrie {
108    root: TrieNode,
109    count: usize,
110}
111
112impl FunctionTrie {
113    /// Create a new empty trie
114    #[inline]
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    /// Insert a function into the trie
120    pub fn insert(&mut self, key: &str, func: Arc<Function>) {
121        let mut node = &mut self.root;
122
123        // Normalize to lowercase for case-insensitive lookup
124        for ch in key.to_lowercase().chars() {
125            node = node
126                .children
127                .entry(ch)
128                .or_insert_with(|| Box::new(TrieNode::default()));
129        }
130
131        if node.value.is_none() {
132            self.count += 1;
133        }
134        node.value = Some(func);
135    }
136
137    /// Get exact match (case-insensitive)
138    pub fn get_exact(&self, key: &str) -> Option<Arc<Function>> {
139        let mut node = &self.root;
140
141        for ch in key.to_lowercase().chars() {
142            match node.children.get(&ch) {
143                Some(next) => node = next,
144                None => return None,
145            }
146        }
147
148        node.value.clone()
149    }
150
151    /// Get longest prefix match
152    pub fn get_prefix(&self, text: &str) -> Option<(String, Arc<Function>)> {
153        let chars: Vec<char> = text.to_lowercase().chars().collect();
154        let mut best_match: Option<(String, Arc<Function>)> = None;
155
156        for start in 0..chars.len() {
157            let mut node = &self.root;
158            let mut matched = String::with_capacity(chars.len() - start);
159
160            for &ch in &chars[start..] {
161                match node.children.get(&ch) {
162                    Some(next) => {
163                        matched.push(ch);
164                        node = next;
165
166                        if let Some(func) = &node.value {
167                            best_match = Some((matched.clone(), func.clone()));
168                        }
169                    }
170                    None => break,
171                }
172            }
173        }
174
175        best_match
176    }
177
178    /// Get all functions with a given prefix
179    pub fn get_completions(&self, prefix: &str) -> Vec<Arc<Function>> {
180        let mut node = &self.root;
181
182        // Navigate to prefix
183        for ch in prefix.to_lowercase().chars() {
184            match node.children.get(&ch) {
185                Some(next) => node = next,
186                None => return Vec::new(),
187            }
188        }
189
190        // Collect all functions under this prefix
191        let mut results = Vec::new();
192        self.collect_all(node, &mut results);
193        results
194    }
195
196    fn collect_all(&self, node: &TrieNode, results: &mut Vec<Arc<Function>>) {
197        if let Some(func) = &node.value {
198            results.push(func.clone());
199        }
200
201        for child in node.children.values() {
202            self.collect_all(child, results);
203        }
204    }
205
206    /// Get all functions in the trie
207    pub fn all_functions(&self) -> Vec<Arc<Function>> {
208        let mut results = Vec::with_capacity(self.count);
209        self.collect_all(&self.root, &mut results);
210        results
211    }
212
213    /// Number of functions in trie
214    #[inline]
215    pub fn len(&self) -> usize {
216        self.count
217    }
218
219    /// Check if trie is empty
220    #[inline]
221    pub fn is_empty(&self) -> bool {
222        self.count == 0
223    }
224
225    /// Clear all functions
226    pub fn clear(&mut self) {
227        self.root = TrieNode::default();
228        self.count = 0;
229    }
230}
231
232// ============================================================================
233// HTTP Fetcher
234// ============================================================================
235
236/// HTTP fetcher for metadata
237pub struct Fetcher {
238    client: reqwest::Client,
239}
240
241impl Fetcher {
242    /// Create a new fetcher
243    pub fn new() -> Self {
244        #[cfg(not(target_arch = "wasm32"))]
245        let client = reqwest::Client::builder()
246            .timeout(std::time::Duration::from_secs(30))
247            .build()
248            .unwrap_or_else(|_| reqwest::Client::new());
249
250        #[cfg(target_arch = "wasm32")]
251        let client = reqwest::Client::builder()
252            .build()
253            .unwrap_or_else(|_| reqwest::Client::new());
254
255        Self { client }
256    }
257
258    /// Fetch JSON from a URL with proper error handling
259    pub async fn fetch_json<T: serde::de::DeserializeOwned>(&self, url: &str) -> Result<T> {
260        // Make request
261        let response =
262            self.client.get(url).send().await.map_err(|e| {
263                MetadataError::NetworkError(format!("Failed to fetch {}: {}", url, e))
264            })?;
265
266        // Check status
267        let status = response.status();
268        if status == reqwest::StatusCode::NOT_FOUND {
269            return Err(MetadataError::NotFound(format!("URL not found: {}", url)));
270        }
271
272        if !status.is_success() {
273            return Err(MetadataError::NetworkError(format!(
274                "HTTP {}: {}",
275                status, url
276            )));
277        }
278
279        // Parse JSON
280        let text = response.text().await.map_err(|e| {
281            MetadataError::NetworkError(format!("Failed to read response from {}: {}", url, e))
282        })?;
283
284        serde_json::from_str(&text).map_err(|e| {
285            MetadataError::ParseError(format!("Failed to parse JSON from {}: {}", url, e))
286        })
287    }
288
289    /// Fetch functions from URL
290    pub async fn fetch_functions(&self, url: &str, extension: String) -> Result<Vec<Function>> {
291        let mut functions: Vec<Function> = self.fetch_json(url).await?;
292
293        // Add extension metadata
294        for func in &mut functions {
295            func.extension = Some(extension.clone());
296            func.source_url = Some(url.to_string());
297        }
298
299        Ok(functions)
300    }
301
302    /// Fetch enums from URL
303    pub async fn fetch_enums(&self, url: &str) -> Result<HashMap<String, Vec<String>>> {
304        self.fetch_json(url).await
305    }
306
307    /// Fetch events from URL
308    pub async fn fetch_events(&self, url: &str) -> Result<Vec<Event>> {
309        self.fetch_json(url).await
310    }
311}
312
313impl Default for Fetcher {
314    fn default() -> Self {
315        Self::new()
316    }
317}
318
319// ============================================================================
320// Metadata Manager
321// ============================================================================
322
323/// High-performance metadata manager
324pub struct MetadataManager {
325    trie: std::sync::RwLock<FunctionTrie>,
326    enums: DashMap<String, Vec<String>>,
327    events: DashMap<String, Event>,
328    sources: std::sync::RwLock<Vec<MetadataSource>>,
329    fetcher: Fetcher,
330}
331
332impl MetadataManager {
333    /// Create a new metadata manager
334    pub fn new() -> Self {
335        Self {
336            trie: std::sync::RwLock::new(FunctionTrie::new()),
337            enums: DashMap::new(),
338            events: DashMap::new(),
339            sources: std::sync::RwLock::new(Vec::new()),
340            fetcher: Fetcher::new(),
341        }
342    }
343
344    /// Add a metadata source
345    pub fn add_source(&self, source: MetadataSource) {
346        self.sources.write().unwrap().push(source);
347    }
348
349    /// Fetch all metadata from configured sources
350    pub async fn fetch_all(&self) -> Result<FetchStats> {
351        let sources = self.sources.read().unwrap().clone();
352
353        let mut total_functions = 0;
354        let mut total_enums = 0;
355        let mut total_events = 0;
356        let mut errors = Vec::new();
357
358        for source in sources {
359            // Fetch functions
360            if let Some(url) = &source.functions_url {
361                match self
362                    .fetcher
363                    .fetch_functions(url, source.extension.clone())
364                    .await
365                {
366                    Ok(functions) => {
367                        total_functions += functions.len();
368                        self.add_functions(functions);
369                    }
370                    Err(e) => {
371                        errors.push(format!("Functions from {}: {}", source.extension, e));
372                    }
373                }
374            }
375
376            // Fetch enums
377            if let Some(url) = &source.enums_url {
378                match self.fetcher.fetch_enums(url).await {
379                    Ok(enums) => {
380                        total_enums += enums.len();
381                        for (name, values) in enums {
382                            self.enums.insert(name, values);
383                        }
384                    }
385                    Err(e) => {
386                        // Enums are optional, just log
387                        if !matches!(e, MetadataError::NotFound(_)) {
388                            errors.push(format!("Enums from {}: {}", source.extension, e));
389                        }
390                    }
391                }
392            }
393
394            // Fetch events
395            if let Some(url) = &source.events_url {
396                match self.fetcher.fetch_events(url).await {
397                    Ok(events) => {
398                        total_events += events.len();
399                        for event in events {
400                            self.events.insert(event.name.clone(), event);
401                        }
402                    }
403                    Err(e) => {
404                        // Events are optional, just log
405                        if !matches!(e, MetadataError::NotFound(_)) {
406                            errors.push(format!("Events from {}: {}", source.extension, e));
407                        }
408                    }
409                }
410            }
411        }
412
413        Ok(FetchStats {
414            functions: total_functions,
415            enums: total_enums,
416            events: total_events,
417            errors,
418        })
419    }
420
421    /// Add functions to the manager
422    fn add_functions(&self, functions: Vec<Function>) {
423        let mut trie = self.trie.write().unwrap();
424
425        for func in functions {
426            let arc_func = Arc::new(func.clone());
427
428            // Insert main name
429            trie.insert(&func.name, arc_func.clone());
430
431            // Insert aliases
432            if let Some(aliases) = &func.aliases {
433                for alias in aliases {
434                    let alias_name = if alias.starts_with('$') {
435                        alias.clone()
436                    } else {
437                        format!("${}", alias)
438                    };
439
440                    // Create alias function
441                    let mut alias_func = (*arc_func).clone();
442                    alias_func.name = alias_name.clone();
443                    trie.insert(&alias_name, Arc::new(alias_func));
444                }
445            }
446        }
447    }
448
449    /// Get function by exact name (case-insensitive)
450    #[inline]
451    pub fn get_exact(&self, name: &str) -> Option<Arc<Function>> {
452        self.trie.read().unwrap().get_exact(name)
453    }
454
455    /// Get function by prefix match
456    #[inline]
457    pub fn get_prefix(&self, text: &str) -> Option<(String, Arc<Function>)> {
458        self.trie.read().unwrap().get_prefix(text)
459    }
460
461    /// Get function (tries exact first, then prefix)
462    pub fn get(&self, name: &str) -> Option<Arc<Function>> {
463        let trie = self.trie.read().unwrap();
464
465        // Try exact match first
466        if let Some(func) = trie.get_exact(name) {
467            return Some(func);
468        }
469
470        // Try prefix match
471        trie.get_prefix(name).map(|(_, func)| func)
472    }
473
474    /// Get function with match info (for compatibility)
475    pub fn get_with_match(&self, name: &str) -> Option<(String, Arc<Function>)> {
476        let trie = self.trie.read().unwrap();
477
478        // Try exact match first
479        if let Some(func) = trie.get_exact(name) {
480            return Some((name.to_string(), func));
481        }
482
483        // Try prefix match
484        trie.get_prefix(name)
485    }
486
487    /// Get multiple functions
488    pub fn get_many(&self, names: &[&str]) -> Vec<Option<Arc<Function>>> {
489        names.iter().map(|name| self.get(name)).collect()
490    }
491
492    /// Get completions for a prefix
493    #[inline]
494    pub fn get_completions(&self, prefix: &str) -> Vec<Arc<Function>> {
495        self.trie.read().unwrap().get_completions(prefix)
496    }
497
498    /// Get all functions
499    #[inline]
500    pub fn all_functions(&self) -> Vec<Arc<Function>> {
501        self.trie.read().unwrap().all_functions()
502    }
503
504    /// Get enum values
505    #[inline]
506    pub fn get_enum(&self, name: &str) -> Option<Vec<String>> {
507        self.enums.get(name).map(|v| v.clone())
508    }
509
510    /// Get all enums
511    pub fn all_enums(&self) -> HashMap<String, Vec<String>> {
512        self.enums
513            .iter()
514            .map(|e| (e.key().clone(), e.value().clone()))
515            .collect()
516    }
517
518    /// Get event by name
519    #[inline]
520    pub fn get_event(&self, name: &str) -> Option<Event> {
521        self.events.get(name).map(|v| v.clone())
522    }
523
524    /// Get all events
525    pub fn all_events(&self) -> Vec<Event> {
526        self.events.iter().map(|e| e.value().clone()).collect()
527    }
528
529    /// Get function count
530    #[inline]
531    pub fn function_count(&self) -> usize {
532        self.trie.read().unwrap().len()
533    }
534
535    /// Get enum count
536    #[inline]
537    pub fn enum_count(&self) -> usize {
538        self.enums.len()
539    }
540
541    /// Get event count
542    #[inline]
543    pub fn event_count(&self) -> usize {
544        self.events.len()
545    }
546
547    /// Clear all metadata
548    pub fn clear(&self) {
549        self.trie.write().unwrap().clear();
550        self.enums.clear();
551        self.events.clear();
552    }
553}
554
555impl Default for MetadataManager {
556    fn default() -> Self {
557        Self::new()
558    }
559}
560
561/// Statistics from a fetch operation
562#[derive(Debug, Clone)]
563pub struct FetchStats {
564    pub functions: usize,
565    pub enums: usize,
566    pub events: usize,
567    pub errors: Vec<String>,
568}
569
570impl std::fmt::Display for FetchStats {
571    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
572        write!(
573            f,
574            "Fetched {} functions, {} enums, {} events",
575            self.functions, self.enums, self.events
576        )?;
577
578        if !self.errors.is_empty() {
579            write!(f, " ({} errors)", self.errors.len())?;
580        }
581
582        Ok(())
583    }
584}
585
586// ============================================================================
587// Caching Support (Optional)
588// ============================================================================
589
590/// Serializable cache format
591#[derive(Debug, Serialize, Deserialize)]
592pub struct MetadataCache {
593    pub functions: Vec<Function>,
594    pub enums: HashMap<String, Vec<String>>,
595    pub events: Vec<Event>,
596    pub version: u32,
597}
598
599impl MetadataCache {
600    const VERSION: u32 = 1;
601
602    /// Create a new cache
603    pub fn new(
604        functions: Vec<Function>,
605        enums: HashMap<String, Vec<String>>,
606        events: Vec<Event>,
607    ) -> Self {
608        Self {
609            functions,
610            enums,
611            events,
612            version: Self::VERSION,
613        }
614    }
615}
616
617impl MetadataManager {
618    /// Export metadata to cache
619    pub fn export_cache(&self) -> MetadataCache {
620        MetadataCache::new(
621            self.all_functions().iter().map(|f| (**f).clone()).collect(),
622            self.all_enums(),
623            self.all_events(),
624        )
625    }
626
627    /// Import metadata from cache
628    pub fn import_cache(&self, cache: MetadataCache) -> Result<()> {
629        if cache.version != MetadataCache::VERSION {
630            return Err(MetadataError::CacheError(format!(
631                "Incompatible cache version: expected {}, got {}",
632                MetadataCache::VERSION,
633                cache.version
634            )));
635        }
636
637        // Clear existing data
638        self.clear();
639
640        // Add functions
641        self.add_functions(cache.functions);
642
643        // Add enums
644        for (name, values) in cache.enums {
645            self.enums.insert(name, values);
646        }
647
648        // Add events
649        for event in cache.events {
650            self.events.insert(event.name.clone(), event);
651        }
652
653        Ok(())
654    }
655
656    /// Serialize cache to JSON
657    pub fn cache_to_json(&self) -> Result<String> {
658        let cache = self.export_cache();
659        serde_json::to_string(&cache)
660            .map_err(|e| MetadataError::CacheError(format!("Serialization failed: {}", e)))
661    }
662
663    /// Deserialize cache from JSON
664    pub fn cache_from_json(&self, json: &str) -> Result<()> {
665        let cache: MetadataCache = serde_json::from_str(json)
666            .map_err(|e| MetadataError::CacheError(format!("Deserialization failed: {}", e)))?;
667        self.import_cache(cache)
668    }
669}
670
671// ============================================================================
672// Native Filesystem Caching
673// ============================================================================
674
675#[cfg(not(target_arch = "wasm32"))]
676impl MetadataManager {
677    /// Save cache to file
678    pub fn save_cache_to_file(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
679        use std::io::Write;
680
681        let json = self.cache_to_json()?;
682        let mut file = std::fs::File::create(path)
683            .map_err(|e| MetadataError::CacheError(format!("Failed to create file: {}", e)))?;
684
685        file.write_all(json.as_bytes())
686            .map_err(|e| MetadataError::CacheError(format!("Failed to write file: {}", e)))?;
687
688        Ok(())
689    }
690
691    /// Load cache from file
692    pub fn load_cache_from_file(&self, path: impl AsRef<std::path::Path>) -> Result<()> {
693        let json = std::fs::read_to_string(path)
694            .map_err(|e| MetadataError::CacheError(format!("Failed to read file: {}", e)))?;
695
696        self.cache_from_json(&json)
697    }
698}
699
700// ============================================================================
701// Utility Functions
702// ============================================================================
703
704/// Create a metadata source from a GitHub repository
705pub fn github_source(extension: impl Into<String>, repo: &str, branch: &str) -> MetadataSource {
706    let base = format!("https://raw.githubusercontent.com/{}/{}/", repo, branch);
707
708    MetadataSource::new(extension)
709        .with_functions(format!("{}functions.json", base))
710        .with_enums(format!("{}enums.json", base))
711        .with_events(format!("{}events.json", base))
712}
713
714/// Create a metadata source from custom URLs
715pub fn custom_source(extension: impl Into<String>) -> MetadataSource {
716    MetadataSource::new(extension)
717}