zino_core/validation/
mod.rs1use crate::{Map, SharedString, error::Error, extension::JsonObjectExt};
3use smallvec::SmallVec;
4use std::fmt;
5
6mod validator;
7
8pub use validator::{
9 AlphabeticValidator, AlphanumericValidator, AsciiAlphabeticValidator,
10 AsciiAlphanumericValidator, AsciiDigitValidator, AsciiHexdigitValidator,
11 AsciiLowercaseValidator, AsciiUppercaseValidator, AsciiValidator, DateTimeValidator,
12 DateValidator, HostValidator, HostnameValidator, IpAddrValidator, Ipv4AddrValidator,
13 Ipv6AddrValidator, LowercaseValidator, NumericValidator, TimeValidator, UppercaseValidator,
14 UriValidator, UuidValidator, Validator,
15};
16
17#[cfg(feature = "validator-credit-card")]
18pub use validator::CreditCardValidator;
19#[cfg(feature = "validator-email")]
20pub use validator::EmailValidator;
21#[cfg(feature = "validator-phone-number")]
22pub use validator::PhoneNumberValidator;
23#[cfg(feature = "validator-regex")]
24pub use validator::RegexValidator;
25
26#[derive(Debug, Default)]
28pub struct Validation {
29 failed_entries: SmallVec<[(SharedString, Error); 4]>,
30}
31
32impl Validation {
33 #[inline]
35 pub fn new() -> Self {
36 Self {
37 failed_entries: SmallVec::new(),
38 }
39 }
40
41 #[inline]
43 pub fn from_entry(key: impl Into<SharedString>, err: impl Into<Error>) -> Self {
44 let mut entries = SmallVec::new();
45 entries.push((key.into(), err.into()));
46 Self {
47 failed_entries: entries,
48 }
49 }
50
51 #[inline]
53 pub fn record(&mut self, key: impl Into<SharedString>, message: impl Into<SharedString>) {
54 self.failed_entries.push((key.into(), Error::new(message)));
55 }
56
57 #[inline]
59 pub fn record_fail(&mut self, key: impl Into<SharedString>, err: impl Into<Error>) {
60 self.failed_entries.push((key.into(), err.into()));
61 }
62
63 pub fn validate_format(&mut self, key: impl Into<SharedString>, value: &str, format: &str) {
65 match format {
66 "alphabetic" => {
67 if let Err(err) = AlphabeticValidator.validate(value) {
68 self.record_fail(key, err);
69 }
70 }
71 "alphanumeric" => {
72 if let Err(err) = AlphanumericValidator.validate(value) {
73 self.record_fail(key, err);
74 }
75 }
76 "ascii" => {
77 if let Err(err) = AsciiValidator.validate(value) {
78 self.record_fail(key, err);
79 }
80 }
81 "ascii-alphabetic" => {
82 if let Err(err) = AsciiAlphabeticValidator.validate(value) {
83 self.record_fail(key, err);
84 }
85 }
86 "ascii-alphanumeric" => {
87 if let Err(err) = AsciiAlphanumericValidator.validate(value) {
88 self.record_fail(key, err);
89 }
90 }
91 "ascii-digit" => {
92 if let Err(err) = AsciiDigitValidator.validate(value) {
93 self.record_fail(key, err);
94 }
95 }
96 "ascii-hexdigit" => {
97 if let Err(err) = AsciiHexdigitValidator.validate(value) {
98 self.record_fail(key, err);
99 }
100 }
101 "ascii-lowercase" => {
102 if let Err(err) = AsciiLowercaseValidator.validate(value) {
103 self.record_fail(key, err);
104 }
105 }
106 "ascii-uppercase" => {
107 if let Err(err) = AsciiUppercaseValidator.validate(value) {
108 self.record_fail(key, err);
109 }
110 }
111 #[cfg(feature = "validator-credit-card")]
112 "credit-card" => {
113 if let Err(err) = CreditCardValidator.validate(value) {
114 self.record_fail(key, err);
115 }
116 }
117 "date" => {
118 if let Err(err) = DateValidator.validate(value) {
119 self.record_fail(key, err);
120 }
121 }
122 "date-time" => {
123 if let Err(err) = DateTimeValidator.validate(value) {
124 self.record_fail(key, err);
125 }
126 }
127 #[cfg(feature = "validator-email")]
128 "email" => {
129 if let Err(err) = EmailValidator.validate(value) {
130 self.record_fail(key, err);
131 }
132 }
133 "host" => {
134 if let Err(err) = HostValidator.validate(value) {
135 self.record_fail(key, err);
136 }
137 }
138 "hostname" => {
139 if let Err(err) = HostnameValidator.validate(value) {
140 self.record_fail(key, err);
141 }
142 }
143 "ip" => {
144 if let Err(err) = IpAddrValidator.validate(value) {
145 self.record_fail(key, err);
146 }
147 }
148 "ipv4" => {
149 if let Err(err) = Ipv4AddrValidator.validate(value) {
150 self.record_fail(key, err);
151 }
152 }
153 "ipv6" => {
154 if let Err(err) = Ipv6AddrValidator.validate(value) {
155 self.record_fail(key, err);
156 }
157 }
158 "lowercase" => {
159 if let Err(err) = LowercaseValidator.validate(value) {
160 self.record_fail(key, err);
161 }
162 }
163 "numeric" => {
164 if let Err(err) = NumericValidator.validate(value) {
165 self.record_fail(key, err);
166 }
167 }
168 #[cfg(feature = "validator-phone-number")]
169 "phone-number" => {
170 if let Err(err) = PhoneNumberValidator.validate(value) {
171 self.record_fail(key, err);
172 }
173 }
174 #[cfg(feature = "validator-regex")]
175 "regex" => {
176 if let Err(err) = RegexValidator.validate(value) {
177 self.record_fail(key, err);
178 }
179 }
180 "time" => {
181 if let Err(err) = TimeValidator.validate(value) {
182 self.record_fail(key, err);
183 }
184 }
185 "uppercase" => {
186 if let Err(err) = UppercaseValidator.validate(value) {
187 self.record_fail(key, err);
188 }
189 }
190 "uri" => {
191 if let Err(err) = UriValidator.validate(value) {
192 self.record_fail(key, err);
193 }
194 }
195 "uuid" => {
196 if let Err(err) = UuidValidator.validate(value) {
197 self.record_fail(key, err);
198 }
199 }
200 _ => {
201 let field = key.into();
202 tracing::warn!("unsupported format `{format}` for the field `{field}`");
203 }
204 }
205 }
206
207 #[inline]
209 pub fn contains_key(&self, key: &str) -> bool {
210 self.failed_entries.iter().any(|(field, _)| field == key)
211 }
212
213 #[inline]
215 pub fn is_success(&self) -> bool {
216 self.failed_entries.is_empty()
217 }
218
219 #[inline]
221 pub fn invalid_params(&self) -> Vec<&str> {
222 self.failed_entries
223 .iter()
224 .map(|entry| entry.0.as_ref())
225 .collect()
226 }
227
228 #[must_use]
230 pub fn into_map(self) -> Map {
231 let mut map = Map::new();
232 for (key, err) in self.failed_entries {
233 let message = err.message();
234 tracing::warn!("invalid value for `{key}`: {message}");
235 map.upsert(key, message);
236 }
237 map
238 }
239}
240
241impl fmt::Display for Validation {
242 #[inline]
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 let failed_entries = &self.failed_entries;
245 let mut errors = Vec::with_capacity(failed_entries.len());
246 for (key, err) in failed_entries {
247 let message = format!("invalid value for `{key}`: {}", err.message());
248 errors.push(message);
249 }
250 errors.join(",").fmt(f)
251 }
252}