1use super::{ExtensionRegistry, GrammarExtension, ParseError};
8use serde::{Deserialize, Serialize};
9use std::collections::{BTreeMap, BTreeSet};
10use std::path::{Path, PathBuf};
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct ExtensionMetadata {
15 pub name: String,
16 pub version: String,
17 pub description: String,
18 pub author: String,
19 pub dependencies: Vec<String>,
20 pub required_telltale_version: Option<String>,
21 pub priority: Option<u32>,
22 pub overview: Option<String>,
24 pub syntax_guide: Option<String>,
25 pub use_cases: Option<Vec<String>>,
26 pub keywords: Option<Vec<String>>,
27}
28
29#[derive(Debug)]
31pub struct ExtensionPackage {
32 pub metadata: ExtensionMetadata,
33 pub extension: Box<dyn GrammarExtension>,
34 pub source_path: Option<PathBuf>,
35}
36
37#[derive(Debug, Default)]
39pub struct ExtensionDiscovery {
40 discovered_extensions: BTreeMap<String, ExtensionPackage>,
41 search_paths: Vec<PathBuf>,
42}
43
44impl ExtensionDiscovery {
45 pub fn new() -> Self {
47 Self::default()
48 }
49
50 pub fn add_search_path<P: AsRef<Path>>(&mut self, path: P) {
52 self.search_paths.push(path.as_ref().to_path_buf());
53 }
54
55 pub fn register_extension(
57 &mut self,
58 metadata: ExtensionMetadata,
59 extension: Box<dyn GrammarExtension>,
60 ) -> Result<(), ParseError> {
61 if metadata.name.is_empty() {
63 return Err(ParseError::InvalidSyntax {
64 details: "Extension name cannot be empty".to_string(),
65 });
66 }
67
68 if self.discovered_extensions.contains_key(&metadata.name) {
70 return Err(ParseError::RegistrationFailed {
71 extension: metadata.name.clone(),
72 rule: "discovery".to_string(),
73 details: format!(
74 "Extension '{}' is already registered in discovery system",
75 metadata.name
76 ),
77 });
78 }
79
80 let package = ExtensionPackage {
81 metadata,
82 extension,
83 source_path: None,
84 };
85
86 self.discovered_extensions
87 .insert(package.metadata.name.clone(), package);
88 Ok(())
89 }
90
91 pub fn get_extensions(&self) -> &BTreeMap<String, ExtensionPackage> {
93 &self.discovered_extensions
94 }
95
96 pub fn has_extension(&self, name: &str) -> bool {
98 self.discovered_extensions.contains_key(name)
99 }
100
101 pub fn get_metadata(&self, name: &str) -> Option<&ExtensionMetadata> {
103 self.discovered_extensions
104 .get(name)
105 .map(|pkg| &pkg.metadata)
106 }
107
108 pub fn resolve_dependencies(
112 &self,
113 extension_names: &[String],
114 ) -> Result<Vec<String>, ParseError> {
115 let mut resolved = Vec::new();
116 let mut visited = BTreeSet::new();
117 let mut visiting = BTreeSet::new(); fn visit(
121 name: &str,
122 extensions: &BTreeMap<String, ExtensionPackage>,
123 visited: &mut BTreeSet<String>,
124 visiting: &mut BTreeSet<String>,
125 resolved: &mut Vec<String>,
126 ) -> Result<(), ParseError> {
127 if visited.contains(name) {
128 return Ok(());
129 }
130
131 if visiting.contains(name) {
132 return Err(ParseError::Conflict {
133 message: format!("Circular dependency detected involving '{}'", name),
134 });
135 }
136
137 visiting.insert(name.to_string());
138
139 if let Some(package) = extensions.get(name) {
140 for dep in &package.metadata.dependencies {
142 visit(dep, extensions, visited, visiting, resolved)?;
143 }
144 } else {
145 return Err(ParseError::MissingDependency {
146 extension: "dependency_resolution".to_string(),
147 dependency: name.to_string(),
148 });
149 }
150
151 visiting.remove(name);
152 visited.insert(name.to_string());
153 resolved.push(name.to_string());
154
155 Ok(())
156 }
157
158 for ext_name in extension_names {
160 visit(
161 ext_name,
162 &self.discovered_extensions,
163 &mut visited,
164 &mut visiting,
165 &mut resolved,
166 )?;
167 }
168
169 Ok(resolved)
170 }
171
172 pub fn create_registry(
174 &self,
175 extension_names: &[String],
176 ) -> Result<ExtensionRegistry, ParseError> {
177 let resolved = self.resolve_dependencies(extension_names)?;
178 let mut registry = ExtensionRegistry::new();
179
180 for ext_name in resolved {
182 if let Some(package) = self.discovered_extensions.get(&ext_name) {
183 registry.register_grammar(ClonableExtensionWrapper::new(
186 &*package.extension,
187 &package.metadata,
188 ))?;
189
190 for dep in &package.metadata.dependencies {
192 registry.add_dependency(&ext_name, dep);
193 }
194 }
195 }
196
197 Ok(registry)
205 }
206
207 pub fn check_compatibility(&self, extension_names: &[String]) -> Result<(), ParseError> {
209 let resolved = self.resolve_dependencies(extension_names)?;
210
211 for ext_name in &resolved {
213 if let Some(package) = self.discovered_extensions.get(ext_name) {
214 if let Some(required_version) = &package.metadata.required_telltale_version {
215 if required_version != "0.5.0" {
217 return Err(ParseError::IncompatibleExtensions {
218 details: format!(
219 "Extension '{}' requires telltale version '{}', but current version is '0.5.0'. Please update the extension or telltale to compatible versions.",
220 ext_name, required_version
221 ),
222 });
223 }
224 }
225 }
226 }
227
228 Ok(())
229 }
230
231 pub fn load_from_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), ParseError> {
243 let path = path.as_ref();
244
245 let metadata_path = path.join("extension.toml");
247 if metadata_path.exists() {
248 let metadata_str =
249 std::fs::read_to_string(&metadata_path).map_err(|e| ParseError::InvalidSyntax {
250 details: format!("Failed to read extension metadata: {}", e),
251 })?;
252
253 let metadata: ExtensionMetadata =
254 toml::from_str(&metadata_str).map_err(|e| ParseError::InvalidSyntax {
255 details: format!("Invalid extension metadata: {}", e),
256 })?;
257
258 let extension = Box::new(MetadataOnlyExtension::new(&metadata));
260
261 let package = ExtensionPackage {
262 metadata: metadata.clone(),
263 extension,
264 source_path: Some(path.to_path_buf()),
265 };
266
267 self.discovered_extensions.insert(metadata.name, package);
268 }
269
270 Ok(())
271 }
272
273 pub fn with_common_extensions() -> Result<ExtensionRegistry, ParseError> {
275 let mut discovery = Self::new();
276
277 discovery.register_extension(
279 ExtensionMetadata {
280 name: "timeout".to_string(),
281 version: "0.5.0".to_string(),
282 description: "Timeout support for choreographic protocols".to_string(),
283 author: "Telltale Team".to_string(),
284 dependencies: vec![],
285 required_telltale_version: Some("0.5.0".to_string()),
286 priority: Some(100),
287 overview: Some("Adds timeout semantics to choreographic protocols".to_string()),
288 syntax_guide: Some("Use `timeout(duration) { ... }` syntax".to_string()),
289 use_cases: Some(vec![
290 "Network protocols".to_string(),
291 "Real-time systems".to_string(),
292 ]),
293 keywords: Some(vec!["timeout".to_string(), "timing".to_string()]),
294 },
295 Box::new(super::timeout::TimeoutGrammarExtension),
296 )?;
297
298 discovery.register_extension(
299 ExtensionMetadata {
300 name: "aura_annotations".to_string(),
301 version: "0.1.0".to_string(),
302 description: "Aura-style annotations for capability tracking".to_string(),
303 author: "Aura Project".to_string(),
304 dependencies: vec![],
305 required_telltale_version: Some("0.5.0".to_string()),
306 priority: Some(110),
307 overview: Some(
308 "Adds Aura-specific annotations for capabilities and flow control".to_string(),
309 ),
310 syntax_guide: Some(
311 "Use Role[annotation=value] syntax in communications".to_string(),
312 ),
313 use_cases: Some(vec![
314 "Capability verification".to_string(),
315 "Flow control".to_string(),
316 ]),
317 keywords: Some(vec![
318 "aura".to_string(),
319 "capabilities".to_string(),
320 "annotations".to_string(),
321 ]),
322 },
323 Box::new(AuraAnnotationExtension),
324 )?;
325
326 discovery.create_registry(&["timeout".to_string(), "aura_annotations".to_string()])
327 }
328
329 pub fn for_third_party() -> Self {
331 Self::new()
332 }
333
334 pub fn with_builtin_only() -> Result<ExtensionRegistry, ParseError> {
336 let mut discovery = Self::new();
337
338 discovery.register_extension(
339 ExtensionMetadata {
340 name: "timeout".to_string(),
341 version: "0.5.0".to_string(),
342 description: "Timeout support for choreographic protocols".to_string(),
343 author: "Telltale Team".to_string(),
344 dependencies: vec![],
345 required_telltale_version: Some("0.5.0".to_string()),
346 priority: Some(100),
347 overview: Some("Adds timeout semantics to choreographic protocols".to_string()),
348 syntax_guide: Some("Use `timeout(duration) { ... }` syntax".to_string()),
349 use_cases: Some(vec![
350 "Network protocols".to_string(),
351 "Real-time systems".to_string(),
352 ]),
353 keywords: Some(vec!["timeout".to_string(), "timing".to_string()]),
354 },
355 Box::new(super::timeout::TimeoutGrammarExtension),
356 )?;
357
358 discovery.create_registry(&["timeout".to_string()])
359 }
360
361 pub fn validate_metadata(metadata: &ExtensionMetadata) -> Result<(), ParseError> {
363 if metadata.name.is_empty() {
364 return Err(ParseError::InvalidSyntax {
365 details: "Extension name cannot be empty".to_string(),
366 });
367 }
368
369 if metadata.version.is_empty() {
370 return Err(ParseError::InvalidSyntax {
371 details: "Extension version cannot be empty".to_string(),
372 });
373 }
374
375 if metadata.name.contains(' ') {
376 return Err(ParseError::InvalidSyntax {
377 details: "Extension name cannot contain spaces".to_string(),
378 });
379 }
380
381 Ok(())
382 }
383
384 pub fn list_extensions(&self) -> Vec<&ExtensionMetadata> {
386 self.discovered_extensions
387 .values()
388 .map(|pkg| &pkg.metadata)
389 .collect()
390 }
391
392 pub fn find_by_author(&self, author: &str) -> Vec<&ExtensionMetadata> {
394 self.discovered_extensions
395 .values()
396 .filter_map(|pkg| {
397 if pkg.metadata.author == author {
398 Some(&pkg.metadata)
399 } else {
400 None
401 }
402 })
403 .collect()
404 }
405}
406
407#[derive(Debug, Clone)]
412struct ClonableExtensionWrapper {
413 #[allow(dead_code)] id: String,
415 #[allow(dead_code)] rules: Vec<String>,
417 #[allow(dead_code)] grammar: String,
419 priority: u32,
420}
421
422impl ClonableExtensionWrapper {
423 fn new(extension: &dyn GrammarExtension, metadata: &ExtensionMetadata) -> Self {
424 Self {
425 id: metadata.name.clone(),
426 rules: extension
427 .statement_rules()
428 .iter()
429 .map(|s| (*s).to_string())
430 .collect(),
431 grammar: extension.grammar_rules().to_string(),
432 priority: metadata.priority.unwrap_or(extension.priority()),
433 }
434 }
435}
436
437impl GrammarExtension for ClonableExtensionWrapper {
438 fn grammar_rules(&self) -> &'static str {
439 ""
441 }
442
443 fn statement_rules(&self) -> Vec<&'static str> {
444 vec![]
446 }
447
448 fn priority(&self) -> u32 {
449 self.priority
450 }
451
452 fn extension_id(&self) -> &'static str {
453 "cloneable_wrapper"
455 }
456}
457
458#[derive(Debug, Clone)]
466struct MetadataOnlyExtension {
467 #[allow(dead_code)] name: String,
469 priority: u32,
470}
471
472impl MetadataOnlyExtension {
473 fn new(metadata: &ExtensionMetadata) -> Self {
474 Self {
475 name: metadata.name.clone(),
476 priority: metadata.priority.unwrap_or(100),
477 }
478 }
479}
480
481impl GrammarExtension for MetadataOnlyExtension {
482 fn grammar_rules(&self) -> &'static str {
483 ""
484 }
485
486 fn statement_rules(&self) -> Vec<&'static str> {
487 vec![]
488 }
489
490 fn priority(&self) -> u32 {
491 self.priority
492 }
493
494 fn extension_id(&self) -> &'static str {
495 "placeholder"
496 }
497}
498
499#[derive(Debug, Clone)]
501struct AuraAnnotationExtension;
502
503impl GrammarExtension for AuraAnnotationExtension {
504 fn grammar_rules(&self) -> &'static str {
505 r#"
506aura_annotations_stmt = { role_ref ~ "[" ~ aura_annotations_list ~ "]" ~ "->" ~ role_ref ~ ":" ~ message ~ ";" }
507aura_annotations_list = { aura_annotations_item ~ ("," ~ aura_annotations_item)* }
508aura_annotations_item = { ident ~ "=" ~ annotation_value }
509"#
510 }
511
512 fn statement_rules(&self) -> Vec<&'static str> {
513 vec!["aura_annotations_stmt"]
514 }
515
516 fn priority(&self) -> u32 {
517 110
518 }
519
520 fn extension_id(&self) -> &'static str {
521 "aura_annotations"
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_extension_discovery() {
531 let mut discovery = ExtensionDiscovery::new();
532
533 let metadata = ExtensionMetadata {
534 name: "test_ext".to_string(),
535 version: "1.0.0".to_string(),
536 description: "Test extension".to_string(),
537 author: "Test Author".to_string(),
538 dependencies: vec![],
539 required_telltale_version: Some("0.5.0".to_string()),
540 priority: Some(100),
541 overview: None,
542 syntax_guide: None,
543 use_cases: None,
544 keywords: None,
545 };
546
547 let extension = Box::new(MetadataOnlyExtension::new(&metadata));
548 assert!(discovery.register_extension(metadata, extension).is_ok());
549 assert!(discovery.has_extension("test_ext"));
550 }
551
552 #[test]
553 fn test_dependency_resolution() {
554 let mut discovery = ExtensionDiscovery::new();
555
556 let base_metadata = ExtensionMetadata {
558 name: "base".to_string(),
559 version: "1.0.0".to_string(),
560 description: "Base extension".to_string(),
561 author: "Test".to_string(),
562 dependencies: vec![],
563 required_telltale_version: Some("0.5.0".to_string()),
564 priority: Some(100),
565 overview: None,
566 syntax_guide: None,
567 use_cases: None,
568 keywords: None,
569 };
570 discovery
571 .register_extension(
572 base_metadata.clone(),
573 Box::new(MetadataOnlyExtension::new(&base_metadata)),
574 )
575 .unwrap();
576
577 let dep_metadata = ExtensionMetadata {
579 name: "dependent".to_string(),
580 version: "1.0.0".to_string(),
581 description: "Dependent extension".to_string(),
582 author: "Test".to_string(),
583 dependencies: vec!["base".to_string()],
584 required_telltale_version: Some("0.5.0".to_string()),
585 priority: Some(100),
586 overview: None,
587 syntax_guide: None,
588 use_cases: None,
589 keywords: None,
590 };
591 discovery
592 .register_extension(
593 dep_metadata.clone(),
594 Box::new(MetadataOnlyExtension::new(&dep_metadata)),
595 )
596 .unwrap();
597
598 let resolved = discovery
599 .resolve_dependencies(&["dependent".to_string()])
600 .unwrap();
601 assert!(resolved.contains(&"base".to_string()));
602 assert!(resolved.contains(&"dependent".to_string()));
603 }
604}