1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub enum ABIVersion {
15 V1,
16 V2,
17 V3,
18}
19
20impl ABIVersion {
21 pub fn parse(version_str: &str) -> anyhow::Result<Self> {
23 match version_str {
24 "1.0" | "1" => Ok(ABIVersion::V1),
25 "2.0" | "2" => Ok(ABIVersion::V2),
26 "3.0" | "3" => Ok(ABIVersion::V3),
27 _ => anyhow::bail!("Unknown ABI version: {}", version_str),
28 }
29 }
30
31 pub fn as_str(&self) -> &'static str {
33 match self {
34 ABIVersion::V1 => "1.0",
35 ABIVersion::V2 => "2.0",
36 ABIVersion::V3 => "3.0",
37 }
38 }
39
40 pub fn is_compatible_with(&self, other: &ABIVersion) -> bool {
42 self == other
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
50pub enum MaturityLevel {
51 Alpha,
52 Beta,
53 ReleaseCandidate,
54 Stable,
55 Deprecated,
56}
57
58impl MaturityLevel {
59 pub fn parse(level_str: &str) -> anyhow::Result<Self> {
61 match level_str.to_lowercase().as_str() {
62 "alpha" => Ok(MaturityLevel::Alpha),
63 "beta" => Ok(MaturityLevel::Beta),
64 "rc" | "releasecandidate" => Ok(MaturityLevel::ReleaseCandidate),
65 "stable" | "production" => Ok(MaturityLevel::Stable),
66 "deprecated" => Ok(MaturityLevel::Deprecated),
67 _ => anyhow::bail!("Unknown maturity level: {}", level_str),
68 }
69 }
70
71 pub fn as_str(&self) -> &'static str {
73 match self {
74 MaturityLevel::Alpha => "alpha",
75 MaturityLevel::Beta => "beta",
76 MaturityLevel::ReleaseCandidate => "rc",
77 MaturityLevel::Stable => "stable",
78 MaturityLevel::Deprecated => "deprecated",
79 }
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum PluginCategory {
86 Utility,
87 Database,
88 Network,
89 Storage,
90 Security,
91 Monitoring,
92 Payment,
93 Integration,
94 Development,
95 Other,
96}
97
98impl PluginCategory {
99 pub fn parse(category_str: &str) -> anyhow::Result<Self> {
101 match category_str.to_lowercase().as_str() {
102 "utility" => Ok(PluginCategory::Utility),
103 "database" => Ok(PluginCategory::Database),
104 "network" => Ok(PluginCategory::Network),
105 "storage" => Ok(PluginCategory::Storage),
106 "security" => Ok(PluginCategory::Security),
107 "monitoring" => Ok(PluginCategory::Monitoring),
108 "payment" => Ok(PluginCategory::Payment),
109 "integration" => Ok(PluginCategory::Integration),
110 "development" => Ok(PluginCategory::Development),
111 "other" => Ok(PluginCategory::Other),
112 _ => anyhow::bail!("Unknown plugin category: {}", category_str),
113 }
114 }
115
116 pub fn as_str(&self) -> &'static str {
118 match self {
119 PluginCategory::Utility => "utility",
120 PluginCategory::Database => "database",
121 PluginCategory::Network => "network",
122 PluginCategory::Storage => "storage",
123 PluginCategory::Security => "security",
124 PluginCategory::Monitoring => "monitoring",
125 PluginCategory::Payment => "payment",
126 PluginCategory::Integration => "integration",
127 PluginCategory::Development => "development",
128 PluginCategory::Other => "other",
129 }
130 }
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize)]
135pub struct ABICompatibleInfo {
136 pub name: String,
138
139 pub version: String,
141
142 pub abi_version: ABIVersion,
144
145 pub skylet_version_min: Option<String>,
147
148 pub skylet_version_max: Option<String>,
150
151 pub maturity_level: MaturityLevel,
153
154 pub category: PluginCategory,
156
157 pub author: Option<String>,
159
160 pub license: Option<String>,
162
163 pub description: Option<String>,
165
166 pub capabilities: Vec<CapabilityInfo>,
168
169 pub dependencies: Vec<DependencyInfo>,
171
172 pub resources: ResourceRequirements,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize)]
178pub struct CapabilityInfo {
179 pub name: String,
181
182 pub description: Option<String>,
184
185 pub required_permission: Option<String>,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct DependencyInfo {
192 pub name: String,
194
195 pub version_range: String,
197
198 pub required: bool,
200
201 pub service_type: Option<String>,
203}
204
205#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ResourceRequirements {
208 pub min_cpu_cores: u32,
210
211 pub max_cpu_cores: u32,
213
214 pub min_memory_mb: u32,
216
217 pub max_memory_mb: u32,
219
220 pub min_disk_mb: u32,
222
223 pub max_disk_mb: u32,
225
226 pub requires_gpu: bool,
228}
229
230impl Default for ResourceRequirements {
231 fn default() -> Self {
232 Self {
233 min_cpu_cores: 1,
234 max_cpu_cores: 4,
235 min_memory_mb: 256,
236 max_memory_mb: 1024,
237 min_disk_mb: 100,
238 max_disk_mb: 1024,
239 requires_gpu: false,
240 }
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct ABIValidationResult {
247 pub is_compatible: bool,
249
250 pub warnings: Vec<String>,
252
253 pub errors: Vec<String>,
255
256 pub recommendations: Vec<String>,
258}
259
260impl ABIValidationResult {
261 pub fn new(is_compatible: bool) -> Self {
263 Self {
264 is_compatible,
265 warnings: Vec::new(),
266 errors: Vec::new(),
267 recommendations: Vec::new(),
268 }
269 }
270
271 pub fn add_warning(&mut self, message: String) {
273 self.warnings.push(message);
274 }
275
276 pub fn add_error(&mut self, message: String) {
278 self.errors.push(message);
279 self.is_compatible = false;
280 }
281
282 pub fn add_recommendation(&mut self, message: String) {
284 self.recommendations.push(message);
285 }
286
287 pub fn passed(&self) -> bool {
289 self.is_compatible && self.errors.is_empty()
290 }
291}
292
293pub struct ABIValidator;
295
296impl ABIValidator {
297 pub fn validate(info: &ABICompatibleInfo) -> ABIValidationResult {
299 let mut result = ABIValidationResult::new(true);
300
301 if info.abi_version == ABIVersion::V1 {
303 result.add_warning(
304 "ABI v1.0 is deprecated. Consider upgrading to v2.0 for new features.".to_string(),
305 );
306 } else if info.abi_version == ABIVersion::V3 {
307 result.add_warning(
308 "ABI v3.0 is experimental. Some compatibility issues may occur.".to_string(),
309 );
310 }
311
312 if info.maturity_level == MaturityLevel::Alpha {
314 result.add_warning("Plugin is marked as alpha. It may not be stable.".to_string());
315 } else if info.maturity_level == MaturityLevel::Deprecated {
316 result.add_error("Plugin is marked as deprecated.".to_string());
317 }
318
319 if info.capabilities.is_empty() {
321 result.add_recommendation(
322 "Plugin declares no capabilities. Add at least one capability for discoverability."
323 .to_string(),
324 );
325 }
326
327 if info.resources.min_cpu_cores > info.resources.max_cpu_cores {
329 result
330 .add_error("Resource requirement error: min_cpu_cores > max_cpu_cores".to_string());
331 }
332 if info.resources.min_memory_mb > info.resources.max_memory_mb {
333 result
334 .add_error("Resource requirement error: min_memory_mb > max_memory_mb".to_string());
335 }
336 if info.resources.min_disk_mb > info.resources.max_disk_mb {
337 result.add_error("Resource requirement error: min_disk_mb > max_disk_mb".to_string());
338 }
339
340 for dep in &info.dependencies {
342 if dep.name.is_empty() {
343 result.add_error("Dependency with empty name found.".to_string());
344 }
345 if dep.version_range.is_empty() {
346 result.add_error(format!(
347 "Dependency '{}' has empty version range.",
348 dep.name
349 ));
350 }
351 }
352
353 if info.license.is_none() {
355 result.add_recommendation(
356 "No license specified. Add a license for registry publishing.".to_string(),
357 );
358 }
359
360 if info.author.is_none() {
362 result.add_warning("No author specified.".to_string());
363 }
364
365 result
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_abi_version_parse() {
375 assert_eq!(ABIVersion::parse("1.0").unwrap(), ABIVersion::V1);
376 assert_eq!(ABIVersion::parse("2.0").unwrap(), ABIVersion::V2);
377 assert_eq!(ABIVersion::parse("3.0").unwrap(), ABIVersion::V3);
378 assert_eq!(ABIVersion::parse("2").unwrap(), ABIVersion::V2);
379 }
380
381 #[test]
382 fn test_abi_version_as_str() {
383 assert_eq!(ABIVersion::V1.as_str(), "1.0");
384 assert_eq!(ABIVersion::V2.as_str(), "2.0");
385 assert_eq!(ABIVersion::V3.as_str(), "3.0");
386 }
387
388 #[test]
389 fn test_abi_version_compatibility() {
390 assert!(ABIVersion::V2.is_compatible_with(&ABIVersion::V2));
391 assert!(!ABIVersion::V2.is_compatible_with(&ABIVersion::V1));
392 }
393
394 #[test]
395 fn test_maturity_level_parse() {
396 assert_eq!(MaturityLevel::parse("alpha").unwrap(), MaturityLevel::Alpha);
397 assert_eq!(
398 MaturityLevel::parse("stable").unwrap(),
399 MaturityLevel::Stable
400 );
401 assert_eq!(
402 MaturityLevel::parse("production").unwrap(),
403 MaturityLevel::Stable
404 );
405 }
406
407 #[test]
408 fn test_plugin_category_parse() {
409 assert_eq!(
410 PluginCategory::parse("database").unwrap(),
411 PluginCategory::Database
412 );
413 assert_eq!(
414 PluginCategory::parse("security").unwrap(),
415 PluginCategory::Security
416 );
417 }
418
419 #[test]
420 fn test_resource_requirements_default() {
421 let res = ResourceRequirements::default();
422 assert_eq!(res.min_cpu_cores, 1);
423 assert_eq!(res.min_memory_mb, 256);
424 }
425
426 #[test]
427 fn test_abi_validation_basic() {
428 let info = ABICompatibleInfo {
429 name: "test-plugin".to_string(),
430 version: "1.0.0".to_string(),
431 abi_version: ABIVersion::V2,
432 skylet_version_min: None,
433 skylet_version_max: None,
434 maturity_level: MaturityLevel::Stable,
435 category: PluginCategory::Utility,
436 author: Some("Test Author".to_string()),
437 license: Some("MIT".to_string()),
438 description: Some("Test plugin".to_string()),
439 capabilities: vec![],
440 dependencies: vec![],
441 resources: ResourceRequirements::default(),
442 };
443
444 let result = ABIValidator::validate(&info);
445 assert!(result.is_compatible);
446 assert!(
449 !result.errors.is_empty()
450 || !result.warnings.is_empty()
451 || !result.recommendations.is_empty()
452 );
453 }
454
455 #[test]
456 fn test_abi_validation_errors() {
457 let mut resources = ResourceRequirements::default();
458 resources.min_cpu_cores = 10;
459 resources.max_cpu_cores = 2; let info = ABICompatibleInfo {
462 name: "test-plugin".to_string(),
463 version: "1.0.0".to_string(),
464 abi_version: ABIVersion::V2,
465 skylet_version_min: None,
466 skylet_version_max: None,
467 maturity_level: MaturityLevel::Stable,
468 category: PluginCategory::Utility,
469 author: None,
470 license: None,
471 description: None,
472 capabilities: vec![],
473 dependencies: vec![],
474 resources,
475 };
476
477 let result = ABIValidator::validate(&info);
478 assert!(!result.is_compatible);
479 assert!(!result.errors.is_empty());
480 }
481
482 #[test]
483 fn test_abi_validation_deprecated() {
484 let info = ABICompatibleInfo {
485 name: "test-plugin".to_string(),
486 version: "1.0.0".to_string(),
487 abi_version: ABIVersion::V2,
488 skylet_version_min: None,
489 skylet_version_max: None,
490 maturity_level: MaturityLevel::Deprecated,
491 category: PluginCategory::Utility,
492 author: None,
493 license: None,
494 description: None,
495 capabilities: vec![],
496 dependencies: vec![],
497 resources: ResourceRequirements::default(),
498 };
499
500 let result = ABIValidator::validate(&info);
501 assert!(!result.is_compatible);
502 assert!(result.errors.iter().any(|e| e.contains("deprecated")));
503 }
504}