#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::num::NonZeroU64;
use arbitrary::{Arbitrary, Unstructured};
use smartstring::alias::String as SmartString;
use crate::domain::{IdDomain, KeyDomain};
use crate::id::Id;
use crate::key::Key;
impl<'a, D: IdDomain> Arbitrary<'a> for Id<D> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let raw: u64 = u.arbitrary()?;
Ok(Id::from_non_zero(
NonZeroU64::new(raw | 1).expect("raw | 1 is always non-zero"),
))
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
u64::size_hint(depth)
}
}
#[cfg(feature = "uuid")]
impl<'a, D: crate::domain::UuidDomain> Arbitrary<'a> for crate::uuid::Uuid<D> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let bytes: [u8; 16] = u.arbitrary()?;
Ok(Self::from_bytes(bytes))
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<[u8; 16]>::size_hint(depth)
}
}
#[cfg(feature = "ulid")]
impl<'a, D: crate::domain::UlidDomain> Arbitrary<'a> for crate::ulid::Ulid<D> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let bytes: [u8; 16] = u.arbitrary()?;
Ok(Self::from_bytes(bytes))
}
fn size_hint(depth: usize) -> (usize, Option<usize>) {
<[u8; 16]>::size_hint(depth)
}
}
impl<'a, D: KeyDomain> Arbitrary<'a> for Key<D> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
if D::HAS_CUSTOM_VALIDATION {
let examples = D::examples();
if !examples.is_empty() {
let s: &&str = u.choose(examples)?;
return Ok(Key::from(SmartString::from(*s)));
}
}
let alphabet: Vec<char> = (' '..='~')
.filter(|&c| D::allowed_characters(c))
.collect();
if alphabet.is_empty() {
return Err(arbitrary::Error::EmptyChoose);
}
let start_chars: Vec<char> = alphabet
.iter()
.copied()
.filter(|&c| D::allowed_start_character(c))
.collect();
if start_chars.is_empty() {
return Err(arbitrary::Error::EmptyChoose);
}
let min_len = D::min_length().max(1);
let max_len = D::MAX_LENGTH.max(min_len);
let length: usize = u.int_in_range(min_len..=max_len)?;
let mut result = SmartString::new();
let mut prev: Option<char> = None;
for pos in 0..length {
let is_last = pos == length - 1;
let candidates: Vec<char> = match pos {
0 if is_last => start_chars
.iter()
.copied()
.filter(|&c| D::allowed_end_character(c))
.collect(),
0 => start_chars.clone(),
_ => {
let p = prev.expect("prev is set after position 0");
alphabet
.iter()
.copied()
.filter(|&c| D::allowed_consecutive_characters(p, c))
.filter(|&c| !is_last || D::allowed_end_character(c))
.collect()
}
};
if candidates.is_empty() {
return Err(arbitrary::Error::EmptyChoose);
}
let &c = u.choose(&candidates)?;
result.push(c);
prev = Some(c);
}
Key::new(result.as_str()).map_err(|_| arbitrary::Error::IncorrectFormat)
}
fn size_hint(_depth: usize) -> (usize, Option<usize>) {
(1, Some(D::MAX_LENGTH * 4 + 8))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug)]
struct SimpleDomain;
impl crate::domain::Domain for SimpleDomain {
const DOMAIN_NAME: &'static str = "simple";
}
impl KeyDomain for SimpleDomain {
const MAX_LENGTH: usize = 16;
fn allowed_characters(c: char) -> bool {
c.is_ascii_lowercase()
}
}
#[derive(Debug)]
struct ExamplesDomain;
impl crate::domain::Domain for ExamplesDomain {
const DOMAIN_NAME: &'static str = "examples";
}
impl KeyDomain for ExamplesDomain {
const MAX_LENGTH: usize = 32;
const HAS_CUSTOM_VALIDATION: bool = true;
fn examples() -> &'static [&'static str] {
&["foo", "bar", "baz"]
}
fn allowed_characters(c: char) -> bool {
c.is_ascii_lowercase()
}
}
fn arb_from_bytes(data: &[u8]) -> arbitrary::Unstructured<'_> {
arbitrary::Unstructured::new(data)
}
#[test]
fn id_arbitrary_is_non_zero() {
#[derive(Debug)]
struct TestId;
impl crate::domain::Domain for TestId {
const DOMAIN_NAME: &'static str = "test_id";
}
impl crate::domain::IdDomain for TestId {}
let data = [0u8; 64];
let mut u = arb_from_bytes(&data);
let id = Id::<TestId>::arbitrary(&mut u);
if let Ok(id) = id {
assert!(id.get() != 0);
}
}
#[test]
fn key_arbitrary_simple_domain_is_valid() {
let data: Vec<u8> = (0u8..=255).cycle().take(8192).collect();
let mut u = arb_from_bytes(&data);
let mut success_count = 0usize;
for _ in 0..100 {
match Key::<SimpleDomain>::arbitrary(&mut u) {
Ok(key) => {
assert!(
Key::<SimpleDomain>::new(key.as_str()).is_ok(),
"generated key failed re-validation: {key:?}"
);
success_count += 1;
}
Err(arbitrary::Error::NotEnoughData) => break,
Err(e) => panic!("unexpected error: {e:?}"),
}
}
assert!(success_count > 0, "no keys generated");
}
#[test]
fn key_arbitrary_examples_domain_draws_from_examples() {
let data: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
let mut u = arb_from_bytes(&data);
let valid: &[&str] = ExamplesDomain::examples();
let mut count = 0usize;
for _ in 0..50 {
match Key::<ExamplesDomain>::arbitrary(&mut u) {
Ok(key) => {
assert!(
valid.contains(&key.as_str()),
"generated key not in examples: {key:?}"
);
count += 1;
}
Err(arbitrary::Error::NotEnoughData) => break,
Err(e) => panic!("unexpected error: {e:?}"),
}
}
assert!(count > 0);
}
#[test]
fn key_arbitrary_min_eq_max_produces_fixed_length() {
#[derive(Debug)]
struct FixedLen;
impl crate::domain::Domain for FixedLen {
const DOMAIN_NAME: &'static str = "fixed";
}
impl KeyDomain for FixedLen {
const MAX_LENGTH: usize = 4;
fn min_length() -> usize {
4
}
fn allowed_characters(c: char) -> bool {
c == 'a'
}
}
let data: Vec<u8> = (0u8..=255).cycle().take(4096).collect();
let mut u = arb_from_bytes(&data);
for _ in 0..10 {
match Key::<FixedLen>::arbitrary(&mut u) {
Ok(key) => assert_eq!(key.len(), 4),
Err(arbitrary::Error::NotEnoughData) => break,
Err(e) => panic!("{e:?}"),
}
}
}
}