1use serde::{Deserialize, Serialize};
4use std::str::FromStr;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
12#[serde(rename_all = "lowercase")]
13pub enum Scope {
14 #[default]
16 Added,
17 Touched,
19}
20
21impl Scope {
22 pub const fn as_str(&self) -> &'static str {
24 match self {
25 Self::Added => "added",
26 Self::Touched => "touched",
27 }
28 }
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
33#[serde(rename_all = "lowercase")]
34pub enum FailOn {
35 #[default]
37 Error,
38 Warn,
40 Never,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
46#[serde(rename_all = "lowercase")]
47pub enum MissingBehavior {
48 Skip,
50 #[default]
52 Warn,
53 Fail,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
59#[serde(rename_all = "lowercase")]
60pub enum Profile {
61 Oss,
63 Moderate,
65 #[default]
67 Team,
68 Strict,
70 Lenient,
72}
73
74impl Profile {
75 pub const fn as_str(&self) -> &'static str {
77 match self {
78 Self::Oss => "oss",
79 Self::Moderate => "moderate",
80 Self::Team => "team",
81 Self::Strict => "strict",
82 Self::Lenient => "lenient",
83 }
84 }
85
86 pub const fn flags(self) -> ProfileFlags {
88 match self {
89 Self::Oss => ProfileFlags {
90 scope: Scope::Added,
91 fail_on: FailOn::Never,
92 threshold_pct: 70.0,
93 max_uncovered_lines: None,
94 missing_coverage: MissingBehavior::Skip,
95 missing_file: MissingBehavior::Skip,
96 ignore_directives: true,
97 },
98 Self::Moderate => ProfileFlags {
99 scope: Scope::Added,
100 fail_on: FailOn::Error,
101 threshold_pct: 75.0,
102 max_uncovered_lines: None,
103 missing_coverage: MissingBehavior::Warn,
104 missing_file: MissingBehavior::Skip,
105 ignore_directives: true,
106 },
107 Self::Team => ProfileFlags {
108 scope: Scope::Added,
109 fail_on: FailOn::Error,
110 threshold_pct: 80.0,
111 max_uncovered_lines: None,
112 missing_coverage: MissingBehavior::Warn,
113 missing_file: MissingBehavior::Warn,
114 ignore_directives: true,
115 },
116 Self::Strict => ProfileFlags {
117 scope: Scope::Touched,
118 fail_on: FailOn::Error,
119 threshold_pct: 90.0,
120 max_uncovered_lines: Some(5),
121 missing_coverage: MissingBehavior::Fail,
122 missing_file: MissingBehavior::Fail,
123 ignore_directives: true,
124 },
125 Self::Lenient => ProfileFlags {
126 scope: Scope::Added,
127 fail_on: FailOn::Never,
128 threshold_pct: 0.0,
129 max_uncovered_lines: None,
130 missing_coverage: MissingBehavior::Warn,
131 missing_file: MissingBehavior::Warn,
132 ignore_directives: true,
133 },
134 }
135 }
136}
137
138pub fn profile_from_name(name: &str) -> Option<Profile> {
140 match name.to_ascii_lowercase().as_str() {
141 "oss" => Some(Profile::Oss),
142 "moderate" => Some(Profile::Moderate),
143 "team" => Some(Profile::Team),
144 "strict" => Some(Profile::Strict),
145 "lenient" => Some(Profile::Lenient),
146 _ => None,
147 }
148}
149
150impl FromStr for Profile {
151 type Err = ();
152
153 fn from_str(name: &str) -> Result<Self, Self::Err> {
154 profile_from_name(name).ok_or(())
155 }
156}
157
158#[derive(Debug, Clone, Copy, PartialEq)]
160pub struct ProfileFlags {
161 pub scope: Scope,
163 pub fail_on: FailOn,
165 pub threshold_pct: f64,
167 pub max_uncovered_lines: Option<u32>,
169 pub missing_coverage: MissingBehavior,
171 pub missing_file: MissingBehavior,
173 pub ignore_directives: bool,
175}
176
177pub const fn profile_defaults(profile: Profile) -> ProfileFlags {
179 profile.flags()
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_scope_str() {
188 assert_eq!(Scope::Added.as_str(), "added");
189 assert_eq!(Scope::Touched.as_str(), "touched");
190 }
191
192 #[test]
193 fn test_profile_flags_have_expected_thresholds() {
194 assert_eq!(profile_defaults(Profile::Oss).threshold_pct, 70.0);
195 assert_eq!(profile_defaults(Profile::Moderate).threshold_pct, 75.0);
196 assert_eq!(profile_defaults(Profile::Team).threshold_pct, 80.0);
197 assert_eq!(profile_defaults(Profile::Strict).threshold_pct, 90.0);
198 assert_eq!(profile_defaults(Profile::Lenient).threshold_pct, 0.0);
199 }
200
201 #[test]
202 fn test_profile_from_name_parsing() {
203 assert_eq!(Profile::from_str("OSS").ok(), Some(Profile::Oss));
204 assert_eq!(Profile::from_str("lenient").ok(), Some(Profile::Lenient));
205 assert_eq!(profile_from_name("unknown"), None);
206 }
207
208 #[test]
209 fn test_profile_default_behavior() {
210 assert_eq!(profile_defaults(Profile::Lenient).fail_on, FailOn::Never);
211 assert_eq!(
212 profile_defaults(Profile::Strict).max_uncovered_lines,
213 Some(5)
214 );
215 assert_eq!(profile_defaults(Profile::Strict).scope, Scope::Touched);
216 }
217}