ferroid/runtime/
smol_ulid.rs

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