Skip to main content

expecto_patronum/
lib.rs

1//! `expecto-patronum` crate provides you a way to cast a patronus if you are in troubles.
2//!
3//! Help is always given at Rust to those who deserve it
4
5#![deny(clippy::all)]
6#![warn(clippy::pedantic)]
7#![warn(clippy::restriction)]
8#![warn(clippy::nursery)]
9#![warn(clippy::cargo)]
10#![allow(clippy::blanket_clippy_restriction_lints)]
11#![allow(clippy::implicit_return)]
12#![allow(clippy::panic)]
13#![allow(clippy::expect_used)]
14#![allow(clippy::arithmetic_side_effects)]
15#![allow(clippy::integer_arithmetic)]
16#![allow(clippy::pub_use)]
17#![cfg_attr(not(feature = "std"), no_std)]
18
19use core::{fmt::Debug, option::Option, result::Result};
20
21// Importing patronus assets generated by build.rs
22include!(concat!(env!("OUT_DIR"), "/assets.rs"));
23
24/// Extension trait for [`Result`] and [`Option`] to provide `expecto_patronum()` method.
25pub trait ExpectoPatronumExt: sealed::Sealed {
26    /// Type of success value.
27    type Success;
28
29    /// Returns the contained successful value or casts a patronus to deliver your panic message.
30    /// Patronus depends on the `msg` you provide.
31    ///
32    /// Use it well.
33    ///
34    /// # Panics
35    ///
36    /// Panics if `self` does not contain successful value.
37    fn expecto_patronum(self, msg: &str) -> Self::Success;
38}
39
40impl<T, E: Debug> ExpectoPatronumExt for Result<T, E> {
41    type Success = T;
42
43    #[inline]
44    fn expecto_patronum(self, msg: &str) -> Self::Success {
45        self.unwrap_or_else(|error| {
46            let patronus = choose_patronus(msg);
47            let panic_msg = construct_panic_msg(patronus, msg);
48            panic!("{panic_msg}: {error:?}")
49        })
50    }
51}
52
53impl<T> ExpectoPatronumExt for Option<T> {
54    type Success = T;
55
56    #[inline]
57    fn expecto_patronum(self, msg: &str) -> Self::Success {
58        self.unwrap_or_else(|| {
59            let patronus = choose_patronus(msg);
60            let panic_msg = construct_panic_msg(patronus, msg);
61            panic!("{panic_msg}")
62        })
63    }
64}
65
66mod sealed {
67    //! Module with [`Sealed`] trait and its implementations.
68
69    /// Sealed trait to restrict [`ExpectoPatronumExt`](super::ExpectoPatronumExt) implementation.
70    pub trait Sealed {}
71
72    impl<T, E: super::Debug> Sealed for super::Result<T, E> {}
73
74    impl<T> Sealed for super::Option<T> {}
75}
76
77/// Type os Patronus string.
78type Patronus = &'static str;
79
80/// Choose a patronus based on the provided `msg`.
81#[allow(clippy::indexing_slicing)]
82fn choose_patronus(msg: &str) -> Patronus {
83    // ASSETS is guaranteed to be non-empty by build.rs
84
85    let n: u128 = fastmurmur3::hash(msg.as_bytes())
86        % u128::try_from(ASSETS.len()).expect("`usize` should fit in `u128`");
87
88    ASSETS[usize::try_from(n).expect("Calculated index should fit in `usize`")]
89}
90
91/// Construct a panic message concatenating provided `patronus` and `msg`.
92fn construct_panic_msg(patronus: Patronus, base_msg: &str) -> String {
93    // Most efficient way according to https://github.com/hoodie/concatenation_benchmarks-rs
94
95    let mut new_msg = String::with_capacity(patronus.len() + base_msg.len() + 1);
96    new_msg.push('\n');
97    new_msg.push_str(patronus);
98    new_msg.push_str(base_msg);
99    new_msg
100}
101
102pub mod prelude {
103    //! Prelude for `expecto-patronum` crate.
104
105    pub use super::ExpectoPatronumExt as _;
106}