commitlint_rs/rule/
scope_format.rs1use crate::{message::Message, result::Violation, rule::Rule};
2use serde::{Deserialize, Serialize};
3
4use super::Level;
5
6#[derive(Clone, Debug, Deserialize, Serialize)]
8#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
9pub struct ScopeFormat {
10 level: Option<Level>,
15
16 format: Option<String>,
18}
19
20impl Rule for ScopeFormat {
22 const NAME: &'static str = "scope-format";
23 const LEVEL: Level = Level::Error;
24
25 fn message(&self, _message: &Message) -> String {
26 format!(
27 "scope format does not match format: {}",
28 self.format.as_ref().unwrap()
29 )
30 }
31
32 fn validate(&self, message: &Message) -> Option<Violation> {
33 if let Some(format) = &self.format {
34 let regex = match regex::Regex::new(format) {
35 Ok(regex) => regex,
36 Err(err) => {
37 return Some(Violation {
38 level: self.level.unwrap_or(Self::LEVEL),
39 message: err.to_string(),
40 });
41 }
42 };
43
44 match &message.scope {
45 None => {
46 return Some(Violation {
47 level: self.level.unwrap_or(Self::LEVEL),
48 message: "found no scope".to_string(),
49 });
50 }
51 Some(description) => {
52 if !regex.is_match(description) {
53 return Some(Violation {
54 level: self.level.unwrap_or(Self::LEVEL),
55 message: self.message(message),
56 });
57 }
58 }
59 }
60 }
61
62 None
63 }
64}
65
66impl Default for ScopeFormat {
68 fn default() -> Self {
69 Self {
70 level: Some(Self::LEVEL),
71 format: None,
72 }
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[test]
81 fn test_invalid_description_format() {
82 let rule = ScopeFormat {
83 format: Some(r"^[a-z].*".to_string()),
84 ..Default::default()
85 };
86
87 let message = Message {
88 body: None,
89 description: Some("Add new flag".to_string()),
90 footers: None,
91 r#type: Some("feat".to_string()),
92 raw: "feat(scope): Add new flag".to_string(),
93 scope: Some("scope".to_string()),
94 subject: None,
95 };
96
97 assert!(rule.validate(&message).is_none());
98 }
99
100 #[test]
101 fn test_valid_description_format() {
102 let rule = ScopeFormat {
103 format: Some(r"^[a-z].*".to_string()),
104 ..Default::default()
105 };
106
107 let message = Message {
108 body: None,
109 description: Some("Add new flag".to_string()),
110 footers: None,
111 r#type: Some("feat".to_string()),
112 raw: "feat(Scope): Add new flag".to_string(),
113 scope: Some("Scope".to_string()),
114 subject: None,
115 };
116
117 let violation = rule.validate(&message);
118 assert!(violation.is_some());
119 assert_eq!(violation.clone().unwrap().level, Level::Error);
120 assert_eq!(
121 violation.unwrap().message,
122 "scope format does not match format: ^[a-z].*".to_string()
123 );
124 }
125
126 #[test]
127 fn test_invalid_regex() {
128 let rule = ScopeFormat {
129 format: Some(r"(".to_string()),
130 ..Default::default()
131 };
132
133 let message = Message {
134 body: None,
135 description: Some("Add regex".to_string()),
136 footers: None,
137 r#type: Some("feat".to_string()),
138 raw: "feat(scope): Add regex".to_string(),
139 scope: Some("scope".to_string()),
140 subject: None,
141 };
142
143 let violation = rule.validate(&message);
144 assert!(violation.is_some());
145 assert_eq!(violation.clone().unwrap().level, Level::Error);
146 assert!(violation.unwrap().message.contains("regex parse error"));
147 }
148}