1use crate::error::{Error, ErrorKind};
2use crate::{Meta, Validator};
3use tanzim_value::{Value, ValueType};
4
5fn is_hostname(host: &str) -> bool {
8 if host.is_empty() || host.len() > 253 {
9 return false;
10 }
11 for label in host.split('.') {
12 let bytes = label.as_bytes();
13 if bytes.is_empty() || bytes.len() > 63 {
14 return false;
15 }
16 if bytes[0] == b'-' || bytes[bytes.len() - 1] == b'-' {
17 return false;
18 }
19 for &byte in bytes {
20 if !byte.is_ascii_alphanumeric() && byte != b'-' {
21 return false;
22 }
23 }
24 }
25 true
26}
27
28fn as_string(value: &mut Value) -> Result<&mut String, Error> {
30 match value {
31 Value::String(text) => Ok(text),
32 other => Err(Error::new(ErrorKind::Type {
33 expected: ValueType::String,
34 found: other.type_name(),
35 })),
36 }
37}
38
39#[derive(Debug, Clone, Default)]
41pub struct Host {
42 meta: Meta,
43}
44
45impl Host {
46 pub fn new() -> Self {
47 Self {
48 meta: Meta::default(),
49 }
50 }
51
52 pub fn with_meta(mut self, meta: Meta) -> Self {
54 self.meta = meta;
55 self
56 }
57}
58
59impl Validator for Host {
60 fn meta(&self) -> &Meta {
61 &self.meta
62 }
63
64 fn meta_mut(&mut self) -> &mut Meta {
65 &mut self.meta
66 }
67
68 fn check(&self, value: &mut Value) -> Result<(), Error> {
69 let text = as_string(value)?;
70 if text.parse::<std::net::IpAddr>().is_ok() || is_hostname(text) {
71 Ok(())
72 } else {
73 Err(Error::new(ErrorKind::Format { expected: "host" }))
74 }
75 }
76}
77
78#[derive(Debug, Clone, Default)]
80pub struct Domain {
81 meta: Meta,
82 require_dot: bool,
83}
84
85impl Domain {
86 pub fn with_meta(mut self, meta: Meta) -> Self {
88 self.meta = meta;
89 self
90 }
91
92 pub fn new() -> Self {
93 Self::default()
94 }
95
96 pub fn require_dot(mut self) -> Self {
98 self.require_dot = true;
99 self
100 }
101}
102
103impl Validator for Domain {
104 fn meta(&self) -> &Meta {
105 &self.meta
106 }
107
108 fn meta_mut(&mut self) -> &mut Meta {
109 &mut self.meta
110 }
111
112 fn check(&self, value: &mut Value) -> Result<(), Error> {
113 let text = as_string(value)?;
114 *text = text.to_lowercase();
115 if !is_hostname(text) || (self.require_dot && !text.contains('.')) {
116 return Err(Error::new(ErrorKind::Format { expected: "domain" }));
117 }
118 Ok(())
119 }
120}
121
122#[derive(Debug, Clone, Default)]
124pub struct Email {
125 meta: Meta,
126}
127
128impl Email {
129 pub fn new() -> Self {
130 Self {
131 meta: Meta::default(),
132 }
133 }
134
135 pub fn with_meta(mut self, meta: Meta) -> Self {
137 self.meta = meta;
138 self
139 }
140}
141
142impl Validator for Email {
143 fn meta(&self) -> &Meta {
144 &self.meta
145 }
146
147 fn meta_mut(&mut self) -> &mut Meta {
148 &mut self.meta
149 }
150
151 fn check(&self, value: &mut Value) -> Result<(), Error> {
152 let text = as_string(value)?;
153 let (local, domain) = match text.rsplit_once('@') {
154 Some(parts) => parts,
155 None => return Err(Error::new(ErrorKind::Format { expected: "email" })),
156 };
157 if local.is_empty() || local.len() > 64 || !is_hostname(domain) || !domain.contains('.') {
158 return Err(Error::new(ErrorKind::Format { expected: "email" }));
159 }
160 *text = format!("{local}@{}", domain.to_lowercase());
161 Ok(())
162 }
163}
164
165#[derive(Debug, Clone)]
167pub struct Port {
168 meta: Meta,
169 allow_zero: bool,
170 privileged_ok: bool,
171}
172
173impl Default for Port {
174 fn default() -> Self {
175 Self {
176 meta: Meta::default(),
177 allow_zero: false,
178 privileged_ok: true,
179 }
180 }
181}
182
183impl Port {
184 pub fn with_meta(mut self, meta: Meta) -> Self {
186 self.meta = meta;
187 self
188 }
189
190 pub fn new() -> Self {
191 Self::default()
192 }
193
194 pub fn allow_zero(mut self) -> Self {
196 self.allow_zero = true;
197 self
198 }
199
200 pub fn privileged_ok(mut self, allowed: bool) -> Self {
202 self.privileged_ok = allowed;
203 self
204 }
205}
206
207impl Validator for Port {
208 fn meta(&self) -> &Meta {
209 &self.meta
210 }
211
212 fn meta_mut(&mut self) -> &mut Meta {
213 &mut self.meta
214 }
215
216 fn check(&self, value: &mut Value) -> Result<(), Error> {
217 let min = if self.allow_zero { 0 } else { 1 };
218 crate::Integer::new().range(min, 65535).validate(value)?;
219 let port = match value.as_int() {
220 Some(port) => port,
221 None => unreachable!("Integer validation produced a non-integer"),
222 };
223 if !self.privileged_ok && (1..1024).contains(&port) {
224 return Err(Error::new(ErrorKind::Format {
225 expected: "non-privileged port (>= 1024)",
226 }));
227 }
228 Ok(())
229 }
230}
231
232#[derive(Debug, Clone, Default)]
234pub struct IpAddr {
235 meta: Meta,
236 v4_only: bool,
237 v6_only: bool,
238}
239
240impl IpAddr {
241 pub fn with_meta(mut self, meta: Meta) -> Self {
243 self.meta = meta;
244 self
245 }
246
247 pub fn new() -> Self {
248 Self::default()
249 }
250
251 pub fn v4_only(mut self) -> Self {
252 self.v4_only = true;
253 self.v6_only = false;
254 self
255 }
256
257 pub fn v6_only(mut self) -> Self {
258 self.v6_only = true;
259 self.v4_only = false;
260 self
261 }
262}
263
264impl Validator for IpAddr {
265 fn meta(&self) -> &Meta {
266 &self.meta
267 }
268
269 fn meta_mut(&mut self) -> &mut Meta {
270 &mut self.meta
271 }
272
273 fn check(&self, value: &mut Value) -> Result<(), Error> {
274 let text = as_string(value)?;
275 let parsed = match text.parse::<std::net::IpAddr>() {
276 Ok(parsed) => parsed,
277 Err(_) => {
278 return Err(Error::new(ErrorKind::Format {
279 expected: "ip address",
280 }));
281 }
282 };
283 if self.v4_only && !parsed.is_ipv4() {
284 return Err(Error::new(ErrorKind::Format {
285 expected: "IPv4 address",
286 }));
287 }
288 if self.v6_only && !parsed.is_ipv6() {
289 return Err(Error::new(ErrorKind::Format {
290 expected: "IPv6 address",
291 }));
292 }
293 Ok(())
294 }
295}
296
297#[derive(Debug, Clone, Default)]
299pub struct SocketAddr {
300 meta: Meta,
301}
302
303impl SocketAddr {
304 pub fn new() -> Self {
305 Self {
306 meta: Meta::default(),
307 }
308 }
309
310 pub fn with_meta(mut self, meta: Meta) -> Self {
312 self.meta = meta;
313 self
314 }
315}
316
317impl Validator for SocketAddr {
318 fn meta(&self) -> &Meta {
319 &self.meta
320 }
321
322 fn meta_mut(&mut self) -> &mut Meta {
323 &mut self.meta
324 }
325
326 fn check(&self, value: &mut Value) -> Result<(), Error> {
327 let text = as_string(value)?;
328 if text.parse::<std::net::SocketAddr>().is_ok() {
329 return Ok(());
330 }
331 if let Some((host, port)) = text.rsplit_once(':') {
333 let port_ok = match port.parse::<u16>() {
334 Ok(number) => number != 0,
335 Err(_) => false,
336 };
337 if port_ok && is_hostname(host) {
338 return Ok(());
339 }
340 }
341 Err(Error::new(ErrorKind::Format {
342 expected: "socket address",
343 }))
344 }
345}
346
347#[cfg(test)]
348mod tests {
349 use super::*;
350
351 fn string(text: &str) -> Value {
352 Value::String(text.to_string())
353 }
354
355 #[test]
356 fn host_accepts_name_and_ip() {
357 assert!(Host::new().validate(&mut string("example.com")).is_ok());
358 assert!(Host::new().validate(&mut string("127.0.0.1")).is_ok());
359 assert!(Host::new().validate(&mut string("bad_host!")).is_err());
360 }
361
362 #[test]
363 fn domain_lowercases_and_requires_dot() {
364 let mut value = string("Example.COM");
365 Domain::new().require_dot().validate(&mut value).unwrap();
366 assert_eq!(value, string("example.com"));
367 assert!(
368 Domain::new()
369 .require_dot()
370 .validate(&mut string("localhost"))
371 .is_err()
372 );
373 }
374
375 #[test]
376 fn email_validates_and_lowercases_domain() {
377 let mut value = string("User@Example.COM");
378 Email::new().validate(&mut value).unwrap();
379 assert_eq!(value, string("User@example.com"));
380 assert!(Email::new().validate(&mut string("nope")).is_err());
381 }
382
383 #[test]
384 fn port_range_and_privileged() {
385 let mut value = string("8080");
386 Port::new().validate(&mut value).unwrap();
387 assert_eq!(value, Value::Int(8080));
388 assert!(Port::new().validate(&mut Value::Int(0)).is_err());
389 assert!(
390 Port::new()
391 .allow_zero()
392 .validate(&mut Value::Int(0))
393 .is_ok()
394 );
395 assert!(
396 Port::new()
397 .privileged_ok(false)
398 .validate(&mut Value::Int(80))
399 .is_err()
400 );
401 }
402
403 #[test]
404 fn ip_addr_family_filter() {
405 assert!(
406 IpAddr::new()
407 .v4_only()
408 .validate(&mut string("10.0.0.1"))
409 .is_ok()
410 );
411 assert!(
412 IpAddr::new()
413 .v4_only()
414 .validate(&mut string("::1"))
415 .is_err()
416 );
417 assert!(IpAddr::new().v6_only().validate(&mut string("::1")).is_ok());
418 }
419
420 #[test]
421 fn socket_addr_forms() {
422 assert!(
423 SocketAddr::new()
424 .validate(&mut string("127.0.0.1:8080"))
425 .is_ok()
426 );
427 assert!(
428 SocketAddr::new()
429 .validate(&mut string("example.com:443"))
430 .is_ok()
431 );
432 assert!(
433 SocketAddr::new()
434 .validate(&mut string("example.com"))
435 .is_err()
436 );
437 }
438}