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 crate::models::statements::*;

pub struct StatementLexer;

impl StatementLexer {
    pub fn parse(statements: &[String]) -> Vec<Statement> {
        let mut result = vec![];

        for stmt in statements {
            let parts: Vec<&str> = stmt.splitn(2, ':').collect();
            let command = parts[0].trim();
            let args = parts.get(1).unwrap_or(&"").trim();

            let statement = match command {
                "query" | "all" => Statement::Query(QueryStatement {
                    model: Self::parse_model_arg(args),
                    methods: vec!["all".to_string()],
                }),
                "find" | "first" => {
                    let model = Self::parse_model_arg(args);
                    Statement::Find(FindStatement {
                        model,
                        key: None,
                    })
                }
                "save" => Statement::Save(SaveStatement {
                    model: Self::parse_model_arg(args),
                }),
                "update" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let model = if parts.is_empty() {
                        None
                    } else {
                        Some(parts[0].to_string())
                    };
                    let columns = if parts.len() > 1 {
                        parts[1..].iter().map(|s| s.to_string()).collect()
                    } else {
                        vec![]
                    };
                    Statement::Update(UpdateStatement { model, columns })
                }
                "delete" => Statement::Delete(DeleteStatement {
                    model: Self::parse_model_arg(args),
                }),
                "validate" => Statement::Validate(ValidateStatement {
                    fields: args.split(',').map(|s| s.trim().to_string()).filter(|s| !s.is_empty()).collect(),
                }),
                "send" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let class = parts.first().unwrap_or(&"").to_string();
                    let to = parts.iter().position(|&p| p == "to")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.to_string());
                    let data = parts.iter().position(|&p| p == "with")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
                        .unwrap_or_default();
                    Statement::Send(SendStatement { class, to, data })
                }
                "notify" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let model = parts.first().map(|s| s.to_string());
                    let notification = parts.iter().position(|&p| p == "with")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.to_string())
                        .unwrap_or_default();
                    let data = vec![];
                    Statement::Notify(NotifyStatement {
                        model,
                        notification,
                        data,
                    })
                }
                "dispatch" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let job = parts.first().unwrap_or(&"").to_string();
                    let data = parts.iter().position(|&p| p == "with")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
                        .unwrap_or_default();
                    Statement::Dispatch(DispatchStatement { job, data })
                }
                "fire" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let event = parts.first().unwrap_or(&"").to_string();
                    let data = parts.iter().position(|&p| p == "with")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
                        .unwrap_or_default();
                    Statement::Fire(FireStatement { event, data })
                }
                "render" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let view = parts.first().unwrap_or(&"").to_string();
                    let data = parts.iter().position(|&p| p == "with")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
                        .unwrap_or_default();
                    Statement::Render(RenderStatement { view, data })
                }
                "redirect" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let route = parts.first().unwrap_or(&"").to_string();
                    let params = if parts.len() > 1 {
                        parts[1..].iter().map(|s| s.to_string()).collect()
                    } else {
                        vec![]
                    };
                    Statement::Redirect(RedirectStatement { route, params })
                }
                "respond" => {
                    let status = args.parse::<u16>().ok();
                    Statement::Respond(RespondStatement { status })
                }
                "resource" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let collection = parts.iter().any(|&p| p == "collection:");
                    let model = if collection {
                        parts.iter().find_map(|&p| {
                            if p.starts_with("collection:") {
                                p.strip_prefix("collection:").map(|s| s.to_string())
                            } else {
                                None
                            }
                        })
                    } else {
                        parts.first().map(|s| s.to_string())
                    };
                    Statement::Resource(ResourceStatement { model, collection })
                }
                "flash" => Statement::Flash(FlashStatement {
                    reference: args.to_string(),
                }),
                "inertia" => {
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    let component = parts.first().unwrap_or(&"").to_string();
                    let data = parts.iter().position(|&p| p == "with")
                        .and_then(|i| parts.get(i + 1))
                        .map(|s| s.split(',').map(|s| s.trim().to_string()).collect())
                        .unwrap_or_default();
                    Statement::Inertia(InertiaStatement { component, data })
                }
                _ => {
                    // Try to parse as eloquent statement
                    let parts: Vec<&str> = args.split_whitespace().collect();
                    Statement::Eloquent(EloquentStatement {
                        operation: command.to_string(),
                        model: parts.first().map(|s| s.to_string()),
                        columns: parts[1..].iter().map(|s| s.to_string()).collect(),
                    })
                }
            };

            result.push(statement);
        }

        result
    }

    fn parse_model_arg(args: &str) -> Option<String> {
        let args = args.trim();
        if args.is_empty() { None } else { Some(args.to_string()) }
    }
}