rust-db-blueprint 0.1.0

A Rust code generator — reads YAML draft files and generates Axum + SQLx models, migrations, handlers, routes, requests, tests, and seeds
Documentation
use serde::{Deserialize, Serialize};
use indexmap::IndexMap;

use super::column::Column;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelDef {
    pub name: String,
    pub table: Option<String>,
    pub columns: IndexMap<String, Column>,
    pub relationships: Vec<Relationship>,
    pub indexes: Vec<IndexDef>,
    pub pivot: bool,
    pub soft_deletes: bool,
    pub timestamps: bool,
    pub traits: Vec<String>,
    pub primary_key: String,
    pub timestamps_type: String,
    pub soft_delete_type: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Relationship {
    pub type_: String,
    pub model: String,
    pub foreign_key: Option<String>,
    pub local_key: Option<String>,
    pub table: Option<String>,
    pub pivot_table: Option<String>,
    pub related_key: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexDef {
    pub columns: Vec<String>,
    pub index_type: Option<String>,
}

impl ModelDef {
    pub fn new(name: &str) -> Self {
        Self {
            name: name.to_string(),
            table: None,
            columns: IndexMap::new(),
            relationships: vec![],
            indexes: vec![],
            pivot: false,
            soft_deletes: false,
            timestamps: true,
            traits: vec![],
            primary_key: "id".to_string(),
            timestamps_type: "timestamps".to_string(),
            soft_delete_type: "softDeletes".to_string(),
        }
    }

    pub fn table_name(&self) -> String {
        self.table
            .clone()
            .unwrap_or_else(|| self.name_to_table())
    }

    pub fn name_to_table(&self) -> String {
        let snake = convert_case::Casing::to_case(&self.name, convert_case::Case::Snake);
        let plural = pluralize(&snake);
        plural
    }

    pub fn fillable_columns(&self) -> Vec<&Column> {
        self.columns
            .values()
            .filter(|c| {
                !c.is_timestamps()
                    && !c.is_soft_deletes()
                    && c.name != self.primary_key
                    && !c.is_relationship
            })
            .collect()
    }

    pub fn has_soft_deletes(&self) -> bool {
        self.soft_deletes || self.columns.values().any(|c| c.is_soft_deletes())
    }
}

fn pluralize(s: &str) -> String {
    if s.ends_with('y') && !s.ends_with("ay") && !s.ends_with("ey") && !s.ends_with("oy") && !s.ends_with("uy") {
        format!("{}ies", &s[..s.len() - 1])
    } else if s.ends_with('s') || s.ends_with('x') || s.ends_with('z') || s.ends_with("ch") || s.ends_with("sh") {
        format!("{}es", s)
    } else {
        format!("{}s", s)
    }
}