use std::{collections::BTreeMap, process::Command};
use regex::Regex;
use semver::Version;
use crate::{
bgworker,
config::{self, Config, Database},
db, Error, Result,
};
const SEAORM_INSTALLED: &str = "SeaORM CLI is installed";
const SEAORM_NOT_INSTALLED: &str = "SeaORM CLI was not found";
const SEAORM_NOT_FIX: &str = r"To fix, run:
$ cargo install sea-orm-cli";
const DB_CONNECTION_FAILED: &str = "DB connection: fails";
const DB_CONNECTION_SUCCESS: &str = "DB connection: success";
const QUEUE_CONN_OK: &str = "queue connection: success";
const QUEUE_CONN_FAILED: &str = "queue connection: failed";
const QUEUE_NOT_CONFIGURED: &str = "queue not configured?";
#[derive(PartialOrd, PartialEq, Eq, Ord, Debug)]
pub enum Resource {
SeaOrmCLI,
Database,
Redis,
}
#[derive(Debug, PartialEq, Eq)]
pub enum CheckStatus {
Ok,
NotOk,
NotConfigure,
}
#[derive(Debug)]
pub struct Check {
pub status: CheckStatus,
pub message: String,
pub description: Option<String>,
}
impl Check {
#[must_use]
pub fn valid(&self) -> bool {
self.status != CheckStatus::NotOk
}
pub fn to_result(&self) -> Result<()> {
if self.valid() {
Ok(())
} else {
Err(Error::Message(format!(
"{} {}",
self.message,
self.description.clone().unwrap_or_default()
)))
}
}
}
impl std::fmt::Display for Check {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let icon = match self.status {
CheckStatus::Ok => "✅",
CheckStatus::NotOk => "❌",
CheckStatus::NotConfigure => "⚠️ ",
};
write!(
f,
"{} {}{}",
icon,
self.message,
self.description
.as_ref()
.map(|d| format!("\n{d}"))
.unwrap_or_default()
)
}
}
pub async fn run_all(config: &Config) -> Result<BTreeMap<Resource, Check>> {
let mut checks = BTreeMap::from([
(Resource::SeaOrmCLI, check_seaorm_cli()?),
(Resource::Database, check_db(&config.database).await),
]);
if config.workers.mode == config::WorkerMode::BackgroundQueue {
checks.insert(Resource::Redis, check_queue(config).await);
}
Ok(checks)
}
pub async fn check_db(config: &Database) -> Check {
match db::connect(config).await {
Ok(conn) => match conn.ping().await {
Ok(()) => match db::verify_access(&conn).await {
Ok(()) => Check {
status: CheckStatus::Ok,
message: DB_CONNECTION_SUCCESS.to_string(),
description: None,
},
Err(err) => Check {
status: CheckStatus::NotOk,
message: DB_CONNECTION_FAILED.to_string(),
description: Some(err.to_string()),
},
},
Err(err) => Check {
status: CheckStatus::NotOk,
message: DB_CONNECTION_FAILED.to_string(),
description: Some(err.to_string()),
},
},
Err(err) => Check {
status: CheckStatus::NotOk,
message: DB_CONNECTION_FAILED.to_string(),
description: Some(err.to_string()),
},
}
}
pub async fn check_queue(config: &Config) -> Check {
if let Ok(Some(queue)) = bgworker::create_queue_provider(config).await {
match queue.ping().await {
Ok(()) => Check {
status: CheckStatus::Ok,
message: format!("{}: {}", queue.describe(), QUEUE_CONN_OK),
description: None,
},
Err(err) => Check {
status: CheckStatus::NotOk,
message: format!("{}: {}", queue.describe(), QUEUE_CONN_FAILED),
description: Some(err.to_string()),
},
}
} else {
Check {
status: CheckStatus::NotConfigure,
message: QUEUE_NOT_CONFIGURED.to_string(),
description: None,
}
}
}
const MIN_SEAORMCLI_VER: &str = "1.1.0";
pub fn check_seaorm_cli() -> Result<Check> {
match Command::new("sea-orm-cli").arg("--version").output() {
Ok(out) => {
let input = String::from_utf8_lossy(&out.stdout);
let re = Regex::new(r"(\d+\.\d+\.\d+)").unwrap();
let version_str = re
.captures(&input)
.and_then(|caps| caps.get(0))
.map(|m| m.as_str())
.ok_or("SeaORM CLI version not found")
.map_err(Box::from)?;
let version = Version::parse(version_str).map_err(Box::from)?;
let min_version = Version::parse(MIN_SEAORMCLI_VER).map_err(Box::from)?;
if version >= min_version {
Ok(Check {
status: CheckStatus::Ok,
message: SEAORM_INSTALLED.to_string(),
description: None,
})
} else {
Ok(Check {
status: CheckStatus::NotOk,
message: format!(
"SeaORM CLI minimal version is `{min_version}` (you have `{version}`). \
Run `cargo install sea-orm-cli` to update."
),
description: Some(SEAORM_NOT_FIX.to_string()),
})
}
}
Err(_) => Ok(Check {
status: CheckStatus::NotOk,
message: SEAORM_NOT_INSTALLED.to_string(),
description: Some(SEAORM_NOT_FIX.to_string()),
}),
}
}