1use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct OptionalDependency {
17 pub name: String,
18 pub version: String,
19 pub optional: bool,
20 pub description: Option<String>,
21 pub default_enabled: bool,
22 pub platforms: Vec<String>, pub features: Vec<String>, }
25
26impl OptionalDependency {
27 pub fn new(name: &str, version: &str) -> Self {
28 Self {
29 name: name.to_string(),
30 version: version.to_string(),
31 optional: true,
32 description: None,
33 default_enabled: false,
34 platforms: vec!["all".to_string()],
35 features: Vec::new(),
36 }
37 }
38
39 pub fn required(mut self) -> Self {
40 self.optional = false;
41 self
42 }
43
44 pub fn with_description(mut self, desc: &str) -> Self {
45 self.description = Some(desc.to_string());
46 self
47 }
48
49 pub fn with_default_enabled(mut self) -> Self {
50 self.default_enabled = true;
51 self
52 }
53
54 pub fn with_platforms(mut self, platforms: Vec<&str>) -> Self {
55 self.platforms = platforms.iter().map(|p| p.to_string()).collect();
56 self
57 }
58
59 pub fn with_features(mut self, features: Vec<&str>) -> Self {
60 self.features = features.iter().map(|f| f.to_string()).collect();
61 self
62 }
63
64 pub fn is_platform_applicable(&self, current_platform: &str) -> bool {
65 if self.platforms.contains(&"all".to_string()) {
66 return true;
67 }
68
69 let is_unix_like = cfg!(unix);
71 if current_platform == "unix" && is_unix_like {
72 return true;
73 }
74
75 self.platforms.contains(¤t_platform.to_string())
76 }
77
78 pub fn is_enabled(&self, enabled_features: &[String]) -> bool {
79 if self.optional && self.default_enabled {
80 return true;
81 }
82
83 for feature in &self.features {
85 if enabled_features.contains(feature) {
86 return true;
87 }
88 }
89
90 false
91 }
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct FeatureGate {
97 pub name: String,
98 pub description: Option<String>,
99 pub enabled: bool,
100 pub dependencies: Vec<String>, }
102
103impl FeatureGate {
104 pub fn new(name: &str) -> Self {
105 Self {
106 name: name.to_string(),
107 description: None,
108 enabled: false,
109 dependencies: Vec::new(),
110 }
111 }
112
113 pub fn with_description(mut self, desc: &str) -> Self {
114 self.description = Some(desc.to_string());
115 self
116 }
117
118 pub fn with_dependencies(mut self, deps: Vec<&str>) -> Self {
119 self.dependencies = deps.iter().map(|d| d.to_string()).collect();
120 self
121 }
122
123 pub fn enable(mut self) -> Self {
124 self.enabled = true;
125 self
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct PlatformSpecific {
132 pub dependency: String,
133 pub version: String,
134 pub platforms: Vec<String>, pub required: bool,
136}
137
138impl PlatformSpecific {
139 pub fn new(dependency: &str, version: &str, platforms: Vec<&str>) -> Self {
140 Self {
141 dependency: dependency.to_string(),
142 version: version.to_string(),
143 platforms: platforms.iter().map(|p| p.to_string()).collect(),
144 required: false,
145 }
146 }
147
148 pub fn required(mut self) -> Self {
149 self.required = true;
150 self
151 }
152
153 pub fn matches_current_platform(&self) -> bool {
154 for platform in &self.platforms {
155 match platform.as_str() {
156 "linux" => {
157 if cfg!(target_os = "linux") {
158 return true;
159 }
160 }
161 "macos" => {
162 if cfg!(target_os = "macos") {
163 return true;
164 }
165 }
166 "windows" => {
167 if cfg!(target_os = "windows") {
168 return true;
169 }
170 }
171 "unix" => {
172 if cfg!(unix) {
173 return true;
174 }
175 }
176 "all" => return true,
177 _ => {}
178 }
179 }
180 false
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct DependencyCondition {
187 pub condition_type: ConditionType,
188 pub expression: String, }
190
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
192#[serde(rename_all = "lowercase")]
193pub enum ConditionType {
194 Feature,
195 Platform,
196 And,
197 Or,
198 Not,
199}
200
201impl ConditionType {
202 pub fn as_str(&self) -> &'static str {
203 match self {
204 ConditionType::Feature => "feature",
205 ConditionType::Platform => "platform",
206 ConditionType::And => "and",
207 ConditionType::Or => "or",
208 ConditionType::Not => "not",
209 }
210 }
211}
212
213impl DependencyCondition {
214 pub fn new(cond_type: ConditionType, expression: &str) -> Self {
215 Self {
216 condition_type: cond_type,
217 expression: expression.to_string(),
218 }
219 }
220
221 pub fn is_valid_syntax(&self) -> bool {
222 !self.expression.is_empty() && self.expression.len() < 256
223 }
224
225 pub fn evaluate(
226 &self,
227 enabled_features: &[String],
228 current_platform: &str,
229 ) -> Result<bool, String> {
230 if !self.is_valid_syntax() {
231 return Err("Invalid condition syntax".to_string());
232 }
233
234 match self.condition_type {
235 ConditionType::Feature => {
236 let feature_name = self.expression.trim();
237 Ok(enabled_features.contains(&feature_name.to_string()))
238 }
239 ConditionType::Platform => {
240 Ok(self.expression == current_platform || self.expression == "all")
241 }
242 ConditionType::And => {
243 Ok(self.expression.contains(current_platform))
245 }
246 ConditionType::Or => {
247 Ok(true)
249 }
250 ConditionType::Not => {
251 Ok(!enabled_features.contains(&self.expression.to_string()))
253 }
254 }
255 }
256}
257
258pub struct OptionalDependencyManager;
260
261impl OptionalDependencyManager {
262 pub fn evaluate_conditions(
264 dependencies: &[OptionalDependency],
265 enabled_features: &[String],
266 current_platform: &str,
267 ) -> Vec<OptionalDependency> {
268 dependencies
269 .iter()
270 .filter(|dep| {
271 dep.is_platform_applicable(current_platform) && dep.is_enabled(enabled_features)
272 })
273 .cloned()
274 .collect()
275 }
276
277 pub fn filter_by_platform(
279 dependencies: &[OptionalDependency],
280 platform: &str,
281 ) -> Vec<OptionalDependency> {
282 dependencies
283 .iter()
284 .filter(|dep| dep.is_platform_applicable(platform))
285 .cloned()
286 .collect()
287 }
288
289 pub fn resolve_features(features: &[FeatureGate]) -> HashMap<String, Vec<String>> {
291 let mut result = HashMap::new();
292
293 for feature in features {
294 if feature.enabled {
295 result.insert(feature.name.clone(), feature.dependencies.clone());
296 }
297 }
298
299 result
300 }
301
302 pub fn validate_conditions(conditions: &[DependencyCondition]) -> Result<(), Vec<String>> {
304 let mut errors = Vec::new();
305
306 for (idx, cond) in conditions.iter().enumerate() {
307 if !cond.is_valid_syntax() {
308 errors.push(format!(
309 "Invalid condition at index {}: {}",
310 idx, cond.expression
311 ));
312 }
313 }
314
315 if errors.is_empty() {
316 Ok(())
317 } else {
318 Err(errors)
319 }
320 }
321
322 pub fn calculate_optional_count(
324 dependencies: &[OptionalDependency],
325 enabled_features: &[String],
326 ) -> (usize, usize) {
327 let total_optional = dependencies.iter().filter(|d| d.optional).count();
328 let enabled = Self::evaluate_conditions(dependencies, enabled_features, "all")
329 .iter()
330 .filter(|d| d.optional)
331 .count();
332
333 (enabled, total_optional)
334 }
335
336 pub fn get_for_feature(
338 dependencies: &[OptionalDependency],
339 feature: &str,
340 ) -> Vec<OptionalDependency> {
341 dependencies
342 .iter()
343 .filter(|dep| dep.features.contains(&feature.to_string()))
344 .cloned()
345 .collect()
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[test]
354 fn test_optional_dependency_creation() {
355 let dep = OptionalDependency::new("plugin-a", "1.0.0");
356 assert_eq!(dep.name, "plugin-a");
357 assert!(dep.optional);
358 assert!(!dep.default_enabled);
359 }
360
361 #[test]
362 fn test_optional_dependency_required() {
363 let dep = OptionalDependency::new("plugin-a", "1.0.0").required();
364 assert!(!dep.optional);
365 }
366
367 #[test]
368 fn test_optional_dependency_with_description() {
369 let dep =
370 OptionalDependency::new("plugin-a", "1.0.0").with_description("A test dependency");
371 assert!(dep.description.is_some());
372 }
373
374 #[test]
375 fn test_optional_dependency_with_default_enabled() {
376 let dep = OptionalDependency::new("plugin-a", "1.0.0").with_default_enabled();
377 assert!(dep.default_enabled);
378 }
379
380 #[test]
381 fn test_optional_dependency_is_platform_applicable() {
382 let dep =
383 OptionalDependency::new("plugin-a", "1.0.0").with_platforms(vec!["linux", "macos"]);
384 assert!(dep.is_platform_applicable("linux"));
385 assert!(dep.is_platform_applicable("macos"));
386 assert!(!dep.is_platform_applicable("windows"));
387 }
388
389 #[test]
390 fn test_optional_dependency_is_enabled() {
391 let dep = OptionalDependency::new("plugin-a", "1.0.0").with_features(vec!["logging"]);
392 let features = vec!["logging".to_string()];
393 assert!(dep.is_enabled(&features));
394 }
395
396 #[test]
397 fn test_feature_gate_creation() {
398 let gate = FeatureGate::new("logging");
399 assert_eq!(gate.name, "logging");
400 assert!(!gate.enabled);
401 }
402
403 #[test]
404 fn test_feature_gate_enable() {
405 let gate = FeatureGate::new("logging").enable();
406 assert!(gate.enabled);
407 }
408
409 #[test]
410 fn test_feature_gate_with_dependencies() {
411 let gate = FeatureGate::new("logging").with_dependencies(vec!["slog", "serde"]);
412 assert_eq!(gate.dependencies.len(), 2);
413 }
414
415 #[test]
416 fn test_platform_specific_creation() {
417 let ps = PlatformSpecific::new("openssl", "1.1.0", vec!["linux"]);
418 assert_eq!(ps.dependency, "openssl");
419 assert_eq!(ps.platforms.len(), 1);
420 }
421
422 #[test]
423 fn test_platform_specific_required() {
424 let ps = PlatformSpecific::new("openssl", "1.1.0", vec!["linux"]).required();
425 assert!(ps.required);
426 }
427
428 #[test]
429 fn test_condition_type_to_str() {
430 assert_eq!(ConditionType::Feature.as_str(), "feature");
431 assert_eq!(ConditionType::Platform.as_str(), "platform");
432 assert_eq!(ConditionType::And.as_str(), "and");
433 assert_eq!(ConditionType::Or.as_str(), "or");
434 assert_eq!(ConditionType::Not.as_str(), "not");
435 }
436
437 #[test]
438 fn test_dependency_condition_creation() {
439 let cond = DependencyCondition::new(ConditionType::Feature, "logging");
440 assert_eq!(cond.condition_type, ConditionType::Feature);
441 }
442
443 #[test]
444 fn test_dependency_condition_is_valid_syntax() {
445 let cond = DependencyCondition::new(ConditionType::Feature, "logging");
446 assert!(cond.is_valid_syntax());
447 }
448
449 #[test]
450 fn test_dependency_condition_evaluate_feature() {
451 let cond = DependencyCondition::new(ConditionType::Feature, "logging");
452 let features = vec!["logging".to_string()];
453 let result = cond.evaluate(&features, "linux");
454 assert!(result.is_ok());
455 assert!(result.unwrap());
456 }
457
458 #[test]
459 fn test_dependency_condition_evaluate_platform() {
460 let cond = DependencyCondition::new(ConditionType::Platform, "linux");
461 let features = vec![];
462 let result = cond.evaluate(&features, "linux");
463 assert!(result.is_ok());
464 assert!(result.unwrap());
465 }
466
467 #[test]
468 fn test_optional_dependency_manager_evaluate_conditions() {
469 let deps = vec![OptionalDependency::new("plugin-a", "1.0.0")
470 .with_features(vec!["logging"])
471 .with_platforms(vec!["linux"])];
472 let features = vec!["logging".to_string()];
473 let evaluated = OptionalDependencyManager::evaluate_conditions(&deps, &features, "linux");
474 assert_eq!(evaluated.len(), 1);
475 }
476
477 #[test]
478 fn test_optional_dependency_manager_filter_by_platform() {
479 let deps = vec![
480 OptionalDependency::new("plugin-a", "1.0.0").with_platforms(vec!["linux", "macos"]),
481 OptionalDependency::new("plugin-b", "1.0.0").with_platforms(vec!["windows"]),
482 ];
483 let filtered = OptionalDependencyManager::filter_by_platform(&deps, "linux");
484 assert_eq!(filtered.len(), 1);
485 }
486
487 #[test]
488 fn test_optional_dependency_manager_resolve_features() {
489 let features = vec![FeatureGate::new("logging")
490 .enable()
491 .with_dependencies(vec!["slog"])];
492 let resolved = OptionalDependencyManager::resolve_features(&features);
493 assert_eq!(resolved.len(), 1);
494 }
495
496 #[test]
497 fn test_optional_dependency_manager_validate_conditions() {
498 let conditions = vec![
499 DependencyCondition::new(ConditionType::Feature, "logging"),
500 DependencyCondition::new(ConditionType::Platform, "linux"),
501 ];
502 let result = OptionalDependencyManager::validate_conditions(&conditions);
503 assert!(result.is_ok());
504 }
505
506 #[test]
507 fn test_optional_dependency_manager_validate_conditions_invalid() {
508 let conditions = vec![DependencyCondition::new(ConditionType::Feature, "")];
509 let result = OptionalDependencyManager::validate_conditions(&conditions);
510 assert!(result.is_err());
511 }
512
513 #[test]
514 fn test_optional_dependency_manager_calculate_optional_count() {
515 let deps = vec![
516 OptionalDependency::new("plugin-a", "1.0.0").with_features(vec!["logging"]),
517 OptionalDependency::new("plugin-b", "1.0.0").with_features(vec!["metrics"]),
518 ];
519 let features = vec!["logging".to_string()];
520 let (enabled, total) =
521 OptionalDependencyManager::calculate_optional_count(&deps, &features);
522 assert_eq!(enabled, 1);
523 assert_eq!(total, 2);
524 }
525
526 #[test]
527 fn test_optional_dependency_manager_get_for_feature() {
528 let deps = vec![
529 OptionalDependency::new("plugin-a", "1.0.0").with_features(vec!["logging"]),
530 OptionalDependency::new("plugin-b", "1.0.0").with_features(vec!["logging", "metrics"]),
531 OptionalDependency::new("plugin-c", "1.0.0").with_features(vec!["metrics"]),
532 ];
533 let for_logging = OptionalDependencyManager::get_for_feature(&deps, "logging");
534 assert_eq!(for_logging.len(), 2);
535 }
536
537 #[test]
538 fn test_optional_dependency_serialization() {
539 let dep = OptionalDependency::new("plugin-a", "1.0.0");
540 let json = serde_json::to_string(&dep).unwrap();
541 let deserialized: OptionalDependency = serde_json::from_str(&json).unwrap();
542 assert_eq!(deserialized.name, dep.name);
543 }
544
545 #[test]
546 fn test_feature_gate_serialization() {
547 let gate = FeatureGate::new("logging");
548 let json = serde_json::to_string(&gate).unwrap();
549 let deserialized: FeatureGate = serde_json::from_str(&json).unwrap();
550 assert_eq!(deserialized.name, gate.name);
551 }
552
553 #[test]
554 fn test_platform_specific_serialization() {
555 let ps = PlatformSpecific::new("openssl", "1.1.0", vec!["linux"]);
556 let json = serde_json::to_string(&ps).unwrap();
557 let deserialized: PlatformSpecific = serde_json::from_str(&json).unwrap();
558 assert_eq!(deserialized.dependency, ps.dependency);
559 }
560}