1use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Examples {
16 pub bad: String,
18 pub good: String,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24pub struct RuleCard {
25 pub message: String,
27 pub remediation: String,
29 pub examples: Examples,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum CardProblem {
36 EmptyMessage,
38 EmptyRemediation,
40 EmptyExample,
42 IdenticalExamples,
44}
45
46impl RuleCard {
47 pub fn validate(&self) -> Result<(), Vec<CardProblem>> {
58 let mut problems = Vec::new();
59
60 if self.message.trim().is_empty() {
61 problems.push(CardProblem::EmptyMessage);
62 }
63 if self.remediation.trim().is_empty() {
64 problems.push(CardProblem::EmptyRemediation);
65 }
66 if self.examples.bad.trim().is_empty() || self.examples.good.trim().is_empty() {
67 problems.push(CardProblem::EmptyExample);
68 } else if self.examples.bad.trim() == self.examples.good.trim() {
69 problems.push(CardProblem::IdenticalExamples);
70 }
71
72 if problems.is_empty() {
73 Ok(())
74 } else {
75 Err(problems)
76 }
77 }
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83
84 fn card(message: &str, remediation: &str, bad: &str, good: &str) -> RuleCard {
85 RuleCard {
86 message: message.to_owned(),
87 remediation: remediation.to_owned(),
88 examples: Examples {
89 bad: bad.to_owned(),
90 good: good.to_owned(),
91 },
92 }
93 }
94
95 fn valid() -> RuleCard {
96 card(
97 "Literal numeric size inside makeStyles",
98 "Use theme.spacing.* instead",
99 "padding: 12",
100 "padding: theme.spacing.md",
101 )
102 }
103
104 #[test]
105 fn accepts_a_complete_card() {
106 assert_eq!(valid().validate(), Ok(()));
107 }
108
109 #[test]
110 fn rejects_an_empty_message() {
111 let mut c = valid();
112 c.message = String::new();
113 assert_eq!(c.validate(), Err(vec![CardProblem::EmptyMessage]));
114 }
115
116 #[test]
117 fn rejects_an_empty_remediation() {
118 let mut c = valid();
119 c.remediation = String::new();
120 assert_eq!(c.validate(), Err(vec![CardProblem::EmptyRemediation]));
121 }
122
123 #[test]
124 fn treats_whitespace_as_empty() {
125 let c = card(" ", "\t\n", " ", " ");
127 let problems = c.validate().expect_err("should reject");
128 assert!(problems.contains(&CardProblem::EmptyMessage));
129 assert!(problems.contains(&CardProblem::EmptyRemediation));
130 assert!(problems.contains(&CardProblem::EmptyExample));
131 }
132
133 #[test]
134 fn rejects_examples_that_demonstrate_nothing() {
135 let c = card("msg", "fix", "padding: 12", "padding: 12");
136 assert_eq!(c.validate(), Err(vec![CardProblem::IdenticalExamples]));
137 }
138
139 #[test]
140 fn compares_examples_ignoring_surrounding_whitespace() {
141 let c = card("msg", "fix", " padding: 12 ", "padding: 12");
144 assert_eq!(c.validate(), Err(vec![CardProblem::IdenticalExamples]));
145 }
146
147 #[test]
148 fn reports_every_problem_at_once() {
149 let c = card("", "", "", "");
151 let problems = c.validate().expect_err("should reject");
152 assert_eq!(problems.len(), 3);
153 }
154
155 #[test]
156 fn empty_examples_are_reported_instead_of_identical() {
157 let c = card("msg", "fix", "", "");
160 assert_eq!(c.validate(), Err(vec![CardProblem::EmptyExample]));
161 }
162
163 #[test]
164 fn round_trips_through_json() {
165 let c = valid();
166 let json = serde_json::to_string(&c).expect("serializes");
167 let back: RuleCard = serde_json::from_str(&json).expect("deserializes");
168 assert_eq!(back, c);
169 }
170}