loco-rs 0.6.1

The one-person framework for Rust
Documentation
use std::env::current_dir;

use chrono::Utc;
use duct::cmd;
use rrgen::RRgen;
use serde_json::json;

use crate::{app::Hooks, errors::Error, Result};

const MODEL_T: &str = include_str!("templates/model.t");
const MODEL_TEST_T: &str = include_str!("templates/model_test.t");

use super::{collect_messages, MAPPINGS};

/// skipping some fields from the generated models.
/// For example, the `created_at` and `updated_at` fields are automatically
/// generated by the Loco app and should be given
pub const IGNORE_FIELDS: &[&str] = &["created_at", "updated_at", "create_at", "update_at"];

pub fn generate<H: Hooks>(
    rrgen: &RRgen,
    name: &str,
    is_link: bool,
    migration_only: bool,
    fields: &[(String, String)],
) -> Result<String> {
    let pkg_name: &str = H::app_name();
    let ts = Utc::now();

    let mut columns = Vec::new();
    let mut references = Vec::new();
    for (fname, ftype) in fields {
        if IGNORE_FIELDS.contains(&fname.as_str()) {
            tracing::warn!(
                field = fname,
                "note that a redundant field was specified, it is already generated automatically"
            );
            continue;
        }
        if ftype == "references" {
            let fkey = format!("{fname}_id");
            columns.push((fkey.clone(), "integer"));
            // user, user_id
            references.push((fname, fkey));
        } else {
            let schema_type = MAPPINGS.schema_field(ftype.as_str()).ok_or_else(|| {
                Error::Message(format!(
                    "type: {} not found. try any of: {:?}",
                    ftype,
                    MAPPINGS.schema_fields()
                ))
            })?;
            columns.push((fname.to_string(), schema_type.as_str()));
        }
    }

    let vars = json!({"name": name, "ts": ts, "pkg_name": pkg_name, "is_link": is_link, "columns": columns, "references": references});
    let res1 = rrgen.generate(MODEL_T, &vars)?;
    let res2 = rrgen.generate(MODEL_TEST_T, &vars)?;

    if !migration_only {
        let cwd = current_dir()?;
        let _ = cmd!("cargo", "loco", "db", "migrate",)
            .stderr_to_stdout()
            .dir(cwd.as_path())
            .run()
            .map_err(|err| {
                Error::Message(format!(
                    "failed to run loco db migration. error details: `{err}`",
                ))
            })?;
        let _ = cmd!("cargo", "loco", "db", "entities",)
            .stderr_to_stdout()
            .dir(cwd.as_path())
            .run()
            .map_err(|err| {
                Error::Message(format!(
                    "failed to run loco db entities. error details: `{err}`",
                ))
            })?;
    }

    let messages = collect_messages(vec![res1, res2]);
    Ok(messages)
}