mockforge_plugin_sdk/
builders.rs

1//! Builder patterns for plugin manifests and configurations
2//!
3//! This module provides fluent builder APIs for creating plugin manifests,
4//! making it easier to configure plugins without dealing with raw structs.
5
6use mockforge_plugin_core::*;
7
8/// Builder for plugin manifests
9///
10/// # Example
11///
12/// ```rust
13/// use mockforge_plugin_sdk::builders::ManifestBuilder;
14///
15/// let manifest = ManifestBuilder::new("my-plugin", "1.0.0")
16///     .name("My Plugin")
17///     .description("A custom plugin for authentication")
18///     .author("Your Name", "your.email@example.com")
19///     .capability("network")
20///     .capability("filesystem.read")
21///     .build();
22/// ```
23pub struct ManifestBuilder {
24    manifest: PluginManifest,
25}
26
27impl ManifestBuilder {
28    /// Create a new manifest builder
29    pub fn new(id: &str, version: &str) -> Self {
30        let version = PluginVersion::parse(version).unwrap_or_else(|_| PluginVersion::new(0, 1, 0));
31        let info = PluginInfo {
32            id: PluginId::new(id),
33            version,
34            name: String::new(),
35            description: String::new(),
36            author: PluginAuthor::new("Unknown"),
37        };
38
39        Self {
40            manifest: PluginManifest::new(info),
41        }
42    }
43
44    /// Set plugin name
45    pub fn name(mut self, name: &str) -> Self {
46        self.manifest.info.name = name.to_string();
47        self
48    }
49
50    /// Set plugin description
51    pub fn description(mut self, description: &str) -> Self {
52        self.manifest.info.description = description.to_string();
53        self
54    }
55
56    /// Set plugin author
57    pub fn author(mut self, name: &str, email: &str) -> Self {
58        self.manifest.info.author = PluginAuthor::with_email(name, email);
59        self
60    }
61
62    /// Set plugin author (name only)
63    pub fn author_name(mut self, name: &str) -> Self {
64        self.manifest.info.author = PluginAuthor::new(name);
65        self
66    }
67
68    /// Add a capability
69    ///
70    /// Common capabilities: "network", "filesystem.read", "filesystem.write"
71    pub fn capability(mut self, capability: &str) -> Self {
72        self.manifest.capabilities.push(capability.to_string());
73        self
74    }
75
76    /// Add multiple capabilities
77    pub fn capabilities(mut self, capabilities: &[&str]) -> Self {
78        for cap in capabilities {
79            self.manifest.capabilities.push(cap.to_string());
80        }
81        self
82    }
83
84    /// Add a dependency
85    pub fn dependency(mut self, plugin_id: &str, version: &str) -> Self {
86        if let Ok(parsed_version) = PluginVersion::parse(version) {
87            self.manifest.dependencies.insert(PluginId::new(plugin_id), parsed_version);
88        }
89        self
90    }
91
92    /// Build the manifest
93    pub fn build(self) -> PluginManifest {
94        self.manifest
95    }
96
97    /// Build and save to file
98    pub fn build_and_save(self, path: &str) -> std::result::Result<PluginManifest, std::io::Error> {
99        let manifest = self.manifest;
100        let yaml = serde_yaml::to_string(&manifest)
101            .map_err(|e| std::io::Error::other(format!("YAML error: {}", e)))?;
102        std::fs::write(path, yaml)?;
103        Ok(manifest)
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_manifest_builder() {
113        let manifest = ManifestBuilder::new("test-plugin", "1.0.0")
114            .name("Test Plugin")
115            .description("A test plugin")
116            .author("Test Author", "test@example.com")
117            .capability("network")
118            .capability("filesystem.read")
119            .build();
120
121        assert_eq!(manifest.info.id, PluginId::new("test-plugin"));
122        assert_eq!(manifest.info.name, "Test Plugin");
123        assert_eq!(manifest.info.description, "A test plugin");
124        assert_eq!(manifest.capabilities.len(), 2);
125        assert!(manifest.capabilities.contains(&"network".to_string()));
126        assert!(manifest.capabilities.contains(&"filesystem.read".to_string()));
127    }
128
129    #[test]
130    fn test_manifest_with_dependencies() {
131        let manifest = ManifestBuilder::new("test-plugin", "2.0.0")
132            .name("Test Plugin")
133            .dependency("dep1", "1.0.0")
134            .dependency("dep2", "1.5.0")
135            .build();
136
137        assert_eq!(manifest.dependencies.len(), 2);
138    }
139
140    #[test]
141    fn test_manifest_save() {
142        use tempfile::NamedTempFile;
143
144        let manifest = ManifestBuilder::new("test-plugin", "1.0.0")
145            .name("Test Plugin")
146            .description("A test plugin")
147            .author("Test", "test@example.com")
148            .build();
149
150        let temp_file = NamedTempFile::new().unwrap();
151        let path = temp_file.path().to_str().unwrap();
152
153        let yaml = serde_yaml::to_string(&manifest).unwrap();
154        std::fs::write(path, yaml).unwrap();
155
156        let loaded = PluginManifest::from_file(path).unwrap();
157        assert_eq!(loaded.info.id, manifest.info.id);
158        assert_eq!(loaded.info.name, manifest.info.name);
159    }
160}