assertables/assert_email_address/
assert_email_address.rs

1//! Assert expression is possibly an email address.
2//!
3//! # Example
4//!
5//! ```rust
6//! use assertables::*;
7//!
8//! let a = "hello@example.com";
9//! assert_email_address!(a);
10//! ```
11//!
12//! Note: this implementation is relatively basic.
13//!
14//! * If you want more capabilities, then use an email address parser.
15//!
16//! * If you want to know for sure, then send an email to the address.
17//!
18//! # Module macros
19//!
20//! * [`assert_email_address`](macro@crate::assert_email_address)
21//! * [`assert_email_address_as_result`](macro@crate::assert_email_address_as_result)
22//! * [`debug_assert_email_address`](macro@crate::debug_assert_email_address)
23
24/// Assert expression is possibly an email address.
25///
26/// * If true, return Result `Ok(())`.
27///
28/// * Otherwise, return Result `Err(message)`.
29///
30/// This macro is useful for runtime checks, such as checking parameters,
31/// or sanitizing inputs, or handling different results in different ways.
32///
33/// # Module macros
34///
35/// * [`assert_email_address`](macro@crate::assert_email_address)
36/// * [`assert_email_address_as_result`](macro@crate::assert_email_address_as_result)
37/// * [`debug_assert_email_address`](macro@crate::debug_assert_email_address)
38///
39#[macro_export]
40macro_rules! assert_email_address_as_result {
41    ($a:expr $(,)?) => {
42        match (&$a) {
43            a => {
44                if !a.contains("@") {
45                    Err(
46                        format!(
47                            concat!(
48                                "assertion failed: `assert_email_address!(a)`\n",
49                                "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
50                                " email address must contain an '@' at sign.\n",
51                                " a label: `{}`,\n",
52                                " a debug: `{:?}`,\n",
53                                " a: `{}`",
54                            ),
55                            stringify!($a),
56                            $a,
57                            a,
58                        )
59                    )
60                } else {
61                    let parts = a.split("@").collect::<Vec<&str>>();
62                    match parts.len() {
63                        2 => {
64                            let (local_part, domain_part) = (parts[0], parts[1]);
65                            let local_part_len = local_part.len();
66                            let domain_part_len = domain_part.len();
67                            if local_part_len < 1 {
68                                Err(
69                                    format!(
70                                        concat!(
71                                            "assertion failed: `assert_email_address!(a)`\n",
72                                            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
73                                            " email address local part must be 1 character or more.\n",
74                                            " a label: `{}`,\n",
75                                            " a debug: `{:?}`,\n",
76                                            " a: `{}`,\n",
77                                            " local part length: {}"
78                                        ),
79                                        stringify!($a),
80                                        $a,
81                                        a,
82                                        local_part_len,
83                                    )
84                                )
85                            }
86                            else
87                            if local_part_len > 64 {
88                                Err(
89                                    format!(
90                                        concat!(
91                                            "assertion failed: `assert_email_address!(a)`\n",
92                                            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
93                                            " email address local part must be maximum 64 characters.\n",
94                                            " a label: `{}`,\n",
95                                            " a debug: `{:?}`,\n",
96                                            " a: `{}`,\n",
97                                            " local part length: {}"
98                                        ),
99                                        stringify!($a),
100                                        $a,
101                                        a,
102                                        local_part_len,
103                                    )
104                                )
105                            }
106                            else
107                            if domain_part.len() < 1 {
108                                Err(
109                                    format!(
110                                        concat!(
111                                            "assertion failed: `assert_email_address!(a)`\n",
112                                            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
113                                            " email address domain part must be 1 character or more.\n",
114                                            " a label: `{}`,\n",
115                                            " a debug: `{:?}`,\n",
116                                            " a: `{}`,\n",
117                                            " domain part length: {}"
118                                        ),
119                                        stringify!($a),
120                                        $a,
121                                        a,
122                                        domain_part_len,
123                                    )
124                                )
125                            }
126                            else
127                            if domain_part.len() > 255 {
128                                Err(
129                                    format!(
130                                        concat!(
131                                            "assertion failed: `assert_email_address!(a)`\n",
132                                            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
133                                            " email address domain part must be maximum 255 characters.\n",
134                                            " a label: `{}`,\n",
135                                            " a debug: `{:?}`,\n",
136                                            " a: `{}`,\n",
137                                            " domain part length: {}"
138                                        ),
139                                        stringify!($a),
140                                        $a,
141                                        a,
142                                        domain_part_len,
143                                    )
144                                )
145                            }
146                            else {
147                                Ok(())
148                            }
149                        },
150                        _ => {
151                            Err(
152                                format!(
153                                    concat!(
154                                        "assertion failed: `assert_email_address!(a)`\n",
155                                        "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
156                                        " email address must contain a local part, then an '@' at sign, then a domain part.\n",
157                                        " a label: `{}`,\n",
158                                        " a debug: `{:?}`,\n",
159                                        " a: `{}`",
160                                    ),
161                                    stringify!($a),
162                                    $a,
163                                    a,
164                                )
165                            )
166                        }
167                    }
168                }
169            }
170        }
171    };
172}
173
174#[cfg(test)]
175mod test_assert_email_address_as_result {
176    use std::sync::Once;
177
178    #[test]
179    fn success() {
180        let a = "hello@example.com";
181        for _ in 0..1 {
182            let actual = assert_email_address_as_result!(a);
183            assert_eq!(actual, Ok(()));
184        }
185    }
186
187    #[test]
188    fn success_once() {
189        static A: Once = Once::new();
190        fn a() -> &'static str {
191            if A.is_completed() {
192                panic!("A.is_completed()")
193            } else {
194                A.call_once(|| {})
195            }
196            "hello@example.com"
197        }
198
199        assert_eq!(A.is_completed(), false);
200        let result = assert_email_address_as_result!(a());
201        assert!(result.is_ok());
202        assert_eq!(A.is_completed(), true);
203    }
204
205    #[test]
206    fn failure_because_at_sign_is_absent() {
207        let a = "hello*example.com";
208        let actual = assert_email_address_as_result!(a);
209        let message = concat!(
210            "assertion failed: `assert_email_address!(a)`\n",
211            "https://docs.rs/assertables/",
212            env!("CARGO_PKG_VERSION"),
213            "/assertables/macro.assert_email_address.html\n",
214            " email address must contain an '@' at sign.\n",
215            " a label: `a`,\n",
216            " a debug: `\"hello*example.com\"`,\n",
217            " a: `hello*example.com`",
218        );
219        assert_eq!(actual.unwrap_err(), message);
220    }
221
222    #[test]
223    fn failure_because_local_part_is_blank() {
224        let a = "@example.com";
225        let actual = assert_email_address_as_result!(a);
226        let message = concat!(
227            "assertion failed: `assert_email_address!(a)`\n",
228            "https://docs.rs/assertables/",
229            env!("CARGO_PKG_VERSION"),
230            "/assertables/macro.assert_email_address.html\n",
231            " email address local part must be 1 character or more.\n",
232            " a label: `a`,\n",
233            " a debug: `\"@example.com\"`,\n",
234            " a: `@example.com`,\n",
235            " local part length: 0",
236        );
237        assert_eq!(actual.unwrap_err(), message);
238    }
239
240    #[test]
241    fn failure_because_local_part_is_too_long() {
242        let a = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com";
243        let actual = assert_email_address_as_result!(a);
244        let message = concat!(
245            "assertion failed: `assert_email_address!(a)`\n",
246            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
247            " email address local part must be maximum 64 characters.\n",
248            " a label: `a`,\n",
249            " a debug: `\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com\"`,\n",
250            " a: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com`,\n",
251            " local part length: 65",
252        );
253        assert_eq!(actual.unwrap_err(), message);
254    }
255
256    #[test]
257    fn failure_because_domain_part_is_blank() {
258        let a = "hello@";
259        let actual = assert_email_address_as_result!(a);
260        let message = concat!(
261            "assertion failed: `assert_email_address!(a)`\n",
262            "https://docs.rs/assertables/",
263            env!("CARGO_PKG_VERSION"),
264            "/assertables/macro.assert_email_address.html\n",
265            " email address domain part must be 1 character or more.\n",
266            " a label: `a`,\n",
267            " a debug: `\"hello@\"`,\n",
268            " a: `hello@`,\n",
269            " domain part length: 0",
270        );
271        assert_eq!(actual.unwrap_err(), message);
272    }
273
274    #[test]
275    fn failure_because_domain_part_is_too_long() {
276        let a = "hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
277        let actual = assert_email_address_as_result!(a);
278        let message = concat!(
279            "assertion failed: `assert_email_address!(a)`\n",
280            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
281            " email address domain part must be maximum 255 characters.\n",
282            " a label: `a`,\n",
283            " a debug: `\"hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"`,\n",
284            " a: `hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,\n",
285            " domain part length: 256"
286        );
287        assert_eq!(actual.unwrap_err(), message);
288    }
289}
290
291/// Assert expression is possibly an email address.
292///
293/// * If true, return `()`.
294///
295/// * Otherwise, call [`panic!`] with a message and the values of the
296///   expressions with their debug representations.
297///
298/// # Examples
299///
300/// ```rust
301/// use assertables::*;
302/// # use std::panic;
303///
304/// # fn main() {
305/// let a = "hello@example.com";
306/// assert_email_address!(a);
307///
308/// # let result = panic::catch_unwind(|| {
309/// // This will panic
310/// let a = "hello*example.com";
311/// assert_email_address!(a);
312/// # });
313/// // assertion failed: `assert_email_address!(a)`
314/// // https://docs.rs/assertables/9.7.0/assertables/macro.assert_email_address.html
315/// //  Email address must contain an '@' at sign.
316/// //  a label: `a`,
317/// //  a debug: `\"hello*example.com\"`,
318/// //  a: `hello*example.com`
319/// # let actual = result.unwrap_err().downcast::<String>().unwrap().to_string();
320/// # let message = concat!(
321/// #     "assertion failed: `assert_email_address!(a)`\n",
322/// #     "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
323/// #     " email address must contain an '@' at sign.\n",
324/// #     " a label: `a`,\n",
325/// #     " a debug: `\"hello*example.com\"`,\n",
326/// #     " a: `hello*example.com`",
327/// # );
328/// # assert_eq!(actual, message);
329/// # }
330/// ```
331///
332/// # Module macros
333///
334/// * [`assert_email_address`](macro@crate::assert_email_address)
335/// * [`assert_email_address_as_result`](macro@crate::assert_email_address_as_result)
336/// * [`debug_assert_email_address`](macro@crate::debug_assert_email_address)
337///
338#[macro_export]
339macro_rules! assert_email_address {
340    ($a:expr $(,)?) => {
341        match $crate::assert_email_address_as_result!($a) {
342            Ok(a) => a,
343            Err(err) => panic!("{}", err),
344        }
345    };
346    ($a:expr, $($message:tt)+) => {
347        match $crate::assert_email_address_as_result!($a) {
348            Ok(a) => a,
349            Err(err) => panic!("{}\n{}", format_args!($($message)+), err),
350        }
351    };
352}
353
354#[cfg(test)]
355mod test_assert_email_address {
356    use std::panic;
357
358    #[test]
359    fn success() {
360        let a = "hello@example.com";
361        for _ in 0..1 {
362            let actual = assert_email_address!(a);
363            assert_eq!(actual, ());
364        }
365    }
366
367    #[test]
368    fn failure_because_at_sign_is_absent() {
369        let a = "hello*example.com";
370        let result = panic::catch_unwind(|| {
371            let _actual = assert_email_address!(a);
372        });
373        let message = concat!(
374            "assertion failed: `assert_email_address!(a)`\n",
375            "https://docs.rs/assertables/",
376            env!("CARGO_PKG_VERSION"),
377            "/assertables/macro.assert_email_address.html\n",
378            " email address must contain an '@' at sign.\n",
379            " a label: `a`,\n",
380            " a debug: `\"hello*example.com\"`,\n",
381            " a: `hello*example.com`"
382        );
383        assert_eq!(
384            result
385                .unwrap_err()
386                .downcast::<String>()
387                .unwrap()
388                .to_string(),
389            message
390        );
391    }
392
393    #[test]
394    fn failure_because_local_part_is_blank() {
395        let a = "@example.com";
396        let result = panic::catch_unwind(|| {
397            let _actual = assert_email_address!(a);
398        });
399        let message = concat!(
400            "assertion failed: `assert_email_address!(a)`\n",
401            "https://docs.rs/assertables/",
402            env!("CARGO_PKG_VERSION"),
403            "/assertables/macro.assert_email_address.html\n",
404            " email address local part must be 1 character or more.\n",
405            " a label: `a`,\n",
406            " a debug: `\"@example.com\"`,\n",
407            " a: `@example.com`,\n",
408            " local part length: 0",
409        );
410        assert_eq!(
411            result
412                .unwrap_err()
413                .downcast::<String>()
414                .unwrap()
415                .to_string(),
416            message
417        );
418    }
419
420    #[test]
421    fn failure_because_local_part_is_too_long() {
422        let a = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com";
423        let result = panic::catch_unwind(|| {
424            let _actual = assert_email_address!(a);
425        });
426        let message = concat!(
427            "assertion failed: `assert_email_address!(a)`\n",
428            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
429            " email address local part must be maximum 64 characters.\n",
430            " a label: `a`,\n",
431            " a debug: `\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com\"`,\n",
432            " a: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com`,\n",
433            " local part length: 65",
434        );
435        assert_eq!(
436            result
437                .unwrap_err()
438                .downcast::<String>()
439                .unwrap()
440                .to_string(),
441            message
442        );
443    }
444
445    #[test]
446    fn failure_because_domain_part_is_blank() {
447        let a = "hello@";
448        let result = panic::catch_unwind(|| {
449            let _actual = assert_email_address!(a);
450        });
451        let message = concat!(
452            "assertion failed: `assert_email_address!(a)`\n",
453            "https://docs.rs/assertables/",
454            env!("CARGO_PKG_VERSION"),
455            "/assertables/macro.assert_email_address.html\n",
456            " email address domain part must be 1 character or more.\n",
457            " a label: `a`,\n",
458            " a debug: `\"hello@\"`,\n",
459            " a: `hello@`,\n",
460            " domain part length: 0",
461        );
462        assert_eq!(
463            result
464                .unwrap_err()
465                .downcast::<String>()
466                .unwrap()
467                .to_string(),
468            message
469        );
470    }
471
472    #[test]
473    fn failure_because_domain_part_is_too_long() {
474        let a = "hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
475        let result = panic::catch_unwind(|| {
476            let _actual = assert_email_address!(a);
477        });
478        let message = concat!(
479            "assertion failed: `assert_email_address!(a)`\n",
480            "https://docs.rs/assertables/", env!("CARGO_PKG_VERSION"), "/assertables/macro.assert_email_address.html\n",
481            " email address domain part must be maximum 255 characters.\n",
482            " a label: `a`,\n",
483            " a debug: `\"hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"`,\n",
484            " a: `hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,\n",
485            " domain part length: 256",
486        );
487        assert_eq!(
488            result
489                .unwrap_err()
490                .downcast::<String>()
491                .unwrap()
492                .to_string(),
493            message
494        );
495    }
496}
497
498/// Assert expression is possibly an email address.
499///
500/// This macro provides the same statements as [`assert_email_address`](macro.assert_email_address.html),
501/// except this macro's statements are only enabled in non-optimized
502/// builds by default. An optimized build will not execute this macro's
503/// statements unless `-C debug-assertions` is passed to the compiler.
504///
505/// This macro is useful for checks that are too expensive to be present
506/// in a release build but may be helpful during development.
507///
508/// The result of expanding this macro is always type checked.
509///
510/// An unchecked assertion allows a program in an inconsistent state to
511/// keep running, which might have unexpected consequences but does not
512/// introduce unsafety as long as this only happens in safe code. The
513/// performance cost of assertions, however, is not measurable in general.
514/// Replacing `assert*!` with `debug_assert*!` is thus only encouraged
515/// after thorough profiling, and more importantly, only in safe code!
516///
517/// This macro is intended to work in a similar way to
518/// [`::std::debug_assert`](https://doc.rust-lang.org/std/macro.debug_assert.html).
519///
520/// # Module macros
521///
522/// * [`assert_email_address`](macro@crate::assert_email_address)
523/// * [`assert_email_address_as_result`](macro@crate::assert_email_address_as_result)
524/// * [`debug_assert_email_address`](macro@crate::debug_assert_email_address)
525///
526#[macro_export]
527macro_rules! debug_assert_email_address {
528    ($($arg:tt)*) => {
529        if $crate::cfg!(debug_assertions) {
530            $crate::assert_email_address!($($arg)*);
531        }
532    };
533}