mockforge_plugin_core/manifest/
models.rs1use crate::{PluginCapabilities, PluginError, PluginId, PluginVersion, Result};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use super::schema::ConfigSchema;
11use semver;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct PluginManifest {
19 pub manifest_version: String,
21 pub plugin: PluginInfo,
23 pub capabilities: PluginCapabilities,
25 pub dependencies: Vec<PluginDependency>,
27 pub config_schema: Option<ConfigSchema>,
29 pub metadata: HashMap<String, serde_json::Value>,
31}
32
33impl PluginManifest {
34 pub fn new(plugin: PluginInfo) -> Self {
36 Self {
37 manifest_version: "1.0".to_string(),
38 plugin,
39 capabilities: PluginCapabilities::default(),
40 dependencies: Vec::new(),
41 config_schema: None,
42 metadata: HashMap::new(),
43 }
44 }
45
46 pub fn validate(&self) -> Result<()> {
48 if self.manifest_version != "1.0" {
50 return Err(PluginError::config_error(&format!(
51 "Unsupported manifest version: {}",
52 self.manifest_version
53 )));
54 }
55
56 self.plugin.validate()?;
58
59 for dep in &self.dependencies {
61 dep.validate()?;
62 }
63
64 if let Some(schema) = &self.config_schema {
66 schema.validate()?;
67 }
68
69 Ok(())
70 }
71
72 pub fn id(&self) -> &PluginId {
74 &self.plugin.id
75 }
76
77 pub fn version(&self) -> &PluginVersion {
79 &self.plugin.version
80 }
81
82 pub fn supports_type(&self, plugin_type: &str) -> bool {
84 self.plugin.types.contains(&plugin_type.to_string())
85 }
86
87 pub fn display_name(&self) -> &str {
89 &self.plugin.name
90 }
91
92 pub fn description(&self) -> Option<&str> {
94 self.plugin.description.as_deref()
95 }
96
97 pub fn author(&self) -> Option<&PluginAuthor> {
99 self.plugin.author.as_ref()
100 }
101
102 pub fn has_capability(&self, capability: &str) -> bool {
104 self.capabilities.has_capability(capability)
105 }
106
107 pub fn types(&self) -> &[String] {
109 &self.plugin.types
110 }
111
112 pub fn dependencies(&self) -> &[PluginDependency] {
114 &self.dependencies
115 }
116
117 pub fn requires_config(&self) -> bool {
119 self.config_schema.is_some()
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct PluginInfo {
126 pub id: PluginId,
128 pub name: String,
130 pub version: PluginVersion,
132 pub description: Option<String>,
134 pub author: Option<PluginAuthor>,
136 pub types: Vec<String>,
138 pub homepage: Option<String>,
140 pub repository: Option<String>,
142 pub license: Option<String>,
144 pub keywords: Vec<String>,
146}
147
148impl PluginInfo {
149 pub fn new(id: PluginId, name: String, version: PluginVersion) -> Self {
151 Self {
152 id,
153 name,
154 version,
155 description: None,
156 author: None,
157 types: Vec::new(),
158 homepage: None,
159 repository: None,
160 license: None,
161 keywords: Vec::new(),
162 }
163 }
164
165 pub fn validate(&self) -> Result<()> {
167 if self.name.trim().is_empty() {
168 return Err(PluginError::config_error("Plugin name cannot be empty"));
169 }
170
171 if self.types.is_empty() {
172 return Err(PluginError::config_error("Plugin must specify at least one type"));
173 }
174
175 for plugin_type in &self.types {
176 if plugin_type.trim().is_empty() {
177 return Err(PluginError::config_error("Plugin type cannot be empty"));
178 }
179 }
180
181 Ok(())
182 }
183
184 pub fn matches_keywords(&self, keywords: &[String]) -> bool {
186 if keywords.is_empty() {
187 return true;
188 }
189
190 keywords.iter().any(|keyword| {
191 self.keywords.iter().any(|plugin_keyword| {
192 plugin_keyword.to_lowercase().contains(&keyword.to_lowercase())
193 })
194 })
195 }
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PluginAuthor {
201 pub name: String,
203 pub email: Option<String>,
205 pub url: Option<String>,
207}
208
209impl PluginAuthor {
210 pub fn new(name: String) -> Self {
212 Self {
213 name,
214 email: None,
215 url: None,
216 }
217 }
218
219 pub fn validate(&self) -> Result<()> {
221 if self.name.trim().is_empty() {
222 return Err(PluginError::config_error("Author name cannot be empty"));
223 }
224 Ok(())
225 }
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct PluginDependency {
231 pub id: PluginId,
233 pub version: String,
235 pub optional: bool,
237}
238
239impl PluginDependency {
240 pub fn new(id: PluginId, version: String) -> Self {
242 Self {
243 id,
244 version,
245 optional: false,
246 }
247 }
248
249 pub fn optional(id: PluginId, version: String) -> Self {
251 Self {
252 id,
253 version,
254 optional: true,
255 }
256 }
257
258 pub fn validate(&self) -> Result<()> {
260 if self.version.trim().is_empty() {
261 return Err(PluginError::config_error(&format!(
262 "Dependency {} version cannot be empty",
263 self.id
264 )));
265 }
266 Ok(())
267 }
268
269 pub fn satisfies_version(&self, version: &PluginVersion) -> bool {
271 if self.version == "*" {
273 return true;
274 }
275
276 let req_str = if self.version.starts_with(|c: char| c.is_ascii_digit()) {
279 format!("={}", self.version)
280 } else {
281 self.version.clone()
282 };
283
284 let req = match semver::VersionReq::parse(&req_str) {
286 Ok(req) => req,
287 Err(_) => return false, };
289
290 let semver_version = match version.to_semver() {
292 Ok(v) => v,
293 Err(_) => return false, };
295
296 req.matches(&semver_version)
297 }
298}
299
300#[cfg(test)]
301mod tests {
302
303 #[test]
304 fn test_module_compiles() {
305 }
307}