1use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
4
5pub type FieldMapperFunc = Arc<dyn Fn(&str) -> String + Send + Sync + 'static>;
7
8fn identity_impl(s: &str) -> String {
9 s.to_string()
10}
11
12static IDENTITY_MAPPER: OnceLock<FieldMapperFunc> = OnceLock::new();
13
14pub fn identity_mapper() -> FieldMapperFunc {
16 IDENTITY_MAPPER
17 .get_or_init(|| Arc::new(identity_impl))
18 .clone()
19}
20
21static DEFAULT_FIELD_MAPPER: OnceLock<Mutex<FieldMapperFunc>> = OnceLock::new();
22static DEFAULT_FIELD_MAPPER_LOCK: Mutex<()> = Mutex::new(());
23
24fn mapper_cell() -> &'static Mutex<FieldMapperFunc> {
25 DEFAULT_FIELD_MAPPER.get_or_init(|| Mutex::new(identity_mapper()))
26}
27
28pub fn default_field_mapper() -> FieldMapperFunc {
30 mapper_cell()
31 .lock()
32 .unwrap_or_else(|e| e.into_inner())
33 .clone()
34}
35
36pub fn set_default_field_mapper(mapper: FieldMapperFunc) -> FieldMapperFunc {
38 let mut g = mapper_cell().lock().unwrap_or_else(|e| e.into_inner());
39 std::mem::replace(&mut *g, mapper)
40}
41
42pub struct DefaultFieldMapperGuard {
44 _lock: MutexGuard<'static, ()>,
45 old: FieldMapperFunc,
46}
47
48impl Drop for DefaultFieldMapperGuard {
49 fn drop(&mut self) {
50 let _ = set_default_field_mapper(self.old.clone());
51 }
52}
53
54pub fn set_default_field_mapper_scoped(mapper: FieldMapperFunc) -> DefaultFieldMapperGuard {
56 let lock = DEFAULT_FIELD_MAPPER_LOCK
57 .lock()
58 .unwrap_or_else(|e| e.into_inner());
59 let old = set_default_field_mapper(mapper);
60 DefaultFieldMapperGuard { _lock: lock, old }
61}
62
63fn convert_with_separator(s: &str, sep: char) -> String {
64 let mut out = String::with_capacity(s.len() + 8);
65 let mut prev: Option<char> = None;
66 let chars: Vec<char> = s.chars().collect();
67
68 for (i, &c) in chars.iter().enumerate() {
69 let next = chars.get(i + 1).copied();
70 let is_upper = c.is_ascii_uppercase();
71
72 if is_upper {
73 if let Some(p) = prev {
74 let prev_is_lower_or_digit = p.is_ascii_lowercase() || p.is_ascii_digit();
75 let prev_is_upper = p.is_ascii_uppercase();
76 let next_is_lower = next.map(|n| n.is_ascii_lowercase()).unwrap_or(false);
77
78 if prev_is_lower_or_digit || (prev_is_upper && next_is_lower) {
79 out.push(sep);
80 }
81 }
82 out.push(c.to_ascii_lowercase());
83 } else {
84 out.push(c);
85 }
86
87 prev = Some(c);
88 }
89
90 out
91}
92
93pub fn snake_case_mapper(s: &str) -> String {
99 convert_with_separator(s, '_')
100}
101
102pub fn kebab_case_mapper(s: &str) -> String {
104 convert_with_separator(s, '-')
105}
106
107pub fn upper_case_mapper(s: &str) -> String {
109 s.to_ascii_uppercase()
110}
111
112pub fn prefix_mapper(prefix: &'static str) -> FieldMapperFunc {
114 Arc::new(move |name| format!("{prefix}{name}"))
115}
116
117pub fn suffix_mapper(suffix: &'static str) -> FieldMapperFunc {
119 Arc::new(move |name| format!("{name}{suffix}"))
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn camel_case_helpers_work() {
128 assert_eq!(snake_case_mapper("FieldName"), "field_name");
129 assert_eq!(kebab_case_mapper("FieldName"), "field-name");
130 }
131
132 #[test]
133 fn upper_case_mapper_changes_case() {
134 assert_eq!(upper_case_mapper("FieldName"), "FIELDNAME");
135 }
136
137 #[test]
138 fn prefix_suffix_mappers_apply() {
139 let prefix = prefix_mapper("db_");
140 let suffix = suffix_mapper("_col");
141 assert_eq!(prefix("FieldName"), "db_FieldName");
142 assert_eq!(suffix("FieldName"), "FieldName_col");
143 }
144}