use serde::{Deserialize, Serialize};
use super::types::{RustType, SqlType};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldDef {
pub name: String,
pub column_name: String,
pub rust_type: RustType,
pub sql_type: SqlType,
pub nullable: bool,
pub doc: Option<String>,
}
impl FieldDef {
pub fn new(name: &str, rust_type: RustType) -> Self {
let sql_type = rust_type.to_sql_type();
let nullable = rust_type.is_nullable();
let column_name = to_snake_case(name);
Self {
name: name.to_string(),
column_name,
rust_type,
sql_type,
nullable,
doc: None,
}
}
pub fn to_typescript(&self) -> String {
let ts_type = self.rust_type.to_typescript();
let optional = if self.nullable { "?" } else { "" };
format!(" {}{}: {};", to_camel_case(&self.name), optional, ts_type)
}
}
fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, c) in s.chars().enumerate() {
if c.is_uppercase() {
if i > 0 {
result.push('_');
}
result.push(c.to_lowercase().next().unwrap());
} else {
result.push(c);
}
}
result
}
fn to_camel_case(s: &str) -> String {
let mut result = String::new();
let mut capitalize_next = false;
for c in s.chars() {
if c == '_' {
capitalize_next = true;
} else if capitalize_next {
result.push(c.to_uppercase().next().unwrap());
capitalize_next = false;
} else {
result.push(c);
}
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_field_def_basic() {
let field = FieldDef::new("email", RustType::String);
assert_eq!(field.name, "email");
assert_eq!(field.column_name, "email");
assert!(!field.nullable);
}
#[test]
fn test_field_def_nullable() {
let field = FieldDef::new("avatar_url", RustType::Option(Box::new(RustType::String)));
assert!(field.nullable);
}
#[test]
fn test_to_snake_case() {
assert_eq!(to_snake_case("createdAt"), "created_at");
assert_eq!(to_snake_case("userId"), "user_id");
assert_eq!(to_snake_case("HTTPServer"), "h_t_t_p_server");
}
#[test]
fn test_to_camel_case() {
assert_eq!(to_camel_case("created_at"), "createdAt");
assert_eq!(to_camel_case("user_id"), "userId");
}
}