1use std::borrow::Cow;
2use std::collections::BTreeMap;
3use std::fs;
4use std::path::Path;
5use std::sync::{Arc, LazyLock};
6
7use serde::Deserialize;
8
9use crate::pattern::matches_glob;
10
11#[derive(Debug, Deserialize, Default, Clone)]
13#[serde(deny_unknown_fields)]
14pub struct PatternCheck {
15 #[serde(default)]
17 pub enabled: bool,
18 #[serde(default, deserialize_with = "deserialize_arc_str_slice")]
20 pub patterns: Arc<[Arc<str>]>,
21}
22
23impl PatternCheck {
24 pub fn apply_override(&mut self, ovr: &PatternOverride) {
26 if let Some(enabled) = ovr.enabled {
27 self.enabled = enabled;
28 }
29 if !ovr.patterns.is_empty() {
30 self.patterns = ovr.patterns.clone();
31 }
32 }
33}
34
35fn deserialize_arc_str_slice<'de, D: serde::Deserializer<'de>>(
36 deserializer: D,
37) -> Result<Arc<[Arc<str>]>, D::Error> {
38 let strings: Vec<String> = Vec::deserialize(deserializer)?;
39 Ok(strings.into_iter().map(Arc::from).collect())
40}
41
42const DEFAULT_GENERIC_NAMES: &[&str] = &[
44 "tmp", "temp", "data", "val", "value", "result", "res", "ret", "buf", "buffer", "item", "elem",
45 "obj", "input", "output", "info", "ctx", "args", "params", "thing", "stuff", "foo", "bar",
46 "baz",
47];
48
49#[derive(Debug, Deserialize, Clone)]
51#[serde(deny_unknown_fields)]
52pub struct NamingCheck {
53 #[serde(default)]
55 pub enabled: bool,
56 #[serde(
58 default = "default_generic_names",
59 deserialize_with = "deserialize_arc_str_slice"
60 )]
61 pub generic_names: Arc<[Arc<str>]>,
62 #[serde(default = "default_max_generic_ratio")]
64 pub max_generic_ratio: f64,
65 #[serde(default = "default_min_generic_count")]
67 pub min_generic_count: usize,
68}
69
70impl Default for NamingCheck {
71 fn default() -> Self {
72 Self {
73 enabled: false,
74 generic_names: default_generic_names(),
75 max_generic_ratio: default_max_generic_ratio(),
76 min_generic_count: default_min_generic_count(),
77 }
78 }
79}
80
81impl NamingCheck {
82 pub fn apply_override(&mut self, ovr: &NamingOverride) {
84 if let Some(enabled) = ovr.enabled {
85 self.enabled = enabled;
86 }
87 if let Some(ref names) = ovr.generic_names {
88 self.generic_names = names.clone();
89 }
90 if let Some(ratio) = ovr.max_generic_ratio {
91 self.max_generic_ratio = ratio;
92 }
93 if let Some(count) = ovr.min_generic_count {
94 self.min_generic_count = count;
95 }
96 }
97}
98
99#[derive(Debug, Deserialize, Default, Clone)]
101#[serde(deny_unknown_fields)]
102pub struct NamingOverride {
103 pub enabled: Option<bool>,
105 #[serde(default, deserialize_with = "deserialize_option_arc_str_slice")]
107 pub generic_names: Option<Arc<[Arc<str>]>>,
108 pub max_generic_ratio: Option<f64>,
110 pub min_generic_count: Option<usize>,
112}
113
114static GENERIC_NAMES_ARC: LazyLock<Arc<[Arc<str>]>> = LazyLock::new(|| {
115 DEFAULT_GENERIC_NAMES
116 .iter()
117 .map(|s| Arc::from(*s))
118 .collect()
119});
120
121fn default_generic_names() -> Arc<[Arc<str>]> {
122 Arc::clone(&GENERIC_NAMES_ARC)
123}
124
125type ArcStrSlice = Arc<[Arc<str>]>;
126
127fn deserialize_option_arc_str_slice<'de, D: serde::Deserializer<'de>>(
128 deserializer: D,
129) -> Result<Option<ArcStrSlice>, D::Error> {
130 let opt: Option<Vec<String>> = Option::deserialize(deserializer)?;
131 Ok(opt.map(|v| v.into_iter().map(Arc::from).collect()))
132}
133
134fn default_max_generic_ratio() -> f64 {
135 0.3
136}
137
138fn default_min_generic_count() -> usize {
139 2
140}
141
142#[derive(Debug, Deserialize, Default, Clone)]
144#[serde(deny_unknown_fields)]
145pub struct PatternOverride {
146 pub enabled: Option<bool>,
148 #[serde(default, deserialize_with = "deserialize_arc_str_slice")]
150 pub patterns: Arc<[Arc<str>]>,
151}
152
153#[derive(Debug, Deserialize, Clone)]
156#[serde(deny_unknown_fields)]
157pub struct ItemVisibilityRule {
158 pub path: Box<str>,
160 pub kind: Box<str>,
162 pub name: Box<str>,
164 pub visibility: Box<str>,
167}
168
169#[derive(Debug, Deserialize, Clone)]
171#[serde(deny_unknown_fields)]
172pub struct FeatureBoundaryRule {
173 pub package: Box<str>,
175 pub feature: Box<str>,
177 pub rule: Box<str>,
180}
181
182#[derive(Debug, Deserialize, Clone)]
185#[serde(deny_unknown_fields)]
186pub struct FlatModuleFamily {
187 pub parent: Box<str>,
189 pub package_root: Box<str>,
191 pub prefix: Box<str>,
193}
194
195#[derive(Debug, Deserialize, Default)]
197#[serde(deny_unknown_fields)]
198pub struct ConfigFile {
199 #[serde(default)]
201 pub gate: GateConfig,
202 #[serde(default = "default_max_depth")]
204 pub max_depth: usize,
205 #[serde(default = "default_else_chain_threshold")]
207 pub else_chain_threshold: usize,
208 #[serde(default = "default_max_params")]
210 pub max_params: usize,
211 #[serde(default = "default_max_function_body_lines")]
213 pub max_function_body_lines: usize,
214 #[serde(
217 default = "default_module_root_files",
218 deserialize_with = "deserialize_arc_str_slice"
219 )]
220 pub module_root_files: Arc<[Arc<str>]>,
221 #[serde(default = "default_source_file_warn_lines")]
223 pub source_file_warn_lines: usize,
224 #[serde(default = "default_source_file_deny_lines")]
226 pub source_file_deny_lines: usize,
227 #[serde(default = "default_max_methods")]
229 pub max_methods: usize,
230 #[serde(default)]
232 pub forbid_attributes: PatternCheck,
233 #[serde(default)]
235 pub forbid_types: PatternCheck,
236 #[serde(default)]
238 pub forbid_calls: PatternCheck,
239 #[serde(default)]
241 pub forbid_macros: PatternCheck,
242 #[serde(default)]
244 pub check_naming: NamingCheck,
245 #[serde(default = "default_true")]
247 pub check_nested_if: bool,
248 #[serde(default = "default_true")]
250 pub check_if_in_match: bool,
251 #[serde(default = "default_true")]
253 pub check_nested_match: bool,
254 #[serde(default = "default_true")]
256 pub check_match_in_if: bool,
257 #[serde(default = "default_true")]
259 pub check_else_chain: bool,
260 #[serde(default)]
262 pub forbid_else: bool,
263 #[serde(default = "default_true")]
265 pub forbid_unsafe: bool,
266 #[serde(default)]
268 pub check_dyn_return: bool,
269 #[serde(default)]
271 pub check_dyn_param: bool,
272 #[serde(default)]
274 pub check_vec_box_dyn: bool,
275 #[serde(default)]
277 pub check_dyn_field: bool,
278 #[serde(default)]
280 pub check_clone_in_loop: bool,
281 #[serde(default)]
283 pub check_default_hasher: bool,
284 #[serde(default)]
286 pub check_mixed_concerns: bool,
287 #[serde(default)]
289 pub check_inline_tests: bool,
290 #[serde(default)]
292 pub check_let_underscore_result: bool,
293 #[serde(default)]
295 pub check_high_param_count: bool,
296 #[serde(default)]
298 pub check_long_function_body: bool,
299 #[serde(default)]
301 pub check_module_root_definitions: bool,
302 #[serde(default)]
304 pub check_large_source_file: bool,
305 #[serde(default)]
307 pub check_high_method_count: bool,
308 #[serde(default)]
310 pub count_forwarders: bool,
311 #[serde(default = "default_true")]
313 pub check_item_visibility_policy: bool,
314 #[serde(default)]
316 pub item_visibility_policy: Vec<ItemVisibilityRule>,
317 #[serde(default)]
319 pub check_ungated_test_api: bool,
320 #[serde(default)]
322 pub check_conflicting_module_root: bool,
323 #[serde(
325 default = "default_test_api_patterns",
326 deserialize_with = "deserialize_arc_str_slice"
327 )]
328 pub test_api_patterns: Arc<[Arc<str>]>,
329 #[serde(default = "default_test_support_feature")]
331 pub test_support_feature: Box<str>,
332 #[serde(default = "default_true")]
334 pub check_flat_module_family: bool,
335 #[serde(default)]
337 pub flat_module_families: Vec<FlatModuleFamily>,
338 #[serde(default = "default_true")]
340 pub check_feature_boundary: bool,
341 #[serde(default)]
343 pub feature_boundaries: Vec<FeatureBoundaryRule>,
344 #[serde(default)]
346 pub check_scattered_inherent_impl: bool,
347 #[serde(default)]
349 pub overrides: BTreeMap<Box<str>, PathOverride>,
350}
351
352#[derive(Debug, Deserialize, Default)]
354#[serde(deny_unknown_fields)]
355pub struct PathOverride {
356 pub enabled: Option<bool>,
358 pub max_depth: Option<usize>,
360 pub max_params: Option<usize>,
362 pub max_function_body_lines: Option<usize>,
364 pub source_file_warn_lines: Option<usize>,
367 pub source_file_deny_lines: Option<usize>,
369 pub max_methods: Option<usize>,
371 pub forbid_attributes: Option<PatternOverride>,
373 pub forbid_types: Option<PatternOverride>,
375 pub forbid_calls: Option<PatternOverride>,
377 pub forbid_macros: Option<PatternOverride>,
379 pub check_naming: Option<NamingOverride>,
381 pub check_nested_if: Option<bool>,
383 pub check_if_in_match: Option<bool>,
385 pub check_nested_match: Option<bool>,
387 pub check_match_in_if: Option<bool>,
389 pub check_else_chain: Option<bool>,
391 pub forbid_else: Option<bool>,
393 pub forbid_unsafe: Option<bool>,
395 pub check_dyn_return: Option<bool>,
397 pub check_dyn_param: Option<bool>,
399 pub check_vec_box_dyn: Option<bool>,
401 pub check_dyn_field: Option<bool>,
403 pub check_clone_in_loop: Option<bool>,
405 pub check_default_hasher: Option<bool>,
407 pub check_mixed_concerns: Option<bool>,
409 pub check_inline_tests: Option<bool>,
411 pub check_let_underscore_result: Option<bool>,
413 pub check_high_param_count: Option<bool>,
415 pub check_long_function_body: Option<bool>,
417 pub check_module_root_definitions: Option<bool>,
419 pub check_large_source_file: Option<bool>,
421 pub check_high_method_count: Option<bool>,
423 pub count_forwarders: Option<bool>,
425 pub check_item_visibility_policy: Option<bool>,
427 pub check_ungated_test_api: Option<bool>,
429 pub check_conflicting_module_root: Option<bool>,
431 pub check_flat_module_family: Option<bool>,
433 pub check_feature_boundary: Option<bool>,
435 pub check_scattered_inherent_impl: Option<bool>,
437}
438
439fn default_max_depth() -> usize {
440 3
441}
442
443fn default_else_chain_threshold() -> usize {
444 3
445}
446
447fn default_max_params() -> usize {
448 5
449}
450
451fn default_max_function_body_lines() -> usize {
452 120
453}
454
455static MODULE_ROOT_FILES_ARC: LazyLock<Arc<[Arc<str>]>> =
456 LazyLock::new(|| [Arc::from("mod.rs"), Arc::from("lib.rs")].into());
457
458fn default_module_root_files() -> Arc<[Arc<str>]> {
459 Arc::clone(&MODULE_ROOT_FILES_ARC)
460}
461
462fn default_source_file_warn_lines() -> usize {
463 500
464}
465
466fn default_source_file_deny_lines() -> usize {
467 1000
468}
469
470fn default_max_methods() -> usize {
471 40
472}
473
474static TEST_API_PATTERNS_ARC: LazyLock<Arc<[Arc<str>]>> =
475 LazyLock::new(|| [Arc::from("*_for_tests")].into());
476
477fn default_test_api_patterns() -> Arc<[Arc<str>]> {
478 Arc::clone(&TEST_API_PATTERNS_ARC)
479}
480
481fn default_test_support_feature() -> Box<str> {
482 "test-support".into()
483}
484
485fn default_true() -> bool {
486 true
487}
488
489pub fn check_path_override<'a>(
496 file_path: &str,
497 config: &'a ConfigFile,
498) -> Option<&'a PathOverride> {
499 config
500 .overrides
501 .iter()
502 .filter(|(pattern, _)| matches_glob(pattern, file_path))
503 .max_by_key(|(pattern, _)| pattern.len())
504 .map(|(_, override_config)| override_config)
505}
506
507macro_rules! for_each_bool_check {
521 ($callback:ident!) => {
522 $callback! {
523 "Flag `if` inside `if`.", check_nested_if, true;
524 "Flag `if` inside `match` arm.", check_if_in_match, true;
525 "Flag `match` inside `match`.", check_nested_match, true;
526 "Flag `match` inside `if` branch.", check_match_in_if, true;
527 "Flag long `if/else if` chains.", check_else_chain, true;
528 "Flag any use of the `else` keyword.", forbid_else, false;
529 "Flag any `unsafe` block.", forbid_unsafe, true;
530 "Flag dynamic dispatch in return types.", check_dyn_return, false;
531 "Flag dynamic dispatch in function parameters.", check_dyn_param, false;
532 "Flag `Vec<Box<dyn T>>`.", check_vec_box_dyn, false;
533 "Flag dynamic dispatch in struct fields.", check_dyn_field, false;
534 "Flag `.clone()` inside loop bodies.", check_clone_in_loop, false;
535 "Flag `HashMap`/`HashSet` with default hasher.", check_default_hasher, false;
536 "Flag disconnected type groups in a single file.", check_mixed_concerns, false;
537 "Flag `#[cfg(test)] mod` blocks in source files.", check_inline_tests, false;
538 "Flag `let _ = expr` that discards a Result.", check_let_underscore_result, false;
539 "Flag functions with too many parameters.", check_high_param_count, false;
540 "Flag function bodies that exceed the line ceiling.", check_long_function_body, false;
541 "Flag item definitions in module-root files.", check_module_root_definitions, false;
542 "Flag source files that exceed the line ceiling.", check_large_source_file, false;
543 "Flag god-object types by inherent-method count.", check_high_method_count, false;
544 "Count pure forwarders toward `high-method-count`.", count_forwarders, false;
545 "Enforce configured item-visibility policies.", check_item_visibility_policy, true;
546 "Flag ungated test-only APIs under `src/`.", check_ungated_test_api, false;
547 "Flag sibling `<stem>.rs` and `<stem>/` module roots.", check_conflicting_module_root, false;
548 "Enforce configured flat-module-family layout rules.", check_flat_module_family, true;
549 "Enforce configured Cargo feature-boundary invariants.", check_feature_boundary, true;
550 "Flag types whose inherent impls span more than one file.", check_scattered_inherent_impl, false;
551 }
552 };
553}
554
555macro_rules! impl_check_config {
558 ($($doc:literal, $field:ident, $default:expr;)*) => {
559 #[derive(Debug, Clone)]
561 pub struct CheckConfig {
562 pub max_depth: usize,
564 pub else_chain_threshold: usize,
566 pub max_params: usize,
568 pub max_function_body_lines: usize,
570 pub module_root_files: Arc<[Arc<str>]>,
572 pub source_file_warn_lines: usize,
574 pub source_file_deny_lines: usize,
576 pub max_methods: usize,
578 pub item_visibility_policy: Arc<[ItemVisibilityRule]>,
580 pub flat_module_families: Arc<[FlatModuleFamily]>,
582 pub feature_boundaries: Arc<[FeatureBoundaryRule]>,
584 pub test_api_patterns: Arc<[Arc<str>]>,
586 pub test_support_feature: Arc<str>,
588 pub forbid_attributes: PatternCheck,
590 pub forbid_types: PatternCheck,
592 pub forbid_calls: PatternCheck,
594 pub forbid_macros: PatternCheck,
596 pub check_naming: NamingCheck,
598 $(
599 #[doc = $doc]
600 pub $field: bool,
601 )*
602 }
603
604 impl Default for CheckConfig {
605 fn default() -> Self {
606 Self {
607 max_depth: default_max_depth(),
608 else_chain_threshold: default_else_chain_threshold(),
609 max_params: default_max_params(),
610 max_function_body_lines: default_max_function_body_lines(),
611 module_root_files: default_module_root_files(),
612 source_file_warn_lines: default_source_file_warn_lines(),
613 source_file_deny_lines: default_source_file_deny_lines(),
614 max_methods: default_max_methods(),
615 item_visibility_policy: Arc::from([]),
616 flat_module_families: Arc::from([]),
617 feature_boundaries: Arc::from([]),
618 test_api_patterns: default_test_api_patterns(),
619 test_support_feature: Arc::from(default_test_support_feature()),
620 forbid_attributes: PatternCheck::default(),
621 forbid_types: PatternCheck::default(),
622 forbid_calls: PatternCheck::default(),
623 forbid_macros: PatternCheck::default(),
624 check_naming: NamingCheck::default(),
625 $( $field: $default, )*
626 }
627 }
628 }
629
630 impl CheckConfig {
631 pub fn from_config_file(fc: &ConfigFile) -> Self {
633 Self {
634 max_depth: fc.max_depth,
635 else_chain_threshold: fc.else_chain_threshold,
636 max_params: fc.max_params,
637 max_function_body_lines: fc.max_function_body_lines,
638 module_root_files: fc.module_root_files.clone(),
639 source_file_warn_lines: fc.source_file_warn_lines,
640 source_file_deny_lines: fc.source_file_deny_lines,
641 max_methods: fc.max_methods,
642 item_visibility_policy: fc.item_visibility_policy.iter().cloned().collect(),
643 flat_module_families: fc.flat_module_families.iter().cloned().collect(),
644 feature_boundaries: fc.feature_boundaries.iter().cloned().collect(),
645 test_api_patterns: fc.test_api_patterns.clone(),
646 test_support_feature: Arc::from(&*fc.test_support_feature),
647 forbid_attributes: fc.forbid_attributes.clone(),
648 forbid_types: fc.forbid_types.clone(),
649 forbid_calls: fc.forbid_calls.clone(),
650 forbid_macros: fc.forbid_macros.clone(),
651 check_naming: fc.check_naming.clone(),
652 $( $field: fc.$field, )*
653 }
654 }
655
656 pub fn merge_bool_overrides(&mut self, ovr: &PathOverride) {
658 $(
659 if let Some(v) = ovr.$field {
660 self.$field = v;
661 }
662 )*
663 }
664 }
665 };
666}
667
668for_each_bool_check!(impl_check_config!);
669
670macro_rules! assert_bool_fields_in_sync {
674 ($($doc:literal, $field:ident, $default:expr;)*) => {
675 const _: () = {
676 const fn _check(cf: &ConfigFile, po: &PathOverride) {
679 $( let _ = (cf.$field, po.$field); )*
680 }
681 };
682 };
683}
684
685for_each_bool_check!(assert_bool_fields_in_sync!);
686
687impl CheckConfig {
688 pub fn resolve_for_path<'a>(
694 &'a self,
695 file_path: &str,
696 file_config: Option<&ConfigFile>,
697 ) -> Option<Cow<'a, Self>> {
698 let Some(fc) = file_config else {
699 return Some(Cow::Borrowed(self));
700 };
701
702 let Some(override_cfg) = check_path_override(file_path, fc) else {
703 return Some(Cow::Borrowed(self));
704 };
705
706 if override_cfg.enabled == Some(false) {
707 return None;
708 }
709
710 let mut config = self.clone();
711 if let Some(max_depth) = override_cfg.max_depth {
712 config.max_depth = max_depth;
713 }
714 if let Some(max_params) = override_cfg.max_params {
715 config.max_params = max_params;
716 }
717 if let Some(max_body) = override_cfg.max_function_body_lines {
718 config.max_function_body_lines = max_body;
719 }
720 if let Some(warn) = override_cfg.source_file_warn_lines {
721 config.source_file_warn_lines = warn;
722 }
723 if let Some(deny) = override_cfg.source_file_deny_lines {
724 config.source_file_deny_lines = deny;
725 }
726 if let Some(max_methods) = override_cfg.max_methods {
727 config.max_methods = max_methods;
728 }
729
730 config.merge_bool_overrides(override_cfg);
731
732 macro_rules! apply {
733 ($field:ident) => {
734 if let Some(ref ovr) = override_cfg.$field {
735 config.$field.apply_override(ovr);
736 }
737 };
738 }
739 apply!(forbid_attributes);
740 apply!(forbid_types);
741 apply!(forbid_calls);
742 apply!(forbid_macros);
743 apply!(check_naming);
744
745 Some(Cow::Owned(config))
746 }
747}
748
749#[derive(Debug, thiserror::Error)]
751pub enum ConfigError {
752 #[error("failed to read config file: {0}")]
754 Read(#[from] std::io::Error),
755 #[error("failed to parse config file: {0}")]
757 Parse(#[from] toml::de::Error),
758}
759
760pub fn load_config_file(path: &Path) -> Result<ConfigFile, ConfigError> {
762 let content = fs::read_to_string(path)?;
763 Ok(toml::from_str(&content)?)
764}
765
766pub fn find_config_file() -> Result<Option<std::path::PathBuf>, ConfigError> {
768 let project_config = find_project_config_file()?;
769 Ok(project_config.or_else(find_global_config_file))
770}
771
772fn find_project_config_file() -> Result<Option<std::path::PathBuf>, ConfigError> {
773 let config_path = std::env::current_dir()?.join(".pedant.toml");
774 Ok(config_path.exists().then_some(config_path))
775}
776
777fn find_global_config_file() -> Option<std::path::PathBuf> {
778 let config_dir = std::env::var_os("XDG_CONFIG_HOME")
779 .map(std::path::PathBuf::from)
780 .or_else(|| {
781 std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join(".config"))
782 })?;
783 let config_path = config_dir.join("pedant").join("config.toml");
784 config_path.exists().then_some(config_path)
785}
786
787#[derive(Debug)]
789pub enum GateRuleOverride {
790 Disabled,
792 Severity(crate::gate::GateSeverity),
794}
795
796#[derive(Debug)]
801pub struct GateConfig {
802 pub enabled: bool,
804 pub overrides: BTreeMap<Box<str>, GateRuleOverride>,
806}
807
808impl Default for GateConfig {
809 fn default() -> Self {
810 Self {
811 enabled: true,
812 overrides: BTreeMap::new(),
813 }
814 }
815}
816
817impl<'de> Deserialize<'de> for GateConfig {
818 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
819 where
820 D: serde::Deserializer<'de>,
821 {
822 use serde::de::Error;
823
824 #[derive(Deserialize)]
825 #[serde(untagged)]
826 enum GateTomlValue {
827 Bool(bool),
828 String(String),
829 }
830
831 let raw: BTreeMap<Box<str>, GateTomlValue> = BTreeMap::deserialize(deserializer)?;
832 let mut enabled = true;
833 let mut overrides = BTreeMap::new();
834
835 for (key, value) in raw {
836 match (&*key, value) {
837 ("enabled", GateTomlValue::Bool(b)) => enabled = b,
838 ("enabled", GateTomlValue::String(_)) => {
839 return Err(D::Error::custom("'enabled' must be a boolean"));
840 }
841 (_, _) if !is_known_gate_rule(&key) => {
842 return Err(D::Error::custom(format!("unknown gate rule '{key}'")));
843 }
844 (_, GateTomlValue::Bool(false)) => {
845 overrides.insert(key, GateRuleOverride::Disabled);
846 }
847 (_, GateTomlValue::Bool(true)) => {} (_, GateTomlValue::String(s)) => {
849 let severity = parse_gate_severity(&s).ok_or_else(|| {
850 D::Error::custom(format!(
851 "invalid gate severity '{s}': expected \"deny\", \"warn\", or \"info\""
852 ))
853 })?;
854 overrides.insert(key, GateRuleOverride::Severity(severity));
855 }
856 }
857 }
858
859 Ok(GateConfig { enabled, overrides })
860 }
861}
862
863fn is_known_gate_rule(rule_name: &str) -> bool {
864 crate::gate::all_gate_rules()
865 .iter()
866 .any(|rule| rule.name == rule_name)
867}
868
869fn parse_gate_severity(s: &str) -> Option<crate::gate::GateSeverity> {
870 use crate::gate::GateSeverity;
871 match s {
872 "deny" => Some(GateSeverity::Deny),
873 "warn" => Some(GateSeverity::Warn),
874 "info" => Some(GateSeverity::Info),
875 _ => None,
876 }
877}