1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct UnifyConfig {
8 #[serde(default = "default_include_paths")]
11 pub include_paths: bool,
12
13 #[serde(default)]
16 pub include_renamed: bool,
17
18 #[serde(default)]
23 pub pin_transitives: bool,
24
25 #[serde(default = "default_transitive_host")]
28 pub transitive_host: TransitiveFeatureHost,
29
30 #[serde(default)]
32 pub exclude: Vec<String>,
33
34 #[serde(default)]
36 pub include: Vec<String>,
37
38 #[serde(default = "default_max_backups")]
41 pub max_backups: usize,
42
43 #[serde(default = "default_true")]
47 pub msrv: bool,
48
49 #[serde(default)]
57 pub enforce_msrv_inheritance: bool,
58
59 #[serde(default)]
64 pub msrv_source: MsrvSource,
65
66 #[serde(default = "default_true")]
71 pub prune_dead_features: bool,
72
73 #[serde(default)]
77 pub preserve_features: Vec<String>,
78
79 #[serde(default = "default_true")]
84 pub strict_version_compat: bool,
85
86 #[serde(default)]
91 pub exact_pin_handling: ExactPinHandling,
92
93 #[serde(default)]
97 pub major_version_conflict: MajorVersionConflict,
98
99 #[serde(default = "default_true")]
103 pub detect_unused: bool,
104
105 #[serde(default = "default_true")]
109 pub remove_unused: bool,
110
111 #[serde(default = "default_true")]
117 pub detect_undeclared_features: bool,
118
119 #[serde(default = "default_true")]
124 pub fix_undeclared_features: bool,
125
126 #[serde(default = "default_skip_undeclared_patterns")]
130 pub skip_undeclared_patterns: Vec<String>,
131
132 #[serde(default = "default_true")]
135 pub sort_dependencies: bool,
136}
137
138impl Default for UnifyConfig {
139 fn default() -> Self {
140 Self {
141 include_paths: default_include_paths(),
142 include_renamed: false,
143 pin_transitives: false,
144 transitive_host: default_transitive_host(),
145 exclude: Vec::new(),
146 include: Vec::new(),
147 max_backups: default_max_backups(),
148 msrv: true,
149 enforce_msrv_inheritance: false,
150 msrv_source: MsrvSource::default(),
151 prune_dead_features: true,
152 preserve_features: Vec::new(),
153 strict_version_compat: true,
154 exact_pin_handling: ExactPinHandling::default(),
155 major_version_conflict: MajorVersionConflict::default(),
156 detect_unused: true,
157 remove_unused: true,
158 detect_undeclared_features: true,
159 fix_undeclared_features: true,
160 skip_undeclared_patterns: default_skip_undeclared_patterns(),
161 sort_dependencies: true,
162 }
163 }
164}
165
166impl UnifyConfig {
167 pub fn should_exclude(&self, dep_name: &str) -> bool {
169 self.exclude.iter().any(|e| e == dep_name)
170 }
171
172 pub fn should_include(&self, dep_name: &str) -> bool {
174 self.include.iter().any(|i| i == dep_name)
175 }
176
177 pub fn should_preserve_feature(&self, feature_name: &str) -> bool {
181 self.preserve_features.iter().any(|pattern| {
182 if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
183 glob::Pattern::new(pattern)
185 .map(|p| p.matches(feature_name))
186 .unwrap_or(false)
187 } else {
188 pattern == feature_name
190 }
191 })
192 }
193
194 pub fn should_skip_undeclared_feature(&self, feature_name: &str) -> bool {
198 self.skip_undeclared_patterns.iter().any(|pattern| {
199 if pattern.contains('*') || pattern.contains('?') || pattern.contains('[') {
200 glob::Pattern::new(pattern)
202 .map(|p| p.matches(feature_name))
203 .unwrap_or(false)
204 } else {
205 pattern == feature_name
207 }
208 })
209 }
210
211 pub fn validate(&self, workspace_root: &std::path::Path) -> Result<(), crate::error::ConfigError> {
217 if self.pin_transitives
219 && let TransitiveFeatureHost::Path(p) = &self.transitive_host
220 {
221 if p.contains("..") {
223 return Err(crate::error::ConfigError::InvalidValue {
224 field: "unify.transitive_host".to_string(),
225 message: format!("path '{}' contains '..' traversal, which is not allowed", p),
226 });
227 }
228
229 if std::path::Path::new(p).is_absolute() {
231 return Err(crate::error::ConfigError::InvalidValue {
232 field: "unify.transitive_host".to_string(),
233 message: format!("path '{}' is absolute, must be relative to workspace root", p),
234 });
235 }
236
237 let full_path = workspace_root.join(p);
239 if !full_path.exists() {
240 return Err(crate::error::ConfigError::InvalidValue {
241 field: "unify.transitive_host".to_string(),
242 message: format!("path '{}' does not exist", p),
243 });
244 }
245
246 let cargo_toml = full_path.join("Cargo.toml");
248 if !cargo_toml.exists() {
249 return Err(crate::error::ConfigError::InvalidValue {
250 field: "unify.transitive_host".to_string(),
251 message: format!("path '{}' does not contain a Cargo.toml", p),
252 });
253 }
254 }
255
256 Ok(())
257 }
258}
259
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
266#[serde(rename_all = "lowercase")]
267pub enum MsrvSource {
268 Deps,
273 Workspace,
278 #[default]
284 Max,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
289#[serde(rename_all = "lowercase")]
290pub enum ExactPinHandling {
291 Skip,
293 Preserve,
295 #[default]
297 Warn,
298}
299
300#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
305#[serde(rename_all = "lowercase")]
306pub enum MajorVersionConflict {
307 #[default]
312 Warn,
313 Bump,
319}
320
321#[derive(Debug, Clone, PartialEq, Default)]
323pub enum TransitiveFeatureHost {
324 #[default]
326 Root,
327 Path(String),
329}
330
331impl Serialize for TransitiveFeatureHost {
333 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
334 where
335 S: serde::Serializer,
336 {
337 match self {
338 TransitiveFeatureHost::Root => serializer.serialize_str("root"),
339 TransitiveFeatureHost::Path(path) => serializer.serialize_str(path),
340 }
341 }
342}
343
344impl<'de> Deserialize<'de> for TransitiveFeatureHost {
345 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
346 where
347 D: serde::Deserializer<'de>,
348 {
349 struct TransitiveFeatureHostVisitor;
350
351 impl serde::de::Visitor<'_> for TransitiveFeatureHostVisitor {
352 type Value = TransitiveFeatureHost;
353
354 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
355 formatter.write_str("'root' or a path string")
356 }
357
358 fn visit_str<E>(self, value: &str) -> Result<TransitiveFeatureHost, E>
359 where
360 E: serde::de::Error,
361 {
362 match value {
363 "root" => Ok(TransitiveFeatureHost::Root),
364 path => Ok(TransitiveFeatureHost::Path(path.to_string())),
365 }
366 }
367 }
368
369 deserializer.deserialize_any(TransitiveFeatureHostVisitor)
370 }
371}
372
373fn default_max_backups() -> usize {
376 3
377}
378
379fn default_include_paths() -> bool {
380 true
381}
382
383fn default_transitive_host() -> TransitiveFeatureHost {
384 TransitiveFeatureHost::Root
385}
386
387pub(crate) fn default_true() -> bool {
388 true
389}
390
391fn default_skip_undeclared_patterns() -> Vec<String> {
392 const PATTERNS: &[&str] = &["default", "std", "alloc", "*_backend", "*_impl"];
393 PATTERNS.iter().map(|&s| String::from(s)).collect()
394}
395
396#[test]
399fn test_transitive_feature_host_path() {
400 let toml = r#"
402 include_paths = true
403 include_renamed = false
404 pin_transitives = false
405 transitive_host = "path/to/crate"
406 exclude = []
407 include = []
408 "#;
409
410 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
411 assert_eq!(
412 config.transitive_host,
413 TransitiveFeatureHost::Path("path/to/crate".to_string())
414 );
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_unify_config_defaults() {
423 let config = UnifyConfig::default();
424 assert!(config.include_paths); assert!(!config.include_renamed); assert!(!config.pin_transitives); assert_eq!(config.transitive_host, TransitiveFeatureHost::Root);
428 assert!(config.exclude.is_empty());
429 assert!(config.include.is_empty());
430 assert!(config.msrv); assert!(config.detect_unused); assert!(config.remove_unused); }
434
435 #[test]
436 fn test_unify_config_should_exclude() {
437 let config = UnifyConfig {
438 exclude: vec!["tokio".to_string(), "serde".to_string()],
439 ..Default::default()
440 };
441 assert!(config.should_exclude("tokio"));
442 assert!(config.should_exclude("serde"));
443 assert!(!config.should_exclude("regex"));
444 }
445
446 #[test]
447 fn test_unify_config_should_include() {
448 let config = UnifyConfig {
449 include: vec!["special-dep".to_string()],
450 ..Default::default()
451 };
452 assert!(config.should_include("special-dep"));
453 assert!(!config.should_include("normal-dep"));
454 }
455
456 #[test]
457 fn test_transitive_feature_host_in_full_config() {
458 let toml = r#"
459 include_paths = true
460 include_renamed = false
461 pin_transitives = true
462 transitive_host = "root"
463 exclude = []
464 include = []
465 "#;
466
467 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
468 assert_eq!(config.transitive_host, TransitiveFeatureHost::Root);
469 assert!(config.include_paths);
470 assert!(config.pin_transitives);
471 }
472
473 #[test]
474 fn test_unify_config_default_transitive_host() {
475 let config = UnifyConfig::default();
476 assert_eq!(config.transitive_host, TransitiveFeatureHost::Root);
477 assert!(!config.pin_transitives); }
479
480 #[test]
481 fn test_prune_dead_features_default() {
482 let config = UnifyConfig::default();
483 assert!(config.prune_dead_features); }
485
486 #[test]
487 fn test_prune_dead_features_parsing() {
488 let toml = r#"prune_dead_features = true"#;
489 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
490 assert!(config.prune_dead_features);
491
492 let toml = r#"prune_dead_features = false"#;
493 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
494 assert!(!config.prune_dead_features);
495 }
496
497 #[test]
498 fn test_strict_version_compat_default() {
499 let config = UnifyConfig::default();
500 assert!(config.strict_version_compat); }
502
503 #[test]
504 fn test_exact_pin_handling_default() {
505 let config = UnifyConfig::default();
506 assert_eq!(config.exact_pin_handling, ExactPinHandling::Warn);
507 }
508
509 #[test]
510 fn test_exact_pin_handling_parsing() {
511 let toml = r#"exact_pin_handling = "skip""#;
512 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
513 assert_eq!(config.exact_pin_handling, ExactPinHandling::Skip);
514
515 let toml = r#"exact_pin_handling = "preserve""#;
516 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
517 assert_eq!(config.exact_pin_handling, ExactPinHandling::Preserve);
518
519 let toml = r#"exact_pin_handling = "warn""#;
520 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
521 assert_eq!(config.exact_pin_handling, ExactPinHandling::Warn);
522 }
523
524 #[test]
525 fn test_detect_unused_default() {
526 let config = UnifyConfig::default();
527 assert!(config.detect_unused); assert!(config.remove_unused); }
530
531 #[test]
532 fn test_new_config_options_parsing() {
533 let toml = r#"
534 strict_version_compat = false
535 exact_pin_handling = "preserve"
536 detect_unused = true
537 remove_unused = true
538 "#;
539 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
540 assert!(!config.strict_version_compat);
541 assert_eq!(config.exact_pin_handling, ExactPinHandling::Preserve);
542 assert!(config.detect_unused);
543 assert!(config.remove_unused);
544 }
545
546 #[test]
547 fn test_major_version_conflict_default() {
548 let config = UnifyConfig::default();
549 assert_eq!(config.major_version_conflict, MajorVersionConflict::Warn);
550 }
551
552 #[test]
553 fn test_major_version_conflict_parsing() {
554 let toml = r#"major_version_conflict = "warn""#;
555 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
556 assert_eq!(config.major_version_conflict, MajorVersionConflict::Warn);
557
558 let toml = r#"major_version_conflict = "bump""#;
559 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
560 assert_eq!(config.major_version_conflict, MajorVersionConflict::Bump);
561 }
562
563 #[test]
564 fn test_major_version_conflict_with_other_options() {
565 let toml = r#"
566 strict_version_compat = false
567 exact_pin_handling = "preserve"
568 major_version_conflict = "bump"
569 "#;
570 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
571 assert!(!config.strict_version_compat);
572 assert_eq!(config.exact_pin_handling, ExactPinHandling::Preserve);
573 assert_eq!(config.major_version_conflict, MajorVersionConflict::Bump);
574 }
575
576 #[test]
577 fn test_transitive_host_validate_root() {
578 let config = UnifyConfig {
580 pin_transitives: true,
581 transitive_host: TransitiveFeatureHost::Root,
582 ..Default::default()
583 };
584 let workspace = std::env::current_dir().unwrap();
585 assert!(config.validate(&workspace).is_ok());
586 }
587
588 #[test]
589 fn test_transitive_host_validate_valid_path() {
590 let config = UnifyConfig {
592 pin_transitives: false,
593 transitive_host: TransitiveFeatureHost::Path("src".to_string()),
594 ..Default::default()
595 };
596 let workspace = std::env::current_dir().unwrap();
597 assert!(config.validate(&workspace).is_ok());
598 }
599
600 #[test]
601 fn test_transitive_host_validate_nonexistent_path() {
602 let config = UnifyConfig {
603 pin_transitives: true,
604 transitive_host: TransitiveFeatureHost::Path("nonexistent/path".to_string()),
605 ..Default::default()
606 };
607 let workspace = std::env::current_dir().unwrap();
608 let result = config.validate(&workspace);
609 assert!(result.is_err());
610 let err = result.unwrap_err();
611 assert!(matches!(err, crate::error::ConfigError::InvalidValue { .. }));
612 }
613
614 #[test]
615 fn test_transitive_host_validate_path_traversal() {
616 let config = UnifyConfig {
617 pin_transitives: true,
618 transitive_host: TransitiveFeatureHost::Path("../somewhere".to_string()),
619 ..Default::default()
620 };
621 let workspace = std::env::current_dir().unwrap();
622 let result = config.validate(&workspace);
623 assert!(result.is_err());
624 let err = result.unwrap_err();
625 if let crate::error::ConfigError::InvalidValue { message, .. } = err {
626 assert!(message.contains(".."));
627 } else {
628 panic!("Expected InvalidValue error");
629 }
630 }
631
632 #[test]
633 fn test_preserve_features_default() {
634 let config = UnifyConfig::default();
635 assert!(config.preserve_features.is_empty());
636 }
637
638 #[test]
639 fn test_preserve_features_parsing() {
640 let toml = r#"
641 preserve_features = ["future-api", "unstable-*", "bench*"]
642 "#;
643 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
644 assert_eq!(config.preserve_features.len(), 3);
645 assert!(config.preserve_features.contains(&"future-api".to_string()));
646 assert!(config.preserve_features.contains(&"unstable-*".to_string()));
647 assert!(config.preserve_features.contains(&"bench*".to_string()));
648 }
649
650 #[test]
651 fn test_should_preserve_feature_exact_match() {
652 let config = UnifyConfig {
653 preserve_features: vec!["future-api".to_string(), "experimental".to_string()],
654 ..Default::default()
655 };
656 assert!(config.should_preserve_feature("future-api"));
657 assert!(config.should_preserve_feature("experimental"));
658 assert!(!config.should_preserve_feature("other-feature"));
659 }
660
661 #[test]
662 fn test_should_preserve_feature_glob_wildcard() {
663 let config = UnifyConfig {
664 preserve_features: vec!["unstable-*".to_string()],
665 ..Default::default()
666 };
667 assert!(config.should_preserve_feature("unstable-api"));
668 assert!(config.should_preserve_feature("unstable-feature"));
669 assert!(config.should_preserve_feature("unstable-"));
670 assert!(!config.should_preserve_feature("unstable")); assert!(!config.should_preserve_feature("stable-api"));
672 }
673
674 #[test]
675 fn test_should_preserve_feature_glob_suffix() {
676 let config = UnifyConfig {
677 preserve_features: vec!["bench*".to_string()],
678 ..Default::default()
679 };
680 assert!(config.should_preserve_feature("bench"));
681 assert!(config.should_preserve_feature("benchmark"));
682 assert!(config.should_preserve_feature("benchmarks"));
683 assert!(!config.should_preserve_feature("prebench"));
684 }
685
686 #[test]
687 fn test_should_preserve_feature_glob_question_mark() {
688 let config = UnifyConfig {
689 preserve_features: vec!["test-?".to_string()],
690 ..Default::default()
691 };
692 assert!(config.should_preserve_feature("test-a"));
693 assert!(config.should_preserve_feature("test-1"));
694 assert!(!config.should_preserve_feature("test-ab")); assert!(!config.should_preserve_feature("test-")); }
697
698 #[test]
699 fn test_should_preserve_feature_multiple_patterns() {
700 let config = UnifyConfig {
701 preserve_features: vec!["future-api".to_string(), "unstable-*".to_string(), "bench*".to_string()],
702 ..Default::default()
703 };
704 assert!(config.should_preserve_feature("future-api"));
706 assert!(config.should_preserve_feature("unstable-feature"));
708 assert!(config.should_preserve_feature("benchmark"));
709 assert!(!config.should_preserve_feature("stable-api"));
711 assert!(!config.should_preserve_feature("other"));
712 }
713
714 #[test]
715 fn test_should_preserve_feature_empty_list() {
716 let config = UnifyConfig::default();
717 assert!(!config.should_preserve_feature("any-feature"));
718 }
719
720 #[test]
721 fn test_msrv_source_default() {
722 let config = UnifyConfig::default();
723 assert_eq!(config.msrv_source, MsrvSource::Max);
724 }
725
726 #[test]
727 fn test_msrv_source_parsing_deps() {
728 let toml = r#"msrv_source = "deps""#;
729 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
730 assert_eq!(config.msrv_source, MsrvSource::Deps);
731 }
732
733 #[test]
734 fn test_msrv_source_parsing_workspace() {
735 let toml = r#"msrv_source = "workspace""#;
736 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
737 assert_eq!(config.msrv_source, MsrvSource::Workspace);
738 }
739
740 #[test]
741 fn test_msrv_source_parsing_max() {
742 let toml = r#"msrv_source = "max""#;
743 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
744 assert_eq!(config.msrv_source, MsrvSource::Max);
745 }
746
747 #[test]
748 fn test_msrv_source_with_msrv_enabled() {
749 let toml = r#"
750 msrv = true
751 msrv_source = "workspace"
752 "#;
753 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
754 assert!(config.msrv);
755 assert_eq!(config.msrv_source, MsrvSource::Workspace);
756 }
757
758 #[test]
759 fn test_msrv_source_with_msrv_disabled() {
760 let toml = r#"
761 msrv = false
762 msrv_source = "deps"
763 "#;
764 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
765 assert!(!config.msrv);
766 assert_eq!(config.msrv_source, MsrvSource::Deps);
767 }
768
769 #[test]
770 fn test_detect_undeclared_features_default() {
771 let config = UnifyConfig::default();
772 assert!(config.detect_undeclared_features); }
774
775 #[test]
776 fn test_fix_undeclared_features_default() {
777 let config = UnifyConfig::default();
778 assert!(config.fix_undeclared_features); }
780
781 #[test]
782 fn test_detect_undeclared_features_parsing_true() {
783 let toml = r#"detect_undeclared_features = true"#;
784 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
785 assert!(config.detect_undeclared_features);
786 }
787
788 #[test]
789 fn test_detect_undeclared_features_parsing_false() {
790 let toml = r#"detect_undeclared_features = false"#;
791 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
792 assert!(!config.detect_undeclared_features);
793 }
794
795 #[test]
796 fn test_fix_undeclared_features_parsing_true() {
797 let toml = r#"fix_undeclared_features = true"#;
798 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
799 assert!(config.fix_undeclared_features);
800 }
801
802 #[test]
803 fn test_fix_undeclared_features_parsing_false() {
804 let toml = r#"fix_undeclared_features = false"#;
805 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
806 assert!(!config.fix_undeclared_features);
807 }
808
809 #[test]
810 fn test_undeclared_features_both_options() {
811 let toml = r#"
812 detect_undeclared_features = true
813 fix_undeclared_features = false
814 "#;
815 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
816 assert!(config.detect_undeclared_features);
817 assert!(!config.fix_undeclared_features);
818 }
819
820 #[test]
821 fn test_undeclared_features_with_other_options() {
822 let toml = r#"
823 detect_unused = true
824 remove_unused = true
825 detect_undeclared_features = true
826 fix_undeclared_features = true
827 prune_dead_features = false
828 "#;
829 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
830 assert!(config.detect_unused);
831 assert!(config.remove_unused);
832 assert!(config.detect_undeclared_features);
833 assert!(config.fix_undeclared_features);
834 assert!(!config.prune_dead_features);
835 }
836
837 #[test]
838 fn test_undeclared_features_detect_disabled_fix_enabled() {
839 let toml = r#"
842 detect_undeclared_features = false
843 fix_undeclared_features = true
844 "#;
845 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
846 assert!(!config.detect_undeclared_features);
847 assert!(config.fix_undeclared_features);
848 }
849
850 #[test]
853 fn test_skip_undeclared_patterns_default() {
854 let config = UnifyConfig::default();
855 assert!(!config.skip_undeclared_patterns.is_empty());
856 assert!(config.skip_undeclared_patterns.contains(&"default".to_string()));
857 assert!(config.skip_undeclared_patterns.contains(&"std".to_string()));
858 assert!(config.skip_undeclared_patterns.contains(&"alloc".to_string()));
859 assert!(config.skip_undeclared_patterns.contains(&"*_backend".to_string()));
860 assert!(config.skip_undeclared_patterns.contains(&"*_impl".to_string()));
861 }
862
863 #[test]
864 fn test_skip_undeclared_patterns_parsing() {
865 let toml = r#"
866 skip_undeclared_patterns = ["default", "std", "custom-*"]
867 "#;
868 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
869 assert_eq!(config.skip_undeclared_patterns.len(), 3);
870 assert!(config.skip_undeclared_patterns.contains(&"default".to_string()));
871 assert!(config.skip_undeclared_patterns.contains(&"std".to_string()));
872 assert!(config.skip_undeclared_patterns.contains(&"custom-*".to_string()));
873 }
874
875 #[test]
876 fn test_skip_undeclared_patterns_empty() {
877 let toml = r#"
878 skip_undeclared_patterns = []
879 "#;
880 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
881 assert!(config.skip_undeclared_patterns.is_empty());
882 }
883
884 #[test]
885 fn test_should_skip_undeclared_feature_exact_match() {
886 let config = UnifyConfig {
887 skip_undeclared_patterns: vec!["default".to_string(), "std".to_string()],
888 ..Default::default()
889 };
890 assert!(config.should_skip_undeclared_feature("default"));
891 assert!(config.should_skip_undeclared_feature("std"));
892 assert!(!config.should_skip_undeclared_feature("derive"));
893 }
894
895 #[test]
896 fn test_should_skip_undeclared_feature_glob_suffix() {
897 let config = UnifyConfig {
898 skip_undeclared_patterns: vec!["*_backend".to_string()],
899 ..Default::default()
900 };
901 assert!(config.should_skip_undeclared_feature("sqlite_backend"));
902 assert!(config.should_skip_undeclared_feature("postgres_backend"));
903 assert!(config.should_skip_undeclared_feature("_backend")); assert!(!config.should_skip_undeclared_feature("backend"));
905 assert!(!config.should_skip_undeclared_feature("backend_"));
906 }
907
908 #[test]
909 fn test_should_skip_undeclared_feature_glob_prefix() {
910 let config = UnifyConfig {
911 skip_undeclared_patterns: vec!["unstable-*".to_string()],
912 ..Default::default()
913 };
914 assert!(config.should_skip_undeclared_feature("unstable-api"));
915 assert!(config.should_skip_undeclared_feature("unstable-internal"));
916 assert!(config.should_skip_undeclared_feature("unstable-")); assert!(!config.should_skip_undeclared_feature("unstable"));
918 }
919
920 #[test]
921 fn test_should_skip_undeclared_feature_glob_question_mark() {
922 let config = UnifyConfig {
923 skip_undeclared_patterns: vec!["test-?".to_string()],
924 ..Default::default()
925 };
926 assert!(config.should_skip_undeclared_feature("test-1"));
927 assert!(config.should_skip_undeclared_feature("test-a"));
928 assert!(!config.should_skip_undeclared_feature("test-12"));
929 assert!(!config.should_skip_undeclared_feature("test-"));
930 }
931
932 #[test]
933 fn test_should_skip_undeclared_feature_multiple_patterns() {
934 let config = UnifyConfig {
935 skip_undeclared_patterns: vec![
936 "default".to_string(),
937 "std".to_string(),
938 "*_backend".to_string(),
939 "*_impl".to_string(),
940 ],
941 ..Default::default()
942 };
943 assert!(config.should_skip_undeclared_feature("default"));
944 assert!(config.should_skip_undeclared_feature("std"));
945 assert!(config.should_skip_undeclared_feature("sqlite_backend"));
946 assert!(config.should_skip_undeclared_feature("sync_impl"));
947 assert!(!config.should_skip_undeclared_feature("derive"));
948 assert!(!config.should_skip_undeclared_feature("serde"));
949 }
950
951 #[test]
952 fn test_should_skip_undeclared_feature_empty_patterns() {
953 let config = UnifyConfig {
954 skip_undeclared_patterns: vec![],
955 ..Default::default()
956 };
957 assert!(!config.should_skip_undeclared_feature("default"));
959 assert!(!config.should_skip_undeclared_feature("std"));
960 assert!(!config.should_skip_undeclared_feature("anything"));
961 }
962
963 #[test]
966 fn test_sort_dependencies_default() {
967 let config = UnifyConfig::default();
968 assert!(config.sort_dependencies); }
970
971 #[test]
972 fn test_sort_dependencies_parsing_true() {
973 let toml = r#"sort_dependencies = true"#;
974 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
975 assert!(config.sort_dependencies);
976 }
977
978 #[test]
979 fn test_sort_dependencies_parsing_false() {
980 let toml = r#"sort_dependencies = false"#;
981 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
982 assert!(!config.sort_dependencies);
983 }
984
985 #[test]
986 fn test_sort_dependencies_with_other_options() {
987 let toml = r#"
988 detect_unused = true
989 remove_unused = true
990 sort_dependencies = false
991 "#;
992 let config: UnifyConfig = toml_edit::de::from_str(toml).unwrap();
993 assert!(config.detect_unused);
994 assert!(config.remove_unused);
995 assert!(!config.sort_dependencies);
996 }
997}