athena_rs 3.3.0

Database gateway API
Documentation
use serde_json::{Map, Value};

/// Convert camelCase strings to snake_case.
///
/// Handles transitions from lowercase to uppercase letters, as well as consecutive
/// uppercase letters followed by lowercase letters (e.g., "HTTPResponse" -> "http_response").
///
/// # Arguments
///
/// * `input` - A string slice in camelCase format
///
/// # Returns
///
/// A `String` converted to snake_case format.
///
/// # Example
///
/// ```ignore
/// assert_eq!(camel_to_snake_case("userId"), "user_id");
/// assert_eq!(camel_to_snake_case("HTTPResponse"), "http_response");
/// assert_eq!(camel_to_snake_case("id"), "id");
/// ```
pub fn camel_to_snake_case(input: &str) -> String {
    let mut snake: String = String::with_capacity(input.len() * 2);
    let mut chars = input.chars().peekable();
    let mut prev_char: Option<char> = None;

    while let Some(c) = chars.next() {
        if c.is_ascii_uppercase() {
            if let Some(prev) = prev_char {
                let prev_is_lower_or_digit: bool =
                    prev.is_ascii_lowercase() || prev.is_ascii_digit();

                let next_is_lower: bool = chars
                    .peek()
                    .map(|next| next.is_ascii_lowercase())
                    .unwrap_or(false);

                if prev_is_lower_or_digit || (prev.is_ascii_uppercase() && next_is_lower) {
                    snake.push('_');
                }
            }
            snake.push(c.to_ascii_lowercase());
        } else {
            snake.push(c);
        }

        prev_char = Some(c);
    }

    snake
}

/// Normalize a single column name by optionally converting it to snake_case.
///
/// If the column is "*" or normalization is not forced, returns the column name unchanged.
/// Otherwise, converts the column name from camelCase to snake_case.
///
/// # Arguments
///
/// * `column` - The column name to normalize
/// * `force` - Whether to force normalization to snake_case
///
/// # Returns
///
/// The normalized column name.
pub fn normalize_column_name(column: &str, force: bool) -> String {
    if !force || column == "*" {
        return column.to_string();
    }

    camel_to_snake_case(column)
}

/// Normalize a comma-separated list of column names to snake_case.
///
/// Splits the input string by commas, trims whitespace, filters empty values,
/// and converts each column name to snake_case if normalization is forced.
///
/// # Arguments
///
/// * `columns` - A comma-separated string of column names
/// * `force` - Whether to force normalization to snake_case
///
/// # Returns
///
/// A comma-separated string of normalized column names.
pub fn normalize_columns_csv(columns: &str, force: bool) -> String {
    if !force {
        return columns.to_string();
    }

    columns
        .split(',')
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(|value| normalize_column_name(value, true))
        .collect::<Vec<_>>()
        .join(",")
}

/// Recursively convert all keys in a JSON value from camelCase to snake_case.
///
/// If normalization is not forced, returns a clone of the original value.
/// For objects, converts all keys to snake_case and recursively processes all values.
/// For arrays, recursively processes each element. Other types are returned unchanged.
///
/// # Arguments
///
/// * `value` - The JSON value to process
/// * `force` - Whether to force key normalization
///
/// # Returns
///
/// A new JSON value with all keys converted to snake_case (if force is true).
pub fn convert_value_keys(value: &Value, force: bool) -> Value {
    if !force {
        return value.clone();
    }

    match value {
        Value::Object(map) => {
            let mut normalized: Map<String, Value> = Map::new();
            for (key, val) in map {
                normalized.insert(camel_to_snake_case(key), convert_value_keys(val, true));
            }
            Value::Object(normalized)
        }

        Value::Array(items) => Value::Array(
            items
                .iter()
                .map(|item| convert_value_keys(item, true))
                .collect(),
        ),
        other => other.clone(),
    }
}

/// Normalize all keys in an array of JSON rows from camelCase to snake_case.
///
/// If normalization is not forced, returns a clone of the input rows.
/// Otherwise, applies key normalization to each row in the array.
///
/// # Arguments
///
/// * `rows` - A slice of JSON values representing rows
/// * `force` - Whether to force key normalization
///
/// # Returns
///
/// A vector of rows with all keys converted to snake_case (if force is true).
pub fn normalize_rows(rows: &[Value], force: bool) -> Vec<Value> {
    if !force {
        return rows.to_vec();
    }

    rows.iter()
        .map(|row| convert_value_keys(row, true))
        .collect()
}