wary/options/
lowercase.rs1use crate::toolbox::rule::*;
6
7#[doc(hidden)]
8pub type Rule<Mode> = Lowercase<Mode>;
9#[doc(hidden)]
10pub type Transformer<Mode> = Lowercase<Mode>;
11
12#[derive(Debug, thiserror::Error, PartialEq)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14pub enum Error {
15 #[error("expected lowercase character at position {position}")]
16 Lowercase { position: usize },
17}
18
19impl Error {
20 #[must_use]
21 pub(crate) fn code(&self) -> &'static str {
22 match self {
23 Self::Lowercase { .. } => "lowercase",
24 }
25 }
26
27 #[cfg(feature = "alloc")]
28 #[must_use]
29 pub(crate) fn message(&self) -> Cow<'static, str> {
30 match self {
31 Self::Lowercase { position } => {
32 format!("expected lowercase character at position {position}")
33 }
34 }
35 .into()
36 }
37
38 #[cfg(not(feature = "alloc"))]
39 pub(crate) fn message(&self) -> &'static str {
40 match self {
41 Self::Lowercase { .. } => "expected lowercase character",
42 }
43 }
44}
45
46pub struct Ascii;
47
48#[must_use]
75pub struct Lowercase<Mode> {
76 mode: PhantomData<Mode>,
77}
78
79impl Lowercase<Unset> {
80 #[inline]
81 pub const fn new() -> Self {
82 Self { mode: PhantomData }
83 }
84
85 #[inline]
94 pub const fn ascii(self) -> Lowercase<Ascii> {
95 Lowercase { mode: PhantomData }
96 }
97}
98
99impl<I: ?Sized> crate::Rule<I> for Lowercase<Unset>
100where
101 I: AsRef<str>,
102{
103 type Context = ();
104
105 #[inline]
106 fn validate(&self, _ctx: &Self::Context, item: &I) -> Result<()> {
107 let string = item.as_ref();
108
109 for (idx, ch) in string.chars().enumerate() {
110 if !ch.is_lowercase() && !ch.is_whitespace() {
111 return Err(Error::Lowercase { position: idx }.into());
112 }
113 }
114
115 Ok(())
116 }
117}
118
119impl<I: ?Sized> crate::Rule<I> for Lowercase<Ascii>
120where
121 I: AsRef<str>,
122{
123 type Context = ();
124
125 #[inline]
126 fn validate(&self, _ctx: &Self::Context, item: &I) -> Result<()> {
127 let string = item.as_ref();
128
129 for (idx, ch) in string.chars().enumerate() {
130 if !ch.is_ascii_lowercase() && !ch.is_ascii_whitespace() {
131 return Err(Error::Lowercase { position: idx }.into());
132 }
133 }
134
135 Ok(())
136 }
137}
138
139#[cfg(feature = "alloc")]
140impl crate::Transformer<String> for Lowercase<Unset> {
141 type Context = ();
142
143 #[inline]
144 fn transform(&self, _ctx: &Self::Context, item: &mut String) {
145 *item = item.to_lowercase();
146 }
147}
148
149impl<I> crate::Transformer<I> for Lowercase<Ascii>
150where
151 I: AsMut<str>,
152{
153 type Context = ();
154
155 #[inline]
156 fn transform(&self, _ctx: &Self::Context, item: &mut I) {
157 item.as_mut().make_ascii_lowercase();
158 }
159}
160
161#[cfg(test)]
162mod test {
163 use super::Lowercase;
164 use crate::toolbox::test::*;
165
166 #[test]
167 fn test_lowercase_rule() {
168 let rule = Lowercase::new();
169 let input = "ὈΔΥΣΣΕΎΣ hello".to_string();
170
171 assert!(rule.validate(&(), &input).is_err());
172
173 let rule = Lowercase::new().ascii();
174 let input = "ὈΔΥΣΣΕΎΣ".to_string();
175
176 assert!(rule.validate(&(), &input).is_err());
177
178 let rule = Lowercase::new().ascii();
179 let input = "hello world".to_string();
180
181 assert!(rule.validate(&(), &input).is_ok());
182 }
183
184 #[test]
185 fn test_lowercase_transformer() {
186 let rule = Lowercase::new();
187 let mut input = "ὈΔΥΣΣΕΎΣ HELLO".to_string();
188
189 rule.transform(&(), &mut input);
190 assert_eq!(input, "ὀδυσσεύς hello");
191
192 let rule = Lowercase::new().ascii();
193 let mut input = "ßeLLO".to_string();
194
195 rule.transform(&(), &mut input);
196 assert_eq!(input, "ßello");
197 }
198}