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