diff --git a/src/core/utils/string.rs b/src/core/utils/string.rs
index 106d0cb77d387631366b2eb5d9a9db26d109099d..fc3891dfb0f7c0afb3fb0b391d6f41126fbf42d9 100644
--- a/src/core/utils/string.rs
+++ b/src/core/utils/string.rs
@@ -1,4 +1,4 @@
-use crate::Result;
+use crate::{utils::exchange, Result};
 
 pub const EMPTY: &str = "";
 
@@ -26,6 +26,41 @@ macro_rules! is_format {
 	};
 }
 
+#[inline]
+#[must_use]
+pub fn camel_to_snake_string(s: &str) -> String {
+	let est_len = s
+		.chars()
+		.fold(s.len(), |est, c| est.saturating_add(usize::from(c.is_ascii_uppercase())));
+
+	let mut ret = String::with_capacity(est_len);
+	camel_to_snake_case(&mut ret, s.as_bytes()).expect("string-to-string stream error");
+	ret
+}
+
+#[inline]
+pub fn camel_to_snake_case<I, O>(output: &mut O, input: I) -> Result<()>
+where
+	I: std::io::Read,
+	O: std::fmt::Write,
+{
+	let mut state = false;
+	input
+		.bytes()
+		.take_while(Result::is_ok)
+		.map(Result::unwrap)
+		.map(char::from)
+		.try_for_each(|ch| {
+			let m = ch.is_ascii_uppercase();
+			let s = exchange(&mut state, !m);
+			if m && s {
+				output.write_char('_')?;
+			}
+			output.write_char(ch.to_ascii_lowercase())?;
+			Result::<()>::Ok(())
+		})
+}
+
 /// Find the common prefix from a collection of strings and return a slice
 /// ```
 /// use conduit_core::utils::string::common_prefix;
diff --git a/src/core/utils/tests.rs b/src/core/utils/tests.rs
index 4396894704a909fad8e3e02a7891b32147565e3f..e91accdf49fcc77c3910177ce5a5e68892964e79 100644
--- a/src/core/utils/tests.rs
+++ b/src/core/utils/tests.rs
@@ -134,3 +134,19 @@ async fn mutex_map_contend() {
 	tokio::try_join!(join_b, join_a).expect("joined");
 	assert!(map.is_empty(), "Must be empty");
 }
+
+#[test]
+fn camel_to_snake_case_0() {
+	use utils::string::camel_to_snake_string;
+
+	let res = camel_to_snake_string("CamelToSnakeCase");
+	assert_eq!(res, "camel_to_snake_case");
+}
+
+#[test]
+fn camel_to_snake_case_1() {
+	use utils::string::camel_to_snake_string;
+
+	let res = camel_to_snake_string("CAmelTOSnakeCase");
+	assert_eq!(res, "camel_tosnake_case");
+}