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 indexmap::IndexMap;

use crate::tree::Tree;

pub struct RouteGenerator;

impl RouteGenerator {
    pub fn generate(tree: &Tree) -> IndexMap<String, String> {
        let mut routes = vec![
            "use axum::{Router, routing::{get, post, put, delete}};".to_string(),
            "use crate::handlers::prelude::*;".to_string(),
            String::new(),
            "pub fn app_router() -> Router {".to_string(),
            "    Router::new()".to_string(),
        ];

        for controller in tree.all_controllers() {
            let model_name = controller.name.trim_end_matches("Controller").to_string();
            let handler_mod = crate::generators::model_generator::to_snake_case(&model_name);
            let resource = pluralize(&handler_mod);

            if controller.is_resource() {
                let is_api = controller.is_api_resource();
                if is_api {
                    routes.push(format!(
                        "        .route(\"/{}\", get({}::handlers::{}::index).post({}::handlers::{}::store))",
                        resource, handler_mod, handler_mod, handler_mod, handler_mod
                    ));
                    routes.push(format!(
                        "        .route(\"/{{id}}\", get({}::handlers::{}::show).put({}::handlers::{}::update).delete({}::handlers::{}::destroy))",
                        resource, handler_mod, handler_mod, handler_mod, handler_mod, handler_mod
                    ));
                } else {
                    routes.push(format!(
                        "        .route(\"/{}\", get({}::handlers::{}::index).post({}::handlers::{}::store))",
                        resource, handler_mod, handler_mod, handler_mod, handler_mod
                    ));
                    routes.push(format!(
                        "        .route(\"/{{id}}\", get({}::handlers::{}::show).put({}::handlers::{}::update).delete({}::handlers::{}::destroy))",
                        resource, handler_mod, handler_mod, handler_mod, handler_mod, handler_mod
                    ));
                    routes.push(format!(
                        "        .route(\"/{}/new\", get({}::handlers::{}::create))",
                        resource, handler_mod, handler_mod
                    ));
                    routes.push(format!(
                        "        .route(\"/{{id}}/edit\", get({}::handlers::{}::edit))",
                        resource, handler_mod
                    ));
                }
            } else {
                for (method_name, _) in &controller.methods {
                    let http_method = method_to_http(method_name);
                    let path = if *method_name == "index" {
                        format!("/{}", resource)
                    } else {
                        format!("/{}/{}", resource, method_name)
                    };
                    routes.push(format!(
                        "        .route(\"{}\", {}({}::handlers::{}::{}))",
                        path, http_method, handler_mod, handler_mod, method_name
                    ));
                }
            }
        }

        routes.push("}".to_string());

        let mut files = IndexMap::new();
        files.insert("src/routes.rs".to_string(), routes.join("\n"));
        files
    }
}

fn method_to_http(method: &str) -> &str {
    match method {
        "index" | "create" | "show" | "edit" => "get",
        "store" => "post",
        "update" => "put",
        "destroy" => "delete",
        _ => "get",
    }
}

fn pluralize(s: &str) -> String {
    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 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 {
        format!("{}s", s)
    }
}