ferro_cli/doctor/checks/
database_url_sqlite_in_prod.rs1use crate::doctor::check::{CheckResult, DoctorCheck};
5use std::fs;
6use std::path::Path;
7
8pub struct DatabaseUrlSqliteInProdCheck;
9
10const NAME: &str = "database_url_sqlite_in_prod";
11
12impl DoctorCheck for DatabaseUrlSqliteInProdCheck {
13 fn name(&self) -> &'static str {
14 NAME
15 }
16 fn run(&self, root: &Path) -> CheckResult {
17 check_impl(root)
18 }
19}
20
21pub(crate) fn check_impl(root: &Path) -> CheckResult {
22 let path = root.join(".env.production");
23 if !path.is_file() {
24 return CheckResult::ok(NAME, "skipped (.env.production absent)");
25 }
26 let content = match fs::read_to_string(&path) {
27 Ok(s) => s,
28 Err(e) => return CheckResult::error(NAME, format!("failed to read .env.production: {e}")),
29 };
30 for raw in content.lines() {
31 let line = raw.trim();
32 if line.is_empty() || line.starts_with('#') {
33 continue;
34 }
35 let Some((k, v)) = line.split_once('=') else {
36 continue;
37 };
38 if k.trim() == "DATABASE_URL" {
39 let value = v.trim().trim_matches('"').trim_matches('\'');
40 if value.starts_with("sqlite:") {
41 return CheckResult::error(NAME, "DATABASE_URL in .env.production uses sqlite:")
42 .with_details(
43 "Production must use a network-accessible database (postgres, mysql)",
44 );
45 }
46 return CheckResult::ok(NAME, "DATABASE_URL is non-sqlite");
47 }
48 }
49 CheckResult::warn(NAME, "DATABASE_URL not declared in .env.production")
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55 use tempfile::TempDir;
56
57 #[test]
58 fn name_is_database_url_sqlite_in_prod() {
59 assert_eq!(
60 DatabaseUrlSqliteInProdCheck.name(),
61 "database_url_sqlite_in_prod"
62 );
63 }
64
65 #[test]
66 fn skipped_when_env_production_absent() {
67 let tmp = TempDir::new().unwrap();
68 let r = check_impl(tmp.path());
69 assert_eq!(r.status, crate::doctor::check::CheckStatus::Ok);
70 }
71
72 #[test]
73 fn errors_on_sqlite_url() {
74 let tmp = TempDir::new().unwrap();
75 fs::write(
76 tmp.path().join(".env.production"),
77 "DATABASE_URL=sqlite:./db.sqlite\n",
78 )
79 .unwrap();
80 let r = check_impl(tmp.path());
81 assert_eq!(r.status, crate::doctor::check::CheckStatus::Error);
82 }
83
84 #[test]
85 fn ok_on_postgres_url() {
86 let tmp = TempDir::new().unwrap();
87 fs::write(
88 tmp.path().join(".env.production"),
89 "DATABASE_URL=postgres://u:p@h/db\n",
90 )
91 .unwrap();
92 let r = check_impl(tmp.path());
93 assert_eq!(r.status, crate::doctor::check::CheckStatus::Ok);
94 }
95}