use indexmap::IndexMap;
use serde_yaml::Value;
use crate::models::*;
pub struct ModelLexer;
impl ModelLexer {
pub fn analyze(models: &IndexMap<String, Value>) -> IndexMap<String, ModelDef> {
let mut result = IndexMap::new();
for (name, config) in models {
let mut model = ModelDef::new(name);
if let Value::Mapping(mapping) = config {
for (key, value) in mapping {
let key_str = key.as_str().unwrap_or("");
match key_str {
"table" => {
model.table = value.as_str().map(|s| s.to_string());
}
"timestamps" => {
model.timestamps = Self::parse_bool_or_type(value, &mut model.timestamps_type);
}
"softDeletes" => {
model.soft_deletes = Self::parse_bool_or_type(value, &mut model.soft_delete_type);
}
"primaryKey" | "primary_key" => {
model.primary_key = value.as_str().unwrap_or("id").to_string();
}
"pivot" => {
model.pivot = value.as_bool().unwrap_or(false);
}
"traits" => {
model.traits = Self::parse_list(value);
}
"indexes" => {
model.indexes = Self::parse_indexes(value);
}
"relationships" => {
model.relationships = Self::parse_relationships(value);
}
_ => {
let col_str = value.as_str().unwrap_or("string");
let column = Self::parse_column(key_str, col_str);
model.columns.insert(key_str.to_string(), column);
}
}
}
}
result.insert(name.to_string(), model);
}
result
}
fn parse_column(name: &str, definition: &str) -> Column {
let mut column = Column::new(name);
let tokens: Vec<&str> = definition.split_whitespace().collect();
if tokens.is_empty() {
return column;
}
let type_part = tokens[0];
if let Some(colon_pos) = type_part.find(':') {
column.data_type = type_part[..colon_pos].to_string();
let attr = type_part[colon_pos + 1..].to_string();
column.attributes.push(attr);
} else {
column.data_type = type_part.to_string();
}
Self::detect_relationship(name, &mut column);
for token in &tokens[1..] {
Self::add_modifier(&mut column, token);
}
column
}
fn add_modifier(column: &mut Column, token: &str) {
match token {
"nullable" => {
column.modifiers.push(ColumnModifier {
name: "nullable".to_string(),
value: None,
});
}
"unique" => {
column.modifiers.push(ColumnModifier {
name: "unique".to_string(),
value: None,
});
}
"unsigned" => {
column.modifiers.push(ColumnModifier {
name: "unsigned".to_string(),
value: None,
});
}
_ if token.starts_with("default:") => {
let val = token.strip_prefix("default:").unwrap_or("");
column.modifiers.push(ColumnModifier {
name: "default".to_string(),
value: Some(val.to_string()),
});
}
"autoIncrement" => {
column.modifiers.push(ColumnModifier {
name: "autoIncrement".to_string(),
value: None,
});
}
"foreign" => {
column.is_foreign = true;
column.modifiers.push(ColumnModifier {
name: "foreign".to_string(),
value: None,
});
}
_ if token.starts_with("foreign:") => {
column.is_foreign = true;
let target = token.strip_prefix("foreign:").unwrap_or("");
column.foreign_target = Some(target.to_string());
column.modifiers.push(ColumnModifier {
name: "foreign".to_string(),
value: Some(target.to_string()),
});
}
_ if token.starts_with("onDelete:") => {
let val = token.strip_prefix("onDelete:").unwrap_or("cascade");
column.modifiers.push(ColumnModifier {
name: "onDelete".to_string(),
value: Some(val.to_string()),
});
}
_ if token.starts_with("onUpdate:") => {
let val = token.strip_prefix("onUpdate:").unwrap_or("cascade");
column.modifiers.push(ColumnModifier {
name: "onUpdate".to_string(),
value: Some(val.to_string()),
});
}
_ if token.starts_with("comment:") => {
let val = token.strip_prefix("comment:").unwrap_or("");
column.modifiers.push(ColumnModifier {
name: "comment".to_string(),
value: Some(val.to_string()),
});
}
_ => {
column.attributes.push(token.to_string());
}
}
}
fn detect_relationship(name: &str, column: &mut Column) {
if name.ends_with("_id") {
let model_name = name.strip_suffix("_id").unwrap_or(name);
column.is_relationship = true;
column.relationship_type = Some("belongsTo".to_string());
column.relationship_model = Some(Self::snake_to_pascal(model_name));
}
}
fn snake_to_pascal(s: &str) -> String {
s.split('_')
.filter(|p| !p.is_empty())
.map(|p| {
let mut c = p.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().to_string() + c.as_str(),
}
})
.collect()
}
fn parse_bool_or_type(value: &Value, type_field: &mut String) -> bool {
match value {
Value::Bool(b) => *b,
Value::String(s) => {
if s == "true" || s == "1" {
return true;
}
type_field.clone_from(s);
true
}
_ => false,
}
}
fn parse_list(value: &Value) -> Vec<String> {
match value {
Value::Sequence(seq) => seq.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect(),
Value::String(s) => s.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect(),
_ => vec![],
}
}
fn parse_relationships(value: &Value) -> Vec<Relationship> {
let mut rels = vec![];
if let Value::Mapping(map) = value {
for (key, val) in map {
let rel_type = key.as_str().unwrap_or("").to_string();
let targets = match val {
Value::String(s) => s.split(',').map(|s| s.trim().to_string()).collect(),
Value::Sequence(seq) => seq.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect(),
_ => vec![],
};
for target in targets {
rels.push(Relationship {
type_: rel_type.clone(),
model: target,
foreign_key: None,
local_key: None,
table: None,
pivot_table: None,
related_key: None,
});
}
}
}
rels
}
fn parse_indexes(value: &Value) -> Vec<IndexDef> {
let mut indexes = vec![];
if let Value::Sequence(seq) = value {
for item in seq {
if let Value::Mapping(map) = item {
let columns = map
.get(&Value::String("columns".to_string()))
.and_then(|v| {
if let Value::Sequence(seq) = v {
Some(seq.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
} else {
v.as_str().map(|s| vec![s.to_string()])
}
})
.unwrap_or_default();
let index_type = map
.get(&Value::String("type".to_string()))
.and_then(|v| v.as_str().map(|s| s.to_string()));
indexes.push(IndexDef {
columns,
index_type,
});
}
}
}
indexes
}
}