Skip to main content

tor_basic_utils/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3// @@ begin lint list maintained by maint/add_warning @@
4#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6#![warn(missing_docs)]
7#![warn(noop_method_call)]
8#![warn(unreachable_pub)]
9#![warn(clippy::all)]
10#![deny(clippy::await_holding_lock)]
11#![deny(clippy::cargo_common_metadata)]
12#![deny(clippy::cast_lossless)]
13#![deny(clippy::checked_conversions)]
14#![warn(clippy::cognitive_complexity)]
15#![deny(clippy::debug_assert_with_mut_call)]
16#![deny(clippy::exhaustive_enums)]
17#![deny(clippy::exhaustive_structs)]
18#![deny(clippy::expl_impl_clone_on_copy)]
19#![deny(clippy::fallible_impl_from)]
20#![deny(clippy::implicit_clone)]
21#![deny(clippy::large_stack_arrays)]
22#![warn(clippy::manual_ok_or)]
23#![deny(clippy::missing_docs_in_private_items)]
24#![warn(clippy::needless_borrow)]
25#![warn(clippy::needless_pass_by_value)]
26#![warn(clippy::option_option)]
27#![deny(clippy::print_stderr)]
28#![deny(clippy::print_stdout)]
29#![warn(clippy::rc_buffer)]
30#![deny(clippy::ref_option_ref)]
31#![warn(clippy::semicolon_if_nothing_returned)]
32#![warn(clippy::trait_duplication_in_bounds)]
33#![deny(clippy::unchecked_time_subtraction)]
34#![deny(clippy::unnecessary_wraps)]
35#![warn(clippy::unseparated_literal_suffix)]
36#![deny(clippy::unwrap_used)]
37#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45#![allow(clippy::collapsible_if)] // See arti#2342
46#![deny(clippy::unused_async)]
47//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
48
49use std::fmt;
50use std::ops::{RangeInclusive, RangeToInclusive};
51use std::path::Path;
52use std::time::Duration;
53
54pub mod error_sources;
55pub mod intern;
56pub mod iter;
57pub mod n_key_list;
58pub mod n_key_set;
59pub mod rand_hostname;
60pub mod rangebounds;
61pub mod retry;
62pub mod test_rng;
63
64mod byte_qty;
65pub use byte_qty::ByteQty;
66
67pub use paste::paste;
68
69use rand::Rng;
70
71/// Sealed
72mod sealed {
73    /// Sealed
74    pub trait Sealed {}
75}
76use sealed::Sealed;
77
78// ----------------------------------------------------------------------
79
80/// Function with the signature of `Debug::fmt` that just prints `".."`
81///
82/// ```
83/// use educe::Educe;
84/// use tor_basic_utils::skip_fmt;
85///
86/// #[derive(Educe, Default)]
87/// #[educe(Debug)]
88/// struct Wombat {
89///     visible: usize,
90///
91///     #[educe(Debug(method = "skip_fmt"))]
92///     invisible: [u8; 2],
93/// }
94///
95/// assert_eq!( format!("{:?}", &Wombat::default()),
96///             "Wombat { visible: 0, invisible: .. }" );
97/// ```
98pub fn skip_fmt<T>(_: &T, f: &mut fmt::Formatter) -> fmt::Result {
99    /// Inner function avoids code bloat due to generics
100    fn inner(f: &mut fmt::Formatter) -> fmt::Result {
101        write!(f, "..")
102    }
103    inner(f)
104}
105
106// ----------------------------------------------------------------------
107
108/// Formats an iterator as an object whose display implementation is a `separator`-separated string
109/// of items from `iter`.
110pub fn iter_join(
111    separator: &str,
112    iter: impl Iterator<Item: fmt::Display> + Clone,
113) -> impl fmt::Display {
114    // TODO MSRV 1.93: Replace with `std::fmt::from_fn()`?
115    struct Fmt<'a, I: Iterator<Item: fmt::Display> + Clone> {
116        /// Separates items in `iter`.
117        separator: &'a str,
118        /// Iterator to join.
119        iter: I,
120    }
121    impl<'a, I: Iterator<Item: fmt::Display> + Clone> fmt::Display for Fmt<'a, I> {
122        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123            let Self { separator, iter } = self;
124            let mut iter = iter.clone();
125            if let Some(first) = iter.next() {
126                write!(f, "{first}")?;
127            }
128            for x in iter {
129                write!(f, "{separator}{x}")?;
130            }
131            Ok(())
132        }
133    }
134    Fmt { separator, iter }
135}
136
137// ----------------------------------------------------------------------
138
139/// Extension trait to provide `.strip_suffix_ignore_ascii_case()` etc.
140// Using `.as_ref()` as a supertrait lets us make the method a provided one.
141pub trait StrExt: AsRef<str> {
142    /// Like `str.strip_suffix()` but ASCII-case-insensitive
143    fn strip_suffix_ignore_ascii_case(&self, suffix: &str) -> Option<&str> {
144        let whole = self.as_ref();
145        let suffix_start = whole.len().checked_sub(suffix.len())?;
146        whole[suffix_start..]
147            .eq_ignore_ascii_case(suffix)
148            .then(|| &whole[..suffix_start])
149    }
150
151    /// Like `str.ends_with()` but ASCII-case-insensitive
152    fn ends_with_ignore_ascii_case(&self, suffix: &str) -> bool {
153        self.strip_suffix_ignore_ascii_case(suffix).is_some()
154    }
155}
156impl StrExt for str {}
157
158// ----------------------------------------------------------------------
159
160/// Extension trait to provide `.gen_range_checked()`
161pub trait RngExt: Rng {
162    /// Generate a random value in the given range.
163    ///
164    /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform)  distribution type which may be faster if sampling from the same range repeatedly.
165    ///
166    /// If the supplied range is empty, returns `None`.
167    ///
168    /// (This is a non-panicking version of [`rand::RngExt::random_range`].)
169    ///
170    /// ### Example
171    ///
172    /// ```
173    /// use tor_basic_utils::RngExt as _;
174    //
175    // Fake plastic imitation tor_error, since that's actually higher up the stack
176    /// # #[macro_use]
177    /// # mod tor_error {
178    /// #     #[derive(Debug)]
179    /// #     pub struct Bug;
180    /// #     pub fn internal() {} // makes `use` work
181    /// # }
182    /// # macro_rules! internal { { $x:expr } => { Bug } }
183    //
184    /// use tor_error::{Bug, internal};
185    ///
186    /// fn choose(slice: &[i32]) -> Result<i32, Bug> {
187    ///     let index = rand::rng()
188    ///         .gen_range_checked(0..slice.len())
189    ///         .ok_or_else(|| internal!("empty slice"))?;
190    ///     Ok(slice[index])
191    /// }
192    ///
193    /// assert_eq!(choose(&[42]).unwrap(), 42);
194    /// let _: Bug = choose(&[]).unwrap_err();
195    /// ```
196    //
197    // TODO: We may someday wish to rename this function to random_range_checked,
198    // since gen_range was renamed to random_range in rand 0.9.
199    // Or we might decide to leave it alone.
200    fn gen_range_checked<T, R>(&mut self, range: R) -> Option<T>
201    where
202        T: rand::distr::uniform::SampleUniform,
203        R: rand::distr::uniform::SampleRange<T>,
204    {
205        #[allow(clippy::disallowed_methods)]
206        {
207            // Prove that rand::RngExt::random_range exists.  See arti.git/clippy.toml.
208            let _ = |r: &mut rand::rngs::ThreadRng| rand::RngExt::random_range::<u8, _>(r, 0..10);
209        }
210
211        if range.is_empty() {
212            None
213        } else {
214            use rand::RngExt;
215            #[allow(clippy::disallowed_methods)]
216            Some(self.random_range(range))
217        }
218    }
219
220    /// Generate a random value in the given upper-bounded-only range.
221    ///
222    /// For use with an inclusive upper-bounded-only range,
223    /// with types that implement `GenRangeInfallible`
224    /// (that necessarily then implement the appropriate `rand` traits).
225    ///
226    /// This function is optimised for the case that only a single sample is made from the given range. See also the [`Uniform`](rand::distr::uniform::Uniform)  distribution type which may be faster if sampling from the same range repeatedly.
227    ///
228    /// ### Example
229    ///
230    /// ```
231    /// use std::time::Duration;
232    /// use tor_basic_utils::RngExt as _;
233    ///
234    /// fn stochastic_sleep(max: Duration) {
235    ///     let chosen_delay = rand::rng()
236    ///         .gen_range_infallible(..=max);
237    ///     std::thread::sleep(chosen_delay);
238    /// }
239    /// ```
240    fn gen_range_infallible<T>(&mut self, range: RangeToInclusive<T>) -> T
241    where
242        T: GenRangeInfallible,
243    {
244        self.gen_range_checked(T::lower_bound()..=range.end)
245            .expect("GenRangeInfallible type with an empty lower_bound()..=T range")
246    }
247}
248impl<T: Rng> RngExt for T {}
249
250/// Types that can be infallibly sampled using `gen_range_infallible`
251///
252/// In addition to the supertraits, the implementor of this trait must guarantee that:
253///
254/// `<Self as GenRangeInfallible>::lower_bound() ..= UPPER`
255/// is a nonempty range for every value of `UPPER`.
256//
257// One might think that this trait is wrong because we might want to be able to
258// implement gen_range_infallible for arguments other than RangeToInclusive<T>.
259// However, double-ended ranges are inherently fallible because the actual values
260// might be in the wrong order.  Non-inclusive ranges are fallible because the
261// upper bound might be zero, unless a NonZero type is used, which seems like a further
262// complication that we probably don't want to introduce here.  That leaves lower-bounded
263// ranges, but those are very rare.
264pub trait GenRangeInfallible: rand::distr::uniform::SampleUniform + Ord
265where
266    RangeInclusive<Self>: rand::distr::uniform::SampleRange<Self>,
267{
268    /// The usual lower bound, for converting a `RangeToInclusive` to a `RangeInclusive`
269    ///
270    /// Only makes sense with types with a sensible lower bound, such as zero.
271    fn lower_bound() -> Self;
272}
273
274impl GenRangeInfallible for Duration {
275    fn lower_bound() -> Self {
276        Duration::ZERO
277    }
278}
279
280// ----------------------------------------------------------------------
281
282/// Renaming of `Path::display` as `display_lossy`
283pub trait PathExt: Sealed {
284    /// Display this `Path` as an approximate string, for human consumption in messages
285    ///
286    /// Operating system paths cannot always be faithfully represented as Rust strings,
287    /// because they might not be valid Unicode.
288    ///
289    /// This helper method provides a way to display a string for human users.
290    /// **This may lose information** so should only be used for error messages etc.
291    ///
292    /// This method is exactly the same as [`std::path::Path::display`],
293    /// but with a different and more discouraging name.
294    fn display_lossy(&self) -> std::path::Display<'_>;
295}
296impl Sealed for Path {}
297impl PathExt for Path {
298    #[allow(clippy::disallowed_methods)]
299    fn display_lossy(&self) -> std::path::Display<'_> {
300        self.display()
301    }
302}
303
304// ----------------------------------------------------------------------
305
306/// Define an "accessor trait", which describes structs that have fields of certain types
307///
308/// This can be useful if a large struct, living high up in the dependency graph,
309/// contains fields that lower-lever crates want to be able to use without having
310/// to copy the data about etc.
311///
312/// ```
313/// // imagine this in the lower-level module
314/// pub trait Supertrait {}
315/// use tor_basic_utils::define_accessor_trait;
316/// define_accessor_trait! {
317///     pub trait View: Supertrait {
318///         lorem: String,
319///         ipsum: usize,
320///         +
321///         fn other_accessor(&self) -> bool;
322///         // any other trait items can go here
323///    }
324/// }
325///
326/// fn test_view<V: View>(v: &V) {
327///     assert_eq!(v.lorem(), "sit");
328///     assert_eq!(v.ipsum(), &42);
329/// }
330///
331/// // imagine this in the higher-level module
332/// use derive_more::AsRef;
333/// #[derive(AsRef)]
334/// struct Everything {
335///     #[as_ref] lorem: String,
336///     #[as_ref] ipsum: usize,
337///     dolor: Vec<()>,
338/// }
339/// impl Supertrait for Everything { }
340/// impl View for Everything {
341///     fn other_accessor(&self) -> bool { false }
342/// }
343///
344/// let everything = Everything {
345///     lorem: "sit".into(),
346///     ipsum: 42,
347///     dolor: vec![()],
348/// };
349///
350/// test_view(&everything);
351/// ```
352///
353/// ### Generated code
354///
355/// ```
356/// # pub trait Supertrait { }
357/// pub trait View: AsRef<String> + AsRef<usize> + Supertrait {
358///     fn lorem(&self) -> &String { self.as_ref() }
359///     fn ipsum(&self) -> &usize { self.as_ref() }
360/// }
361/// ```
362#[macro_export]
363macro_rules! define_accessor_trait {
364    {
365        $( #[ $attr:meta ])*
366        $vis:vis trait $Trait:ident $( : $( $Super:path )* )? {
367            $( $accessor:ident: $type:ty, )*
368            $( + $( $rest:tt )* )?
369        }
370    } => {
371        $( #[ $attr ])*
372        $vis trait $Trait: $( core::convert::AsRef<$type> + )* $( $( $Super + )* )?
373        {
374            $(
375                /// Access the field
376                fn $accessor(&self) -> &$type { core::convert::AsRef::as_ref(self) }
377            )*
378            $(
379                $( $rest )*
380            )?
381        }
382    }
383}
384
385// ----------------------------------------------------------------------
386
387/// Helper for assisting with macro "argument" defaulting
388///
389/// ```ignore
390/// macro_first_nonempty!{ [ something ]  ... }  // =>   something
391/// macro_first_nonempty!{ [ ], [ other ] ... }  // =>   other
392/// // etc.
393/// ```
394///
395/// ### Usage note
396///
397/// It is generally possible to avoid use of `macro_first_nonempty`, at the cost of
398/// providing many alternative matcher patterns.  Using `macro_first_nonempty` can make
399/// it possible to provide a single pattern with the optional items in `$( )?`.
400///
401/// This is valuable because a single pattern with some optional items
402/// makes much better documentation than several patterns which the reader must compare
403/// by eye - and it also simplifies the implementation.
404///
405/// `macro_first_nonempty` takes each of its possible expansions in `[ ]` and returns
406/// the first nonempty one.
407#[macro_export]
408macro_rules! macro_first_nonempty {
409    { [ $($yes:tt)+ ] $($rhs:tt)* } => { $($yes)* };
410    { [ ]$(,)? [ $($otherwise:tt)* ] $($rhs:tt)* } => {
411        $crate::macro_first_nonempty!{ [ $($otherwise)* ] $($rhs)* }
412    };
413}
414
415/// Helper for assisting with defining macros that need to expand
416/// conditionally when an argument is empty.
417///
418/// ```ignore
419/// if_empty!{ {   } { x } { y } } // => x
420/// if_empty!{ { z } { x } { y } } // => y
421/// // etc.
422/// ```
423///
424/// Note: The `{ y }` argument may be omitted.
425#[macro_export]
426macro_rules! if_empty {
427    { { }                  { $($x:tt)* } $({ $($y:tt)* })? } => { $($x)* };
428    { { $($nonempty:tt)+ } { $($x:tt)* } $({ $($y:tt)* })? } => { $($($y)*)? };
429}
430
431// ----------------------------------------------------------------------
432
433/// Define `Debug` to print as hex
434///
435/// # Usage
436///
437/// ```ignore
438/// impl_debug_hex! { $type }
439/// impl_debug_hex! { $type . $field_accessor }
440/// impl_debug_hex! { $type , $accessor_fn }
441/// ```
442///
443/// By default, this expects `$type` to implement `AsRef<[u8]>`.
444///
445/// Or, you can supply a series of tokens `$field_accessor`,
446/// which will be used like this: `self.$field_accessor.as_ref()`
447/// to get a `&[u8]`.
448///
449/// Or, you can supply `$accessor: fn(&$type) -> &[u8]`.
450///
451/// # Examples
452///
453/// ```
454/// use tor_basic_utils::impl_debug_hex;
455/// #[derive(Default)]
456/// struct FourBytes([u8; 4]);
457/// impl AsRef<[u8]> for FourBytes { fn as_ref(&self) -> &[u8] { &self.0 } }
458/// impl_debug_hex! { FourBytes }
459///
460/// assert_eq!(
461///     format!("{:?}", FourBytes::default()),
462///     "FourBytes(00000000)",
463/// );
464/// ```
465///
466/// ```
467/// use tor_basic_utils::impl_debug_hex;
468/// #[derive(Default)]
469/// struct FourBytes([u8; 4]);
470/// impl_debug_hex! { FourBytes .0 }
471///
472/// assert_eq!(
473///     format!("{:?}", FourBytes::default()),
474///     "FourBytes(00000000)",
475/// );
476/// ```
477///
478/// ```
479/// use tor_basic_utils::impl_debug_hex;
480/// struct FourBytes([u8; 4]);
481/// impl_debug_hex! { FourBytes, |self_| &self_.0 }
482///
483/// assert_eq!(
484///     format!("{:?}", FourBytes([1,2,3,4])),
485///     "FourBytes(01020304)",
486/// )
487/// ```
488#[macro_export]
489macro_rules! impl_debug_hex {
490    { $type:ty $(,)? } => {
491        $crate::impl_debug_hex! { $type, |self_| <$type as AsRef<[u8]>>::as_ref(&self_) }
492    };
493    { $type:ident . $($accessor:tt)+ } => {
494        $crate::impl_debug_hex! { $type, |self_| self_ . $($accessor)* .as_ref() }
495    };
496    { $type:ty, $obtain:expr $(,)? } => {
497        impl std::fmt::Debug for $type {
498            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
499                use std::fmt::Write;
500                let obtain: fn(&$type) -> &[u8] = $obtain;
501                let bytes: &[u8] = obtain(self);
502                write!(f, "{}(", stringify!($type))?;
503                for b in bytes {
504                    write!(f, "{:02x}", b)?;
505                }
506                write!(f, ")")?;
507                Ok(())
508            }
509        }
510    };
511}
512
513// ----------------------------------------------------------------------
514
515/// Helper for defining a struct which can be (de)serialized several ways, including "natively"
516///
517/// Ideally we would have
518/// ```rust ignore
519/// #[derive(Deserialize)]
520/// #[serde(try_from=Possibilities)]
521/// struct Main { /* principal definition */ }
522///
523/// #[derive(Deserialize)]
524/// #[serde(untagged)]
525/// enum Possibilities { Main(Main), Other(OtherRepr) }
526///
527/// #[derive(Deserialize)]
528/// struct OtherRepr { /* other representation we still want to read */ }
529///
530/// impl TryFrom<Possibilities> for Main { /* ... */ }
531/// ```
532///
533/// But the impl for `Possibilities` ends up honouring the `try_from` on `Main`
534/// so is recursive.
535///
536/// We solve that (ab)using serde's remote feature,
537/// on a second copy of the struct definition.
538///
539/// See the Example for instructions.
540/// It is important to **add test cases**
541/// for all the representations you expect to parse and serialise,
542/// since there are easy-to-write bugs,
543/// for example omitting some of the necessary attributes.
544///
545/// # Generated output:
546///
547///  * The original struct definition, unmodified
548///  * `#[derive(Serialize, Deserialize)] struct $main_Raw { }`
549///
550/// The `$main_Raw` struct ought not normally be to constructed anywhere,
551/// and *isn't* convertible to or from the near-identical `$main` struct.
552/// It exists only as a thing to feed to the serde remove derive,
553/// and name in `with=`.
554///
555/// # Example
556///
557/// ```
558/// use serde::{Deserialize, Serialize};
559/// use tor_basic_utils::derive_serde_raw;
560///
561/// derive_serde_raw! {
562///     #[derive(Deserialize, Serialize, Default, Clone, Debug)]
563///     #[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
564///     pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
565///         transport: Option<String>,
566///         //...
567///     }
568/// }
569///
570/// #[derive(Serialize,Deserialize)]
571/// #[serde(untagged)]
572/// enum BridgeConfigBuilderSerde {
573///     BridgeLine(String),
574///     Dict(#[serde(with="BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
575/// }
576///
577/// impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder { //...
578/// #    type Error = std::io::Error;
579/// #    fn try_from(_: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> { todo!() } }
580/// impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde { //...
581/// #    fn from(_: BridgeConfigBuilder) -> BridgeConfigBuilderSerde { todo!() } }
582/// ```
583#[macro_export]
584macro_rules! derive_serde_raw { {
585    $( #[ $($attrs:meta)* ] )*
586    $vis:vis struct $main:ident=$main_s:literal
587    $($body:tt)*
588} => {
589    $(#[ $($attrs)* ])*
590    $vis struct $main
591    $($body)*
592
593    $crate::paste! {
594        #[allow(non_camel_case_types)]
595        #[derive(Serialize, Deserialize)]
596        #[serde(remote=$main_s)]
597        struct [< $main _Raw >]
598        $($body)*
599    }
600} }
601
602// ----------------------------------------------------------------------
603
604/// Asserts that the type of the expression implements the given trait.
605///
606/// Example:
607///
608/// ```
609/// # use tor_basic_utils::assert_val_impl_trait;
610/// let x: u32 = 0;
611/// assert_val_impl_trait!(x, Clone);
612/// ```
613#[macro_export]
614macro_rules! assert_val_impl_trait {
615    ($check:expr, $trait:path $(,)?) => {{
616        fn ensure_trait<T: $trait>(_s: &T) {}
617        ensure_trait(&$check);
618    }};
619}
620
621// ----------------------------------------------------------------------
622
623#[cfg(test)]
624mod test {
625    // @@ begin test lint list maintained by maint/add_warning @@
626    #![allow(clippy::bool_assert_comparison)]
627    #![allow(clippy::clone_on_copy)]
628    #![allow(clippy::dbg_macro)]
629    #![allow(clippy::mixed_attributes_style)]
630    #![allow(clippy::print_stderr)]
631    #![allow(clippy::print_stdout)]
632    #![allow(clippy::single_char_pattern)]
633    #![allow(clippy::unwrap_used)]
634    #![allow(clippy::unchecked_time_subtraction)]
635    #![allow(clippy::useless_vec)]
636    #![allow(clippy::needless_pass_by_value)]
637    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
638    use super::*;
639
640    #[test]
641    fn test_strip_suffix_ignore_ascii_case() {
642        assert_eq!(
643            "hi there".strip_suffix_ignore_ascii_case("THERE"),
644            Some("hi ")
645        );
646        assert_eq!("hi here".strip_suffix_ignore_ascii_case("THERE"), None);
647        assert_eq!("THERE".strip_suffix_ignore_ascii_case("there"), Some(""));
648        assert_eq!("hi".strip_suffix_ignore_ascii_case("THERE"), None);
649    }
650}