use crate::converter::{FieldBuilder, ModelBuilder, SchemaBuilder};
use crate::error::ImportResult;
use crate::seaorm::types::*;
use prax_schema::ast::*;
use smol_str::SmolStr;
use std::fs;
use std::path::Path;
use syn::{Attribute, Fields, Item, Meta, Type};
pub fn parse_seaorm_entity(input: &str) -> ImportResult<SeaOrmEntity> {
let syntax = syn::parse_file(input).map_err(|e| {
crate::error::ImportError::DieselParseError(format!("Failed to parse Rust file: {}", e))
})?;
let mut entity = None;
let mut relations = vec![];
for item in syntax.items {
match item {
Item::Struct(item_struct) => {
if has_derive(&item_struct.attrs, "DeriveEntityModel") {
entity = Some(parse_entity_struct(item_struct)?);
}
}
Item::Enum(item_enum) => {
if has_derive(&item_enum.attrs, "DeriveRelation") {
relations = parse_relation_enum(item_enum)?;
}
}
_ => {}
}
}
let mut entity = entity.ok_or_else(|| {
crate::error::ImportError::DieselParseError(
"No entity struct found with #[derive(DeriveEntityModel)]".to_string(),
)
})?;
entity.relations = relations;
Ok(entity)
}
pub fn parse_seaorm_entity_file<P: AsRef<Path>>(path: P) -> ImportResult<SeaOrmEntity> {
let content = fs::read_to_string(path)?;
parse_seaorm_entity(&content)
}
pub fn import_seaorm_entity(input: &str) -> ImportResult<Schema> {
let entity = parse_seaorm_entity(input)?;
convert_seaorm_to_prax(vec![entity])
}
pub fn import_seaorm_entity_file<P: AsRef<Path>>(path: P) -> ImportResult<Schema> {
let entity = parse_seaorm_entity_file(path)?;
convert_seaorm_to_prax(vec![entity])
}
fn has_derive(attrs: &[Attribute], derive_name: &str) -> bool {
attrs.iter().any(|attr| {
if attr.path().is_ident("derive") {
if let Meta::List(list) = &attr.meta {
let tokens = list.tokens.to_string();
return tokens.contains(derive_name);
}
}
false
})
}
fn parse_entity_struct(item_struct: syn::ItemStruct) -> ImportResult<SeaOrmEntity> {
let name = item_struct.ident.to_string();
let table_name = extract_table_name(&item_struct.attrs).unwrap_or_else(|| {
let snake = name.to_lowercase();
if snake.ends_with('y') {
format!("{}ies", &snake[..snake.len() - 1])
} else {
format!("{}s", snake)
}
});
let mut fields = vec![];
if let Fields::Named(named_fields) = item_struct.fields {
for field in named_fields.named {
let field_name = field.ident.unwrap().to_string();
let (field_type, is_optional) = parse_field_type(&field.ty)?;
let attributes = parse_field_attributes(&field.attrs)?;
fields.push(SeaOrmField {
name: field_name,
field_type,
is_optional,
attributes,
documentation: None,
});
}
}
Ok(SeaOrmEntity {
name,
table_name,
fields,
relations: vec![],
documentation: None,
})
}
fn extract_table_name(attrs: &[Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("sea_orm")
&& let Ok(meta) = attr.parse_args::<syn::Meta>()
&& let syn::Meta::NameValue(nv) = meta
&& nv.path.is_ident("table_name")
&& let syn::Expr::Lit(lit) = &nv.value
&& let syn::Lit::Str(s) = &lit.lit
{
return Some(s.value());
}
}
None
}
fn parse_field_type(ty: &Type) -> ImportResult<(SeaOrmFieldType, bool)> {
if let Type::Path(type_path) = ty {
let segments = &type_path.path.segments;
if let Some(last_segment) = segments.last() {
let type_name = last_segment.ident.to_string();
if type_name == "Option"
&& let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first()
{
let (inner_type, _) = parse_field_type(inner_ty)?;
return Ok((inner_type, true));
}
let field_type = match type_name.as_str() {
"i32" => SeaOrmFieldType::I32,
"i64" => SeaOrmFieldType::I64,
"f32" => SeaOrmFieldType::F32,
"f64" => SeaOrmFieldType::F64,
"String" => SeaOrmFieldType::String,
"bool" => SeaOrmFieldType::Bool,
"DateTime" => SeaOrmFieldType::DateTime,
"Date" => SeaOrmFieldType::Date,
"Time" => SeaOrmFieldType::Time,
"Decimal" => SeaOrmFieldType::Decimal,
"Value" => SeaOrmFieldType::Json, "Uuid" => SeaOrmFieldType::Uuid,
"Vec" => {
if let syn::PathArguments::AngleBracketed(args) = &last_segment.arguments
&& let Some(syn::GenericArgument::Type(Type::Path(inner_path))) =
args.args.first()
&& let Some(seg) = inner_path.path.segments.last()
&& seg.ident == "u8"
{
return Ok((SeaOrmFieldType::Bytes, false));
}
SeaOrmFieldType::Custom("Vec".to_string())
}
other => SeaOrmFieldType::Custom(other.to_string()),
};
return Ok((field_type, false));
}
}
Ok((SeaOrmFieldType::Custom("Unknown".to_string()), false))
}
fn parse_field_attributes(attrs: &[Attribute]) -> ImportResult<Vec<SeaOrmFieldAttribute>> {
let mut attributes = vec![];
for attr in attrs {
if attr.path().is_ident("sea_orm") {
if let Ok(meta) = attr.parse_args::<syn::Meta>() {
match meta {
syn::Meta::Path(path) => {
if path.is_ident("primary_key") {
attributes.push(SeaOrmFieldAttribute::PrimaryKey);
} else if path.is_ident("auto_increment") {
attributes.push(SeaOrmFieldAttribute::AutoIncrement);
} else if path.is_ident("unique") {
attributes.push(SeaOrmFieldAttribute::Unique);
} else if path.is_ident("indexed") {
attributes.push(SeaOrmFieldAttribute::Indexed);
} else if path.is_ident("nullable") {
attributes.push(SeaOrmFieldAttribute::Nullable);
}
}
syn::Meta::NameValue(nv) => {
if nv.path.is_ident("column_name") {
if let syn::Expr::Lit(lit) = &nv.value
&& let syn::Lit::Str(s) = &lit.lit
{
attributes.push(SeaOrmFieldAttribute::ColumnName(s.value()));
}
} else if nv.path.is_ident("column_type") {
if let syn::Expr::Lit(lit) = &nv.value
&& let syn::Lit::Str(s) = &lit.lit
{
attributes.push(SeaOrmFieldAttribute::ColumnType(s.value()));
}
} else if nv.path.is_ident("default_value")
&& let syn::Expr::Lit(lit) = &nv.value
&& let syn::Lit::Str(s) = &lit.lit
{
attributes.push(SeaOrmFieldAttribute::DefaultValue(s.value()));
}
}
_ => {}
}
}
}
}
Ok(attributes)
}
fn parse_relation_enum(item_enum: syn::ItemEnum) -> ImportResult<Vec<SeaOrmRelation>> {
let mut relations = vec![];
for variant in item_enum.variants {
let name = variant.ident.to_string();
for attr in &variant.attrs {
if attr.path().is_ident("sea_orm")
&& let Ok(meta) = attr.parse_args::<syn::Meta>()
&& let Some(relation) = parse_relation_attribute(name.clone(), meta)?
{
relations.push(relation);
}
}
}
Ok(relations)
}
fn parse_relation_attribute(name: String, meta: syn::Meta) -> ImportResult<Option<SeaOrmRelation>> {
match meta {
syn::Meta::NameValue(nv) => {
let relation_type = if nv.path.is_ident("has_one") {
SeaOrmRelationType::HasOne
} else if nv.path.is_ident("has_many") {
SeaOrmRelationType::HasMany
} else if nv.path.is_ident("belongs_to") {
SeaOrmRelationType::BelongsTo
} else {
return Ok(None);
};
let entity = if let syn::Expr::Lit(lit) = &nv.value {
if let syn::Lit::Str(s) = &lit.lit {
s.value()
} else {
return Ok(None);
}
} else if let syn::Expr::Path(path) = &nv.value {
path.path
.segments
.iter()
.filter(|seg| seg.ident != "super" && seg.ident != "Entity")
.map(|seg| seg.ident.to_string())
.last()
.unwrap_or_default()
} else {
return Ok(None);
};
Ok(Some(SeaOrmRelation {
name,
relation_type,
entity,
from: None,
to: None,
on_delete: None,
on_update: None,
}))
}
_ => Ok(None),
}
}
fn convert_seaorm_to_prax(entities: Vec<SeaOrmEntity>) -> ImportResult<Schema> {
let mut builder = SchemaBuilder::new();
for entity in entities {
let model = convert_entity(entity)?;
builder.add_model(model);
}
Ok(builder.build())
}
fn convert_entity(entity: SeaOrmEntity) -> ImportResult<Model> {
let mut model_builder = ModelBuilder::new(&entity.name).with_db_name(&entity.table_name);
for field in entity.fields {
let prax_field = convert_field(field)?;
model_builder.add_field(prax_field);
}
Ok(model_builder.build())
}
fn convert_field(field: SeaOrmField) -> ImportResult<Field> {
let (prax_type, modifier) = convert_field_type(&field.field_type, field.is_optional)?;
let field_name = field.name.clone();
let mut field_builder = FieldBuilder::new(&field_name, prax_type, modifier);
for attr in field.attributes {
match attr {
SeaOrmFieldAttribute::PrimaryKey => {
field_builder = field_builder.with_id();
}
SeaOrmFieldAttribute::AutoIncrement => {
field_builder = field_builder.with_auto();
}
SeaOrmFieldAttribute::Unique => {
field_builder = field_builder.with_unique();
}
SeaOrmFieldAttribute::ColumnName(col_name) => {
field_builder = field_builder.with_map(col_name);
}
SeaOrmFieldAttribute::DefaultValue(val) => {
let default_val = if val == "true" {
AttributeValue::Boolean(true)
} else if val == "false" {
AttributeValue::Boolean(false)
} else if let Ok(n) = val.parse::<i64>() {
AttributeValue::Int(n)
} else if let Ok(f) = val.parse::<f64>() {
AttributeValue::Float(f)
} else {
AttributeValue::String(val)
};
field_builder = field_builder.with_default(default_val);
}
_ => {}
}
}
Ok(field_builder.build())
}
fn convert_field_type(
field_type: &SeaOrmFieldType,
is_optional: bool,
) -> ImportResult<(FieldType, TypeModifier)> {
let base_type = match field_type {
SeaOrmFieldType::I32 | SeaOrmFieldType::I64 => FieldType::Scalar(ScalarType::Int),
SeaOrmFieldType::F32 | SeaOrmFieldType::F64 => FieldType::Scalar(ScalarType::Float),
SeaOrmFieldType::String => FieldType::Scalar(ScalarType::String),
SeaOrmFieldType::Bool => FieldType::Scalar(ScalarType::Boolean),
SeaOrmFieldType::DateTime => FieldType::Scalar(ScalarType::DateTime),
SeaOrmFieldType::Date => FieldType::Scalar(ScalarType::Date),
SeaOrmFieldType::Time => FieldType::Scalar(ScalarType::Time),
SeaOrmFieldType::Decimal => FieldType::Scalar(ScalarType::Decimal),
SeaOrmFieldType::Json => FieldType::Scalar(ScalarType::Json),
SeaOrmFieldType::Bytes => FieldType::Scalar(ScalarType::Bytes),
SeaOrmFieldType::Uuid => FieldType::Scalar(ScalarType::Uuid),
SeaOrmFieldType::Custom(name) => FieldType::Enum(SmolStr::from(name.as_str())),
};
let modifier = if is_optional {
TypeModifier::Optional
} else {
TypeModifier::Required
};
Ok((base_type, modifier))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_simple_entity() {
let entity_code = r#"
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub email: String,
pub name: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
"#;
let result = parse_seaorm_entity(entity_code);
assert!(result.is_ok());
let entity = result.unwrap();
assert_eq!(entity.name, "Model");
assert_eq!(entity.table_name, "users");
assert_eq!(entity.fields.len(), 3);
}
#[test]
fn test_import_entity() {
let entity_code = r#"
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "posts")]
pub struct Model {
#[sea_orm(primary_key, auto_increment)]
pub id: i32,
pub title: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
"#;
let result = import_seaorm_entity(entity_code);
assert!(result.is_ok());
let schema = result.unwrap();
assert_eq!(schema.models.len(), 1);
}
}