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