Skip to main content

domain_key/
arbitrary_impls.rs

1//! [`arbitrary::Arbitrary`] impls for domain-key types.
2//!
3//! Enabled by the `arbitrary` feature flag. Provides structured fuzzing support
4//! for [`Key<D>`](crate::Key), [`Id<D>`](crate::Id),
5//! [`Uuid<D>`](crate::Uuid) (requires `uuid` feature), and
6//! [`Ulid<D>`](crate::Ulid) (requires `ulid` feature).
7//!
8//! `Key<D>` generation is constructive: characters are assembled position-by-position
9//! from the domain's [`KeyDomain`](crate::KeyDomain) predicates over the ASCII printable
10//! range (`U+0020–U+007E`). Every generated `Key<D>` is valid by construction for
11//! domains where `HAS_CUSTOM_VALIDATION = false`. For domains with
12//! `HAS_CUSTOM_VALIDATION = true`, the impl draws uniformly from
13//! [`KeyDomain::examples()`](crate::KeyDomain::examples) when examples are provided;
14//! otherwise it falls back to the constructive path with best-effort validity.
15
16#[cfg(not(feature = "std"))]
17use alloc::vec::Vec;
18
19use core::num::NonZeroU64;
20
21use arbitrary::{Arbitrary, Unstructured};
22use smartstring::alias::String as SmartString;
23
24use crate::domain::{IdDomain, KeyDomain};
25use crate::id::Id;
26use crate::key::Key;
27
28// ── Id<D> ────────────────────────────────────────────────────────────────────
29
30impl<'a, D: IdDomain> Arbitrary<'a> for Id<D> {
31    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
32        let raw: u64 = u.arbitrary()?;
33        // Bit-OR with 1 guarantees non-zero without biasing the distribution
34        // significantly (only 1/2^64 values are affected).
35        Ok(Id::from_non_zero(
36            NonZeroU64::new(raw | 1).expect("raw | 1 is always non-zero"),
37        ))
38    }
39
40    fn size_hint(depth: usize) -> (usize, Option<usize>) {
41        u64::size_hint(depth)
42    }
43}
44
45// ── Uuid<D> ──────────────────────────────────────────────────────────────────
46
47/// `arbitrary::Arbitrary` for [`Uuid<D>`](crate::Uuid).
48///
49/// Generates a uniformly random 16-byte UUID. All 128 bits are arbitrary;
50/// the implementation does not force any particular UUID version.
51///
52/// Requires the `uuid` and `arbitrary` features.
53#[cfg(feature = "uuid")]
54impl<'a, D: crate::domain::UuidDomain> Arbitrary<'a> for crate::uuid::Uuid<D> {
55    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
56        let bytes: [u8; 16] = u.arbitrary()?;
57        Ok(Self::from_bytes(bytes))
58    }
59
60    fn size_hint(depth: usize) -> (usize, Option<usize>) {
61        <[u8; 16]>::size_hint(depth)
62    }
63}
64
65// ── Ulid<D> ──────────────────────────────────────────────────────────────────
66
67/// `arbitrary::Arbitrary` for [`Ulid<D>`](crate::Ulid).
68///
69/// Generates a uniformly random 16-byte ULID. All 128 bits are arbitrary;
70/// the implementation does not enforce timestamp monotonicity.
71///
72/// Requires the `ulid` and `arbitrary` features.
73#[cfg(feature = "ulid")]
74impl<'a, D: crate::domain::UlidDomain> Arbitrary<'a> for crate::ulid::Ulid<D> {
75    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
76        let bytes: [u8; 16] = u.arbitrary()?;
77        Ok(Self::from_bytes(bytes))
78    }
79
80    fn size_hint(depth: usize) -> (usize, Option<usize>) {
81        <[u8; 16]>::size_hint(depth)
82    }
83}
84
85// ── Key<D> ───────────────────────────────────────────────────────────────────
86
87impl<'a, D: KeyDomain> Arbitrary<'a> for Key<D> {
88    /// Generates an arbitrary valid [`Key<D>`] value.
89    ///
90    /// # Generation strategy
91    ///
92    /// - **`HAS_CUSTOM_VALIDATION = true` with examples:** draws uniformly from
93    ///   [`KeyDomain::examples()`] (R8a). Examples are trusted to be valid by
94    ///   the domain author's contract.
95    /// - **Constructive path (all other cases):** assembles a key character-by-character
96    ///   over the ASCII printable range (`' '..='~'`, U+0020–U+007E), respecting all
97    ///   [`KeyDomain`] predicates at each position. Returns
98    ///   [`arbitrary::Error::EmptyChoose`] if no valid key can be constructed.
99    fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
100        // R8a: for custom-validation domains with examples, draw from the examples pool.
101        if D::HAS_CUSTOM_VALIDATION {
102            let examples = D::examples();
103            if !examples.is_empty() {
104                let s: &&str = u.choose(examples)?;
105                // examples() contract: the domain author guarantees these are valid.
106                return Ok(Key::from(SmartString::from(*s)));
107            }
108        }
109
110        // Constructive path: enumerate ASCII printable (U+0020–U+007E).
111        let alphabet: Vec<char> = (' '..='~')
112            .filter(|&c| D::allowed_characters(c))
113            .collect();
114
115        if alphabet.is_empty() {
116            return Err(arbitrary::Error::EmptyChoose);
117        }
118
119        let start_chars: Vec<char> = alphabet
120            .iter()
121            .copied()
122            .filter(|&c| D::allowed_start_character(c))
123            .collect();
124
125        if start_chars.is_empty() {
126            return Err(arbitrary::Error::EmptyChoose);
127        }
128
129        let min_len = D::min_length().max(1);
130        let max_len = D::MAX_LENGTH.max(min_len);
131        let length: usize = u.int_in_range(min_len..=max_len)?;
132
133        let mut result = SmartString::new();
134        let mut prev: Option<char> = None;
135
136        for pos in 0..length {
137            let is_last = pos == length - 1;
138
139            let candidates: Vec<char> = match pos {
140                // Single-character key: must satisfy both start and end predicates.
141                0 if is_last => start_chars
142                    .iter()
143                    .copied()
144                    .filter(|&c| D::allowed_end_character(c))
145                    .collect(),
146                // First character of a multi-character key.
147                0 => start_chars.clone(),
148                // Interior or final character.
149                _ => {
150                    let p = prev.expect("prev is set after position 0");
151                    alphabet
152                        .iter()
153                        .copied()
154                        .filter(|&c| D::allowed_consecutive_characters(p, c))
155                        .filter(|&c| !is_last || D::allowed_end_character(c))
156                        .collect()
157                }
158            };
159
160            if candidates.is_empty() {
161                return Err(arbitrary::Error::EmptyChoose);
162            }
163
164            let &c = u.choose(&candidates)?;
165            result.push(c);
166            prev = Some(c);
167        }
168
169        Key::new(result.as_str()).map_err(|_| arbitrary::Error::IncorrectFormat)
170    }
171
172    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
173        // Rough estimate: 1 byte for length selection + up to MAX_LENGTH chars.
174        (1, Some(D::MAX_LENGTH * 4 + 8))
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    // A minimal test domain: only lowercase ASCII letters, length 1–16.
183    #[derive(Debug)]
184    struct SimpleDomain;
185
186    impl crate::domain::Domain for SimpleDomain {
187        const DOMAIN_NAME: &'static str = "simple";
188    }
189
190    impl KeyDomain for SimpleDomain {
191        const MAX_LENGTH: usize = 16;
192
193        fn allowed_characters(c: char) -> bool {
194            c.is_ascii_lowercase()
195        }
196    }
197
198    // A custom-validation domain backed by examples only.
199    #[derive(Debug)]
200    struct ExamplesDomain;
201
202    impl crate::domain::Domain for ExamplesDomain {
203        const DOMAIN_NAME: &'static str = "examples";
204    }
205
206    impl KeyDomain for ExamplesDomain {
207        const MAX_LENGTH: usize = 32;
208        const HAS_CUSTOM_VALIDATION: bool = true;
209
210        fn examples() -> &'static [&'static str] {
211            &["foo", "bar", "baz"]
212        }
213
214        fn allowed_characters(c: char) -> bool {
215            c.is_ascii_lowercase()
216        }
217    }
218
219    /// Constructs an `Unstructured` from a repeated-byte buffer; returns None on error.
220    fn arb_from_bytes(data: &[u8]) -> arbitrary::Unstructured<'_> {
221        arbitrary::Unstructured::new(data)
222    }
223
224    #[test]
225    fn id_arbitrary_is_non_zero() {
226        #[derive(Debug)]
227        struct TestId;
228        impl crate::domain::Domain for TestId {
229            const DOMAIN_NAME: &'static str = "test_id";
230        }
231        impl crate::domain::IdDomain for TestId {}
232
233        let data = [0u8; 64];
234        let mut u = arb_from_bytes(&data);
235        // Even all-zero input must produce a non-zero Id.
236        let id = Id::<TestId>::arbitrary(&mut u);
237        if let Ok(id) = id {
238            assert!(id.get() != 0);
239        }
240    }
241
242    #[test]
243    fn key_arbitrary_simple_domain_is_valid() {
244        // Generate 256 keys from arbitrary data; all must be valid for SimpleDomain.
245        let data: Vec<u8> = (0u8..=255).cycle().take(8192).collect();
246        let mut u = arb_from_bytes(&data);
247        let mut success_count = 0usize;
248        for _ in 0..100 {
249            match Key::<SimpleDomain>::arbitrary(&mut u) {
250                Ok(key) => {
251                    // Re-validate via Key::new to confirm correctness.
252                    assert!(
253                        Key::<SimpleDomain>::new(key.as_str()).is_ok(),
254                        "generated key failed re-validation: {key:?}"
255                    );
256                    success_count += 1;
257                }
258                Err(arbitrary::Error::NotEnoughData) => break,
259                Err(e) => panic!("unexpected error: {e:?}"),
260            }
261        }
262        assert!(success_count > 0, "no keys generated");
263    }
264
265    #[test]
266    fn key_arbitrary_examples_domain_draws_from_examples() {
267        let data: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
268        let mut u = arb_from_bytes(&data);
269        let valid: &[&str] = ExamplesDomain::examples();
270        let mut count = 0usize;
271        for _ in 0..50 {
272            match Key::<ExamplesDomain>::arbitrary(&mut u) {
273                Ok(key) => {
274                    assert!(
275                        valid.contains(&key.as_str()),
276                        "generated key not in examples: {key:?}"
277                    );
278                    count += 1;
279                }
280                Err(arbitrary::Error::NotEnoughData) => break,
281                Err(e) => panic!("unexpected error: {e:?}"),
282            }
283        }
284        assert!(count > 0);
285    }
286
287    #[test]
288    fn key_arbitrary_min_eq_max_produces_fixed_length() {
289        #[derive(Debug)]
290        struct FixedLen;
291        impl crate::domain::Domain for FixedLen {
292            const DOMAIN_NAME: &'static str = "fixed";
293        }
294        impl KeyDomain for FixedLen {
295            const MAX_LENGTH: usize = 4;
296            fn min_length() -> usize {
297                4
298            }
299            fn allowed_characters(c: char) -> bool {
300                c == 'a'
301            }
302        }
303
304        let data: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
305        let mut u = arb_from_bytes(&data);
306        for _ in 0..10 {
307            match Key::<FixedLen>::arbitrary(&mut u) {
308                Ok(key) => assert_eq!(key.len(), 4),
309                Err(arbitrary::Error::NotEnoughData) => break,
310                Err(e) => panic!("{e:?}"),
311            }
312        }
313    }
314}
315