Skip to main content

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/9.8.5/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/9.8.5/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/9.8.5/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/9.8.5/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/9.8.5/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/9.8.5/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/9.8.5/assertables/macro.assert_email_address.html\n",
212            " email address must contain an '@' at sign.\n",
213            " a label: `a`,\n",
214            " a debug: `\"hello*example.com\"`,\n",
215            " a: `hello*example.com`",
216        );
217        assert_eq!(actual.unwrap_err(), message);
218    }
219
220    #[test]
221    fn failure_because_local_part_is_blank() {
222        let a = "@example.com";
223        let actual = assert_email_address_as_result!(a);
224        let message = concat!(
225            "assertion failed: `assert_email_address!(a)`\n",
226            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
227            " email address local part must be 1 character or more.\n",
228            " a label: `a`,\n",
229            " a debug: `\"@example.com\"`,\n",
230            " a: `@example.com`,\n",
231            " local part length: 0",
232        );
233        assert_eq!(actual.unwrap_err(), message);
234    }
235
236    #[test]
237    fn failure_because_local_part_is_too_long() {
238        let a = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com";
239        let actual = assert_email_address_as_result!(a);
240        let message = concat!(
241            "assertion failed: `assert_email_address!(a)`\n",
242            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
243            " email address local part must be maximum 64 characters.\n",
244            " a label: `a`,\n",
245            " a debug: `\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com\"`,\n",
246            " a: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com`,\n",
247            " local part length: 65",
248        );
249        assert_eq!(actual.unwrap_err(), message);
250    }
251
252    #[test]
253    fn failure_because_domain_part_is_blank() {
254        let a = "hello@";
255        let actual = assert_email_address_as_result!(a);
256        let message = concat!(
257            "assertion failed: `assert_email_address!(a)`\n",
258            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
259            " email address domain part must be 1 character or more.\n",
260            " a label: `a`,\n",
261            " a debug: `\"hello@\"`,\n",
262            " a: `hello@`,\n",
263            " domain part length: 0",
264        );
265        assert_eq!(actual.unwrap_err(), message);
266    }
267
268    #[test]
269    fn failure_because_domain_part_is_too_long() {
270        let a = "hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
271        let actual = assert_email_address_as_result!(a);
272        let message = concat!(
273            "assertion failed: `assert_email_address!(a)`\n",
274            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
275            " email address domain part must be maximum 255 characters.\n",
276            " a label: `a`,\n",
277            " a debug: `\"hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"`,\n",
278            " a: `hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,\n",
279            " domain part length: 256"
280        );
281        assert_eq!(actual.unwrap_err(), message);
282    }
283}
284
285/// Assert expression is possibly an email address.
286///
287/// * If true, return `()`.
288///
289/// * Otherwise, call [`panic!`] with a message and the values of the
290///   expressions with their debug representations.
291///
292/// # Examples
293///
294/// ```rust
295/// use assertables::*;
296/// # use std::panic;
297///
298/// # fn main() {
299/// let a = "hello@example.com";
300/// assert_email_address!(a);
301///
302/// # let result = panic::catch_unwind(|| {
303/// // This will panic
304/// let a = "hello*example.com";
305/// assert_email_address!(a);
306/// # });
307/// // assertion failed: `assert_email_address!(a)`
308/// // https://docs.rs/assertables/…/assertables/macro.assert_email_address.html
309/// //  Email address must contain an '@' at sign.
310/// //  a label: `a`,
311/// //  a debug: `\"hello*example.com\"`,
312/// //  a: `hello*example.com`
313/// # let actual = result.unwrap_err().downcast::<String>().unwrap().to_string();
314/// # let message = concat!(
315/// #     "assertion failed: `assert_email_address!(a)`\n",
316/// #     "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
317/// #     " email address must contain an '@' at sign.\n",
318/// #     " a label: `a`,\n",
319/// #     " a debug: `\"hello*example.com\"`,\n",
320/// #     " a: `hello*example.com`",
321/// # );
322/// # assert_eq!(actual, message);
323/// # }
324/// ```
325///
326/// # Module macros
327///
328/// * [`assert_email_address`](macro@crate::assert_email_address)
329/// * [`assert_email_address_as_result`](macro@crate::assert_email_address_as_result)
330/// * [`debug_assert_email_address`](macro@crate::debug_assert_email_address)
331///
332#[macro_export]
333macro_rules! assert_email_address {
334    ($a:expr $(,)?) => {
335        match $crate::assert_email_address_as_result!($a) {
336            Ok(a) => a,
337            Err(err) => panic!("{}", err),
338        }
339    };
340    ($a:expr, $($message:tt)+) => {
341        match $crate::assert_email_address_as_result!($a) {
342            Ok(a) => a,
343            Err(err) => panic!("{}\n{}", format_args!($($message)+), err),
344        }
345    };
346}
347
348#[cfg(test)]
349mod test_assert_email_address {
350    use std::panic;
351
352    #[test]
353    fn success() {
354        let a = "hello@example.com";
355        for _ in 0..1 {
356            let actual = assert_email_address!(a);
357            assert_eq!(actual, ());
358        }
359    }
360
361    #[test]
362    fn failure_because_at_sign_is_absent() {
363        let a = "hello*example.com";
364        let result = panic::catch_unwind(|| {
365            let _actual = assert_email_address!(a);
366        });
367        let message = concat!(
368            "assertion failed: `assert_email_address!(a)`\n",
369            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
370            " email address must contain an '@' at sign.\n",
371            " a label: `a`,\n",
372            " a debug: `\"hello*example.com\"`,\n",
373            " a: `hello*example.com`"
374        );
375        assert_eq!(
376            result
377                .unwrap_err()
378                .downcast::<String>()
379                .unwrap()
380                .to_string(),
381            message
382        );
383    }
384
385    #[test]
386    fn failure_because_local_part_is_blank() {
387        let a = "@example.com";
388        let result = panic::catch_unwind(|| {
389            let _actual = assert_email_address!(a);
390        });
391        let message = concat!(
392            "assertion failed: `assert_email_address!(a)`\n",
393            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
394            " email address local part must be 1 character or more.\n",
395            " a label: `a`,\n",
396            " a debug: `\"@example.com\"`,\n",
397            " a: `@example.com`,\n",
398            " local part length: 0",
399        );
400        assert_eq!(
401            result
402                .unwrap_err()
403                .downcast::<String>()
404                .unwrap()
405                .to_string(),
406            message
407        );
408    }
409
410    #[test]
411    fn failure_because_local_part_is_too_long() {
412        let a = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com";
413        let result = panic::catch_unwind(|| {
414            let _actual = assert_email_address!(a);
415        });
416        let message = concat!(
417            "assertion failed: `assert_email_address!(a)`\n",
418            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
419            " email address local part must be maximum 64 characters.\n",
420            " a label: `a`,\n",
421            " a debug: `\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com\"`,\n",
422            " a: `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx@example.com`,\n",
423            " local part length: 65",
424        );
425        assert_eq!(
426            result
427                .unwrap_err()
428                .downcast::<String>()
429                .unwrap()
430                .to_string(),
431            message
432        );
433    }
434
435    #[test]
436    fn failure_because_domain_part_is_blank() {
437        let a = "hello@";
438        let result = panic::catch_unwind(|| {
439            let _actual = assert_email_address!(a);
440        });
441        let message = concat!(
442            "assertion failed: `assert_email_address!(a)`\n",
443            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
444            " email address domain part must be 1 character or more.\n",
445            " a label: `a`,\n",
446            " a debug: `\"hello@\"`,\n",
447            " a: `hello@`,\n",
448            " domain part length: 0",
449        );
450        assert_eq!(
451            result
452                .unwrap_err()
453                .downcast::<String>()
454                .unwrap()
455                .to_string(),
456            message
457        );
458    }
459
460    #[test]
461    fn failure_because_domain_part_is_too_long() {
462        let a = "hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
463        let result = panic::catch_unwind(|| {
464            let _actual = assert_email_address!(a);
465        });
466        let message = concat!(
467            "assertion failed: `assert_email_address!(a)`\n",
468            "https://docs.rs/assertables/9.8.5/assertables/macro.assert_email_address.html\n",
469            " email address domain part must be maximum 255 characters.\n",
470            " a label: `a`,\n",
471            " a debug: `\"hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"`,\n",
472            " a: `hello@xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`,\n",
473            " domain part length: 256",
474        );
475        assert_eq!(
476            result
477                .unwrap_err()
478                .downcast::<String>()
479                .unwrap()
480                .to_string(),
481            message
482        );
483    }
484}
485
486/// Assert expression is possibly an email address.
487///
488/// This macro provides the same statements as [`assert_email_address`](macro.assert_email_address.html),
489/// except this macro's statements are only enabled in non-optimized
490/// builds by default. An optimized build will not execute this macro's
491/// statements unless `-C debug-assertions` is passed to the compiler.
492///
493/// This macro is useful for checks that are too expensive to be present
494/// in a release build but may be helpful during development.
495///
496/// The result of expanding this macro is always type checked.
497///
498/// An unchecked assertion allows a program in an inconsistent state to
499/// keep running, which might have unexpected consequences but does not
500/// introduce unsafety as long as this only happens in safe code. The
501/// performance cost of assertions, however, is not measurable in general.
502/// Replacing `assert*!` with `debug_assert*!` is thus only encouraged
503/// after thorough profiling, and more importantly, only in safe code!
504///
505/// This macro is intended to work in a similar way to
506/// [`::std::debug_assert`](https://doc.rust-lang.org/std/macro.debug_assert.html).
507///
508/// # Module macros
509///
510/// * [`assert_email_address`](macro@crate::assert_email_address)
511/// * [`assert_email_address_as_result`](macro@crate::assert_email_address_as_result)
512/// * [`debug_assert_email_address`](macro@crate::debug_assert_email_address)
513///
514#[macro_export]
515macro_rules! debug_assert_email_address {
516    ($($arg:tt)*) => {
517        if cfg!(debug_assertions) {
518            $crate::assert_email_address!($($arg)*);
519        }
520    };
521}