1use nanoid::nanoid;
2use regex::Regex;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum IdType {
7 Event,
8}
9
10#[derive(Debug, Clone)]
12pub struct IdConfig {
13 prefix: &'static str,
14 lowercase: bool,
15 length: usize,
16}
17
18impl IdConfig {
19 const fn new(prefix: &'static str, lowercase: bool, length: usize) -> Self {
20 Self {
21 prefix,
22 lowercase,
23 length,
24 }
25 }
26}
27
28const fn get_id_config(id_type: IdType) -> IdConfig {
30 match id_type {
31 IdType::Event => IdConfig::new("event", false, 28),
32 }
33}
34
35pub const ALPHABET_LOWERCASE: &[char] = &[
36 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i',
37 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
38];
39
40pub const ALPHABET_MIXED_CASE: &[char] = &[
41 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
42 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
43 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u',
44 'v', 'w', 'x', 'y', 'z',
45];
46
47pub fn new_id(id_type: IdType) -> String {
49 let config = get_id_config(id_type);
50 let alphabet = if config.lowercase {
51 ALPHABET_LOWERCASE
52 } else {
53 ALPHABET_MIXED_CASE
54 };
55
56 let length = config.length;
57
58 let id = nanoid!(length, &alphabet);
59
60 format!("{}_{}", config.prefix, id)
61}
62
63pub fn generate_id_example(id_type: IdType) -> String {
65 let config = get_id_config(id_type);
66
67 let char_set = if config.lowercase {
69 ALPHABET_LOWERCASE
70 } else {
71 ALPHABET_MIXED_CASE
72 };
73
74 let suffix_length = config.length;
76
77 let mut seed = 0u32;
79 for c in config.prefix.chars() {
80 seed = (seed.wrapping_mul(31) + c as u32) % 4_294_967_295;
81 }
82
83 let mut suffix = String::with_capacity(suffix_length);
85 for _ in 0..suffix_length {
86 seed = (seed.wrapping_mul(1_664_525) + 1_013_904_223) % 4_294_967_295;
88 let random_value = seed as f64 / 4_294_967_295.0;
89
90 let index = (random_value * char_set.len() as f64).floor() as usize;
91 suffix.push(char_set[index]);
92 }
93
94 format!("{}_{}", config.prefix, suffix)
96}
97
98fn convert_camel_to_spaces(input: &str) -> String {
100 let re = Regex::new(r"([a-z])([A-Z])").unwrap();
101 re.replace_all(input, "$1 $2").to_lowercase()
102}
103
104fn capitalize_first_letter(input: &str) -> String {
106 let mut chars = input.chars();
107 match chars.next() {
108 None => String::new(),
109 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
110 }
111}
112
113pub fn id_regex_pattern(id_type: IdType) -> String {
115 let config = get_id_config(id_type);
116 let alphabet_regex = if config.lowercase {
117 "0-9a-z"
118 } else {
119 "0-9a-zA-Z"
120 };
121
122 format!(
123 "^{}_[{}]{{{}}}$",
124 config.prefix, alphabet_regex, config.length
125 )
126}
127
128pub fn id_error_message(id_type: IdType) -> String {
130 let config = get_id_config(id_type);
131 let type_name = convert_camel_to_spaces(&format!("{:?}", id_type));
132 let alphabet_desc = if config.lowercase {
133 "lowercase letters and numbers"
134 } else {
135 "alphanumeric characters"
136 };
137
138 format!(
139 "{} ID must start with {}_ and can only contain {}.",
140 capitalize_first_letter(&type_name),
141 config.prefix,
142 alphabet_desc
143 )
144}