ferroid/runtime/
smol_ulid.rs

1use crate::{RandSource, Result, SmolSleep, TimeSource, UlidGenerator, UlidId};
2use core::future::Future;
3
4/// Extension trait for asynchronously generating ULIDs using the
5/// [`smol`](https://docs.rs/smol) async runtime.
6///
7/// This trait provides a convenience method for using a [`SleepProvider`]
8/// backed by the `smol` runtime, allowing you to call `.try_next_id_async()`
9/// without needing to specify the sleep strategy manually.
10///
11/// [`SleepProvider`]: crate::SleepProvider
12pub trait UlidGeneratorAsyncSmolExt<ID, T, R>
13where
14    ID: UlidId,
15    T: TimeSource<ID::Ty>,
16    R: RandSource<ID::Ty>,
17{
18    type Err;
19    /// Returns a future that resolves to the next available Ulid using
20    /// the [`SmolSleep`] provider.
21    ///
22    /// Internally delegates to
23    /// [`UlidGeneratorAsyncExt::try_next_id_async`] method with
24    /// [`SmolSleep`] as the sleep strategy.
25    ///
26    /// # Errors
27    ///
28    /// This future may return an error if the underlying generator does.
29    ///
30    /// [`UlidGeneratorAsyncExt::try_next_id_async`]:
31    ///     crate::UlidGeneratorAsyncExt::try_next_id_async
32    fn try_next_id_async(&self) -> impl Future<Output = Result<ID, Self::Err>>;
33}
34
35impl<G, ID, T, R> UlidGeneratorAsyncSmolExt<ID, T, R> for G
36where
37    G: UlidGenerator<ID, T, R>,
38    ID: UlidId,
39    T: TimeSource<ID::Ty>,
40    R: RandSource<ID::Ty>,
41{
42    type Err = G::Err;
43
44    #[allow(clippy::future_not_send)]
45    fn try_next_id_async(&self) -> impl Future<Output = Result<ID, Self::Err>> {
46        <Self as crate::UlidGeneratorAsyncExt<ID, T, R>>::try_next_id_async::<SmolSleep>(self)
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::{
54        LockMonoUlidGenerator, MonotonicClock, RandSource, Result, SleepProvider, SmolYield,
55        ThreadRandom, TimeSource, UlidGenerator, UlidId, ULID,
56    };
57    use futures::future::try_join_all;
58    use smol::Task;
59    use std::collections::HashSet;
60    use std::vec::Vec;
61
62    const TOTAL_IDS: usize = 4096;
63    const NUM_GENERATORS: u64 = 8;
64    const IDS_PER_GENERATOR: usize = TOTAL_IDS * 8;
65
66    // Test the explicit SleepProvider approach
67    #[test]
68    fn generates_many_unique_ids_basic_smol_sleep() {
69        smol::block_on(async {
70            test_many_ulid_unique_ids_explicit::<ULID, _, _, _, SmolSleep>(
71                LockMonoUlidGenerator::new,
72                MonotonicClock::default,
73                ThreadRandom::default,
74            )
75            .await
76            .unwrap();
77        });
78    }
79    #[test]
80    fn generates_many_unique_ids_basic_smol_yield() {
81        smol::block_on(async {
82            test_many_ulid_unique_ids_explicit::<ULID, _, _, _, SmolYield>(
83                LockMonoUlidGenerator::new,
84                MonotonicClock::default,
85                ThreadRandom::default,
86            )
87            .await
88            .unwrap();
89        });
90    }
91    #[test]
92    fn generates_many_unique_ids_basic_smol_convience() {
93        smol::block_on(async {
94            test_many_ulid_unique_ids_convenience::<ULID, _, _, _>(
95                LockMonoUlidGenerator::new,
96                MonotonicClock::default,
97                ThreadRandom::default,
98            )
99            .await
100            .unwrap();
101        });
102    }
103
104    // Helper function for explicit SleepProvider testing
105    async fn test_many_ulid_unique_ids_explicit<ID, G, T, R, S>(
106        generator_fn: impl Fn(T, R) -> G,
107        clock_factory: impl Fn() -> T,
108        rand_factory: impl Fn() -> R,
109    ) -> Result<()>
110    where
111        G: UlidGenerator<ID, T, R> + Send + Sync + 'static,
112        ID: UlidId + Send + 'static,
113        T: TimeSource<ID::Ty> + Clone + Send,
114        R: RandSource<ID::Ty> + Clone + Send,
115        S: SleepProvider,
116    {
117        let clock = clock_factory();
118        let rand = rand_factory();
119        let generators: Vec<_> = (0..NUM_GENERATORS)
120            .map(|_| generator_fn(clock.clone(), rand.clone()))
121            .collect();
122
123        // Test explicit SleepProvider syntax
124        let tasks: Vec<Task<Result<Vec<ID>>>> = generators
125            .into_iter()
126            .map(|g| {
127                smol::spawn(async move {
128                    let mut ids = Vec::with_capacity(IDS_PER_GENERATOR);
129                    for _ in 0..IDS_PER_GENERATOR {
130                        let id = crate::UlidGeneratorAsyncExt::try_next_id_async::<S>(&g)
131                            .await
132                            .unwrap();
133                        ids.push(id);
134                    }
135                    Ok(ids)
136                })
137            })
138            .collect();
139
140        validate_unique_ulid_ids(tasks).await
141    }
142
143    // Helper function for convenience extension trait testing
144    async fn test_many_ulid_unique_ids_convenience<ID, G, T, R>(
145        generator_fn: impl Fn(T, R) -> G,
146        clock_factory: impl Fn() -> T,
147        rand_factory: impl Fn() -> R,
148    ) -> Result<()>
149    where
150        G: UlidGenerator<ID, T, R> + Send + Sync + 'static,
151        G::Err: Send + Sync + 'static,
152        ID: UlidId + Send + 'static,
153        T: TimeSource<ID::Ty> + Clone + Send,
154        R: RandSource<ID::Ty> + Clone + Send,
155    {
156        let clock = clock_factory();
157        let rand = rand_factory();
158        let generators: Vec<_> = (0..NUM_GENERATORS)
159            .map(|_| generator_fn(clock.clone(), rand.clone()))
160            .collect();
161
162        // Test convenience extension trait syntax (uses SmolSleep by default)
163        let tasks: Vec<Task<Result<Vec<ID>>>> = generators
164            .into_iter()
165            .map(|g| {
166                smol::spawn(async move {
167                    let mut ids = Vec::with_capacity(IDS_PER_GENERATOR);
168                    for _ in 0..IDS_PER_GENERATOR {
169                        // This uses the convenience method - no explicit
170                        // SleepProvider type!
171                        let id = g.try_next_id_async().await.unwrap();
172                        ids.push(id);
173                    }
174                    Ok(ids)
175                })
176            })
177            .collect();
178
179        validate_unique_ulid_ids(tasks).await
180    }
181
182    // Helper to validate uniqueness - shared between test approaches
183    async fn validate_unique_ulid_ids(tasks: Vec<Task<Result<Vec<impl UlidId>>>>) -> Result<()> {
184        let all_ids: Vec<_> = try_join_all(tasks).await?.into_iter().flatten().collect();
185
186        #[allow(clippy::cast_possible_truncation)]
187        let expected_total = NUM_GENERATORS as usize * IDS_PER_GENERATOR;
188        assert_eq!(
189            all_ids.len(),
190            expected_total,
191            "Expected {} IDs but got {}",
192            expected_total,
193            all_ids.len()
194        );
195
196        let mut seen = HashSet::with_capacity(all_ids.len());
197        for id in &all_ids {
198            assert!(seen.insert(id), "Duplicate ID found: {id:?}");
199        }
200
201        Ok(())
202    }
203}