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