specta-elm 0.0.1

Yey! now your Rust types in Elm ;)
Documentation
use std::borrow::Cow;

use crate::{Error, elm::RESERVED_TYPE_NAMES};

pub(crate) fn is_elm_identifier(name: &str) -> bool {
    let mut chars = name.chars();
    let Some(first) = chars.next() else {
        return false;
    };

    first.is_ascii_lowercase()
        && chars.all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
        && !RESERVED_TYPE_NAMES.contains(&name)
}

pub(crate) fn sanitise_key(
    field_name: Cow<'static, str>,
    maybe_path: Option<&str>,
) -> Result<Cow<'static, str>, Error> {
    if is_elm_identifier(&field_name) {
        return Ok(field_name);
    }

    let mut chars = field_name.chars();
    if let Some(first) = chars.next()
        && first.is_ascii_uppercase()
    {
        let candidate: String = first.to_ascii_lowercase().to_string() + chars.as_str();
        if is_elm_identifier(&candidate) {
            return Ok(Cow::Owned(candidate));
        }
    }

    Err(Error::invalid_map_key(
        maybe_path.unwrap_or("").to_string(),
        field_name.to_string(),
    ))
}

// pub fn escape_elm_string_literal(value: &str) -> Cow<'_, str> {
//     if !value
//         .chars()
//         .any(|ch| ch == '"' || ch == '\\' || ch.is_control())
//     {
//         return Cow::Borrowed(value);
//     }
//
//     let mut escaped = String::with_capacity(value.len());
//     for ch in value.chars() {
//         match ch {
//             '"' => escaped.push_str("\\\""),
//             '\\' => escaped.push_str("\\\\"),
//             '\n' => escaped.push_str("\\n"),
//             '\r' => escaped.push_str("\\r"),
//             '\t' => escaped.push_str("\\t"),
//             ch if ch.is_control() => escaped.push_str(&format!("\\u{{{:04X}}}", ch as u32)),
//             _ => escaped.push(ch),
//         }
//     }
//
//     Cow::Owned(escaped)
// }