commitlint_rs/
rule.rs

1use std::fmt::Debug;
2
3use crate::{message::Message, result::Violation};
4use serde::{Deserialize, Serialize};
5
6use self::{
7    body_empty::BodyEmpty, body_max_length::BodyMaxLength, description_empty::DescriptionEmpty,
8    description_format::DescriptionFormat, description_max_length::DescriptionMaxLength,
9    footers_empty::FootersEmpty, r#type::Type, scope::Scope, scope_empty::ScopeEmpty,
10    scope_format::ScopeFormat, scope_max_length::ScopeMaxLength, subject_empty::SubjectEmpty,
11    type_empty::TypeEmpty, type_format::TypeFormat, type_max_length::TypeMaxLength,
12};
13
14pub mod body_empty;
15pub mod body_max_length;
16pub mod description_empty;
17pub mod description_format;
18pub mod description_max_length;
19pub mod footers_empty;
20pub mod scope;
21pub mod scope_empty;
22pub mod scope_format;
23pub mod scope_max_length;
24pub mod subject_empty;
25pub mod r#type;
26pub mod type_empty;
27pub mod type_format;
28pub mod type_max_length;
29
30/// Rules represents the rules of commitlint.
31/// See: https://commitlint.js.org/reference/rules.html
32#[derive(Clone, Debug, Deserialize, Serialize)]
33#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
34pub struct Rules {
35    #[serde(rename = "body-empty")]
36    #[serde(skip_serializing_if = "Option::is_none")]
37    pub body_empty: Option<BodyEmpty>,
38
39    #[serde(rename = "body-max-length")]
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub body_max_length: Option<BodyMaxLength>,
42
43    #[serde(rename = "description-empty")]
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub description_empty: Option<DescriptionEmpty>,
46
47    #[serde(rename = "description-format")]
48    #[serde(skip_serializing_if = "Option::is_none")]
49    pub description_format: Option<DescriptionFormat>,
50
51    #[serde(rename = "description-max-length")]
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub description_max_length: Option<DescriptionMaxLength>,
54
55    #[serde(rename = "footers-empty")]
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub footers_empty: Option<FootersEmpty>,
58
59    #[serde(rename = "scope")]
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub scope: Option<Scope>,
62
63    #[serde(rename = "scope-empty")]
64    #[serde(skip_serializing_if = "Option::is_none")]
65    pub scope_empty: Option<ScopeEmpty>,
66
67    #[serde(rename = "scope-format")]
68    #[serde(skip_serializing_if = "Option::is_none")]
69    pub scope_format: Option<ScopeFormat>,
70
71    #[serde(rename = "scope-max-length")]
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub scope_max_length: Option<ScopeMaxLength>,
74
75    #[serde(rename = "subject-empty")]
76    #[serde(skip_serializing_if = "Option::is_none")]
77    pub subject_empty: Option<SubjectEmpty>,
78
79    #[serde(rename = "type")]
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub r#type: Option<Type>,
82
83    #[serde(rename = "type-empty")]
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub type_empty: Option<TypeEmpty>,
86
87    #[serde(rename = "type-format")]
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub type_format: Option<TypeFormat>,
90
91    #[serde(rename = "type-max-length")]
92    #[serde(skip_serializing_if = "Option::is_none")]
93    pub type_max_length: Option<TypeMaxLength>,
94}
95
96/// Rule is a collection of rules.
97impl Rules {
98    pub fn validate(&self, message: &Message) -> Vec<Violation> {
99        let mut results = Vec::new();
100
101        if let Some(rule) = &self.body_empty {
102            if let Some(validation) = rule.validate(message) {
103                results.push(validation);
104            }
105        }
106
107        if let Some(rule) = &self.body_max_length {
108            if let Some(validation) = rule.validate(message) {
109                results.push(validation);
110            }
111        }
112
113        if let Some(rule) = &self.description_empty {
114            if let Some(validation) = rule.validate(message) {
115                results.push(validation);
116            }
117        }
118
119        if let Some(rule) = &self.description_format {
120            if let Some(validation) = rule.validate(message) {
121                results.push(validation);
122            }
123        }
124
125        if let Some(rule) = &self.description_max_length {
126            if let Some(validation) = rule.validate(message) {
127                results.push(validation);
128            }
129        }
130
131        if let Some(rule) = &self.scope {
132            if let Some(validation) = rule.validate(message) {
133                results.push(validation);
134            }
135        }
136
137        if let Some(rule) = &self.scope_empty {
138            if let Some(validation) = rule.validate(message) {
139                results.push(validation);
140            }
141        }
142
143        if let Some(rule) = &self.scope_format {
144            if let Some(validation) = rule.validate(message) {
145                results.push(validation);
146            }
147        }
148
149        if let Some(rule) = &self.scope_max_length {
150            if let Some(validation) = rule.validate(message) {
151                results.push(validation);
152            }
153        }
154
155        if let Some(rule) = &self.subject_empty {
156            if let Some(validation) = rule.validate(message) {
157                results.push(validation);
158            }
159        }
160
161        if let Some(rule) = &self.r#type {
162            if let Some(validation) = rule.validate(message) {
163                results.push(validation);
164            }
165        }
166
167        if let Some(rule) = &self.type_empty {
168            if let Some(validation) = rule.validate(message) {
169                results.push(validation);
170            }
171        }
172
173        if let Some(rule) = &self.type_format {
174            if let Some(validation) = rule.validate(message) {
175                results.push(validation);
176            }
177        }
178
179        if let Some(rule) = &self.type_max_length {
180            if let Some(validation) = rule.validate(message) {
181                results.push(validation);
182            }
183        }
184
185        results
186    }
187}
188
189/// Default implementation of Rules.
190/// If no config files are specified, this will be used.
191impl Default for Rules {
192    fn default() -> Self {
193        Self {
194            body_empty: None,
195            body_max_length: None,
196            description_empty: DescriptionEmpty::default().into(),
197            description_format: None,
198            description_max_length: None,
199            footers_empty: None,
200            scope: None,
201            scope_empty: None,
202            scope_format: None,
203            scope_max_length: None,
204            subject_empty: SubjectEmpty::default().into(),
205            r#type: None,
206            type_empty: TypeEmpty::default().into(),
207            type_format: None,
208            type_max_length: None,
209        }
210    }
211}
212
213/// Rule trait represents a rule that can be applied to a text.
214pub trait Rule: Default {
215    /// The name of the rule.
216    /// Note that it should be unique
217    const NAME: &'static str;
218
219    /// The message to display when the rule fails.
220    fn message(&self, message: &Message) -> String;
221
222    /// The level of the rule.
223    const LEVEL: Level;
224
225    /// Validate the given text.
226    fn validate(&self, message: &Message) -> Option<Violation>;
227}
228
229/// Level represents the level of a rule.
230#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
231#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
232pub enum Level {
233    #[serde(rename = "error")]
234    Error,
235
236    #[serde(rename = "ignore")]
237    Ignore,
238
239    #[serde(rename = "warning")]
240    Warning,
241}