use std::sync::{Mutex, OnceLock, PoisonError};
use chrono::{DateTime, Utc};
use rand::{Rng, RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng;
use rust_decimal::Decimal;
use uuid::Uuid;
struct FakeState {
rng: ChaCha8Rng,
deterministic: bool,
}
static RNG: OnceLock<Mutex<FakeState>> = OnceLock::new();
const DETERMINISTIC_BASE_EPOCH_SECS: i64 = 1_704_067_200;
fn global() -> &'static Mutex<FakeState> {
RNG.get_or_init(|| {
let seed = std::env::var("AUTUMN_FAKE_SEED")
.ok()
.and_then(|s| s.trim().parse::<u64>().ok());
Mutex::new(seed.map_or_else(
|| FakeState {
rng: ChaCha8Rng::from_os_rng(),
deterministic: false,
},
|seed| FakeState {
rng: ChaCha8Rng::seed_from_u64(seed),
deterministic: true,
},
))
})
}
fn lock_state() -> std::sync::MutexGuard<'static, FakeState> {
global().lock().unwrap_or_else(PoisonError::into_inner)
}
fn with_rng<R>(f: impl FnOnce(&mut ChaCha8Rng) -> R) -> R {
let mut state = lock_state();
f(&mut state.rng)
}
fn is_deterministic() -> bool {
lock_state().deterministic
}
pub fn reseed(seed: u64) {
let mut state = lock_state();
state.rng = ChaCha8Rng::seed_from_u64(seed);
state.deterministic = true;
}
#[doc(hidden)]
pub fn test_serial_guard() -> std::sync::MutexGuard<'static, ()> {
static LOCK: Mutex<()> = Mutex::new(());
LOCK.lock().unwrap_or_else(PoisonError::into_inner)
}
fn pick(list: &[&'static str]) -> &'static str {
debug_assert!(!list.is_empty(), "fake: word list must be non-empty");
let idx = with_rng(|r| r.random_range(0..list.len()));
list[idx]
}
#[must_use]
pub fn first_name() -> String {
pick(FIRST_NAMES).to_string()
}
#[must_use]
pub fn last_name() -> String {
pick(LAST_NAMES).to_string()
}
#[must_use]
pub fn name() -> String {
format!("{} {}", first_name(), last_name())
}
#[must_use]
pub fn username() -> String {
with_rng(|r| {
let first = FIRST_NAMES[r.random_range(0..FIRST_NAMES.len())].to_ascii_lowercase();
let last = LAST_NAMES[r.random_range(0..LAST_NAMES.len())].to_ascii_lowercase();
let n: u32 = r.random_range(0..1000);
format!("{first}{last}{n}")
})
}
#[must_use]
pub fn email() -> String {
with_rng(|r| {
let first = FIRST_NAMES[r.random_range(0..FIRST_NAMES.len())].to_ascii_lowercase();
let n: u32 = r.random_range(0..1000);
let domain = DOMAINS[r.random_range(0..DOMAINS.len())];
format!("{first}{n}@{domain}")
})
}
#[must_use]
pub fn word() -> String {
pick(LOREM).to_string()
}
#[must_use]
pub fn words(n: usize) -> String {
if n == 0 {
return String::new();
}
let mut out = String::with_capacity(n * 8);
with_rng(|r| {
for i in 0..n {
if i > 0 {
out.push(' ');
}
out.push_str(LOREM[r.random_range(0..LOREM.len())]);
}
});
out
}
#[must_use]
pub fn sentence() -> String {
let n = with_rng(|r| r.random_range(4..12));
let mut s = words(n);
if let Some(head) = s.get_mut(0..1) {
head.make_ascii_uppercase();
}
s.push('.');
s
}
#[must_use]
pub fn paragraph() -> String {
let n = with_rng(|r| r.random_range(3..7));
let mut out = String::new();
for i in 0..n {
if i > 0 {
out.push(' ');
}
out.push_str(&sentence());
}
out
}
#[must_use]
pub fn url() -> String {
with_rng(|r| {
let domain = DOMAINS[r.random_range(0..DOMAINS.len())];
let path = LOREM[r.random_range(0..LOREM.len())];
format!("https://{domain}/{path}")
})
}
#[must_use]
pub fn boolean() -> bool {
with_rng(|r| r.random_bool(0.5))
}
#[must_use]
pub fn int_range(lo: i64, hi: i64) -> i64 {
if lo >= hi {
return lo;
}
with_rng(|r| r.random_range(lo..=hi))
}
#[must_use]
pub fn decimal() -> Decimal {
let cents = with_rng(|r| r.random_range(0..1_000_000_i64));
Decimal::new(cents, 2)
}
#[must_use]
pub fn decimal_f64() -> f64 {
with_rng(|r| r.random_range(0.0..10_000.0))
}
#[must_use]
pub fn recent_datetime() -> DateTime<Utc> {
const THIRTY_DAYS_SECS: i64 = 30 * 24 * 60 * 60;
let offset = with_rng(|r| r.random_range(0..THIRTY_DAYS_SECS));
let base = if is_deterministic() {
DateTime::<Utc>::UNIX_EPOCH + chrono::Duration::seconds(DETERMINISTIC_BASE_EPOCH_SECS)
} else {
Utc::now()
};
base - chrono::Duration::seconds(offset)
}
#[must_use]
pub fn uuid() -> Uuid {
let mut bytes = [0u8; 16];
with_rng(|r| r.fill_bytes(&mut bytes));
bytes[6] = (bytes[6] & 0x0F) | 0x40;
bytes[8] = (bytes[8] & 0x3F) | 0x80;
Uuid::from_bytes(bytes)
}
static FIRST_NAMES: &[&str] = &[
"Olivia",
"Liam",
"Emma",
"Noah",
"Ava",
"Oliver",
"Sophia",
"Elijah",
"Isabella",
"James",
"Mia",
"William",
"Amelia",
"Benjamin",
"Harper",
"Lucas",
"Evelyn",
"Henry",
"Abigail",
"Alexander",
"Emily",
"Mason",
"Elizabeth",
"Michael",
"Sofia",
"Ethan",
"Avery",
"Daniel",
"Ella",
"Jacob",
"Scarlett",
"Logan",
"Grace",
"Jackson",
"Chloe",
"Levi",
"Victoria",
"Sebastian",
"Riley",
"Mateo",
"Aria",
"Jack",
"Lily",
"Owen",
"Aubrey",
"Theodore",
"Zoey",
"Aiden",
"Penelope",
"Samuel",
"Lillian",
"Joseph",
"Addison",
"John",
"Layla",
"David",
"Natalie",
"Wyatt",
"Camila",
"Matthew",
"Hannah",
"Luke",
"Brooklyn",
"Asher",
"Zoe",
"Carter",
"Nora",
"Julian",
"Leah",
"Grayson",
"Savannah",
"Leo",
"Audrey",
"Jayden",
"Claire",
"Gabriel",
"Eleanor",
"Isaac",
"Skylar",
"Lincoln",
"Ellie",
"Anthony",
"Samantha",
"Hudson",
"Stella",
"Dylan",
"Paisley",
"Ezra",
"Violet",
"Thomas",
"Mila",
"Charles",
"Allison",
"Christopher",
"Alexa",
"Jaxon",
"Anna",
"Maverick",
"Hazel",
"Josiah",
"Aaliyah",
"Isaiah",
"Ariana",
"Andrew",
"Gabriella",
"Elias",
"Alice",
"Joshua",
"Sarah",
"Nathan",
"Ruby",
"Caleb",
"Eva",
"Ryan",
"Serenity",
"Adrian",
"Autumn",
"Miles",
"Quinn",
"Eli",
"Nova",
];
static LAST_NAMES: &[&str] = &[
"Smith",
"Johnson",
"Williams",
"Brown",
"Jones",
"Garcia",
"Miller",
"Davis",
"Rodriguez",
"Martinez",
"Hernandez",
"Lopez",
"Gonzalez",
"Wilson",
"Anderson",
"Thomas",
"Taylor",
"Moore",
"Jackson",
"Martin",
"Lee",
"Perez",
"Thompson",
"White",
"Harris",
"Sanchez",
"Clark",
"Ramirez",
"Lewis",
"Robinson",
"Walker",
"Young",
"Allen",
"King",
"Wright",
"Scott",
"Torres",
"Nguyen",
"Hill",
"Flores",
"Green",
"Adams",
"Nelson",
"Baker",
"Hall",
"Rivera",
"Campbell",
"Mitchell",
"Carter",
"Roberts",
"Gomez",
"Phillips",
"Evans",
"Turner",
"Diaz",
"Parker",
"Cruz",
"Edwards",
"Collins",
"Reyes",
"Stewart",
"Morris",
"Morales",
"Murphy",
"Cook",
"Rogers",
"Gutierrez",
"Ortiz",
"Morgan",
"Cooper",
"Peterson",
"Bailey",
"Reed",
"Kelly",
"Howard",
"Ramos",
"Kim",
"Cox",
"Ward",
"Richardson",
"Watson",
"Brooks",
"Chavez",
"Wood",
"James",
"Bennett",
"Gray",
"Mendoza",
"Ruiz",
"Hughes",
"Price",
"Alvarez",
"Castillo",
"Sanders",
"Patel",
"Myers",
"Long",
"Ross",
"Foster",
"Jimenez",
"Powell",
"Jenkins",
"Perry",
"Russell",
"Sullivan",
"Bell",
"Coleman",
"Butler",
"Henderson",
"Barnes",
"Gonzales",
"Fisher",
"Vasquez",
"Simmons",
"Romero",
"Jordan",
"Patterson",
"Alexander",
"Hamilton",
"Graham",
"Reynolds",
];
static LOREM: &[&str] = &[
"lorem",
"ipsum",
"dolor",
"sit",
"amet",
"consectetur",
"adipiscing",
"elit",
"sed",
"do",
"eiusmod",
"tempor",
"incididunt",
"ut",
"labore",
"et",
"dolore",
"magna",
"aliqua",
"enim",
"ad",
"minim",
"veniam",
"quis",
"nostrud",
"exercitation",
"ullamco",
"laboris",
"nisi",
"aliquip",
"ex",
"ea",
"commodo",
"consequat",
"duis",
"aute",
"irure",
"in",
"reprehenderit",
"voluptate",
"velit",
"esse",
"cillum",
"eu",
"fugiat",
"nulla",
"pariatur",
"excepteur",
"sint",
"occaecat",
"cupidatat",
"non",
"proident",
"sunt",
"culpa",
"qui",
"officia",
"deserunt",
"mollit",
"anim",
"id",
"est",
"laborum",
"perspiciatis",
"unde",
"omnis",
"iste",
"natus",
"error",
"voluptatem",
"accusantium",
"doloremque",
"laudantium",
"totam",
"rem",
"aperiam",
"eaque",
"ipsa",
"quae",
"ab",
"illo",
"inventore",
"veritatis",
"quasi",
"architecto",
"beatae",
"vitae",
"dicta",
"explicabo",
"nemo",
"ipsam",
"quia",
"voluptas",
"aspernatur",
"aut",
"odit",
"fugit",
"consequuntur",
"magni",
"dolores",
"eos",
"ratione",
"sequi",
"nesciunt",
"neque",
"porro",
"quisquam",
"dolorem",
"adipisci",
"numquam",
"eius",
"modi",
"tempora",
"incidunt",
"magnam",
"quaerat",
"voluptatem",
"minus",
"quod",
"maxime",
"placeat",
"facere",
"possimus",
"assumenda",
"repellendus",
"temporibus",
"quibusdam",
"officiis",
"debitis",
"rerum",
"necessitatibus",
"saepe",
"eveniet",
"voluptates",
"repudiandae",
"recusandae",
"itaque",
"earum",
"hic",
"tenetur",
"sapiente",
"delectus",
"reiciendis",
"voluptatibus",
"maiores",
"alias",
"perferendis",
"doloribus",
"asperiores",
"repellat",
];
static DOMAINS: &[&str] = &[
"example.com",
"example.org",
"example.net",
"test.com",
"mail.com",
"acme.io",
"globex.dev",
"initech.co",
"umbrella.app",
"hooli.tech",
"stark.io",
"wayne.net",
];