mod human;
mod json;
pub use human::{print_instance_human, print_instances_human};
pub use json::{print_instance_json, print_instances_json};
use dbnest_core::{Instance, InstanceSummary};
use serde_json::json;
pub fn print_error(err: &dbnest_core::DbnestError, json_mode: bool) {
if json_mode {
eprintln!(
"{}",
serde_json::to_string_pretty(&error_json(err)).unwrap()
);
} else {
eprintln!("{err}");
}
}
pub fn error_json(err: &dbnest_core::DbnestError) -> serde_json::Value {
json!({
"ok": false,
"error": {
"kind": err.kind(),
"message": err.to_string()
}
})
}
pub fn print_ok(json_mode: bool, action: &str, id: Option<&str>) {
if json_mode {
println!(
"{}",
serde_json::to_string_pretty(&json!({
"ok": true,
"action": action,
"id": id
}))
.unwrap()
);
} else {
if let Some(id) = id {
println!("{action} ok: {id}");
} else {
println!("{action} ok");
}
}
}
pub fn print_status(json_mode: bool, res: crate::cli::StatusResult) {
if json_mode {
match res {
crate::cli::StatusResult::One(r) => {
println!("{}", serde_json::to_string_pretty(&r).unwrap())
}
crate::cli::StatusResult::Many(v) => {
println!("{}", serde_json::to_string_pretty(&v).unwrap())
}
}
} else {
match res {
crate::cli::StatusResult::One(r) => print_status_human(&[r]),
crate::cli::StatusResult::Many(v) => print_status_human(&v),
}
}
}
fn print_status_human(list: &[dbnest_core::InstanceStatusReport]) {
if list.is_empty() {
println!("No instances found.");
return;
}
for r in list {
println!("{} {:8} {:?}", r.id, r.engine.as_str(), r.status);
}
}
pub fn print_instance(inst: &Instance, json: bool, show_secrets: bool) {
if json {
print_instance_json(inst, show_secrets);
} else {
print_instance_human(inst, show_secrets);
}
}
pub fn print_instances(list: &[InstanceSummary], json: bool, show_secrets: bool) {
if json {
print_instances_json(list, show_secrets);
} else {
print_instances_human(list, show_secrets);
}
}
pub fn redact_database_url(url: &str) -> String {
let Some(scheme_end) = url.find("://") else {
return url.to_string();
};
let authority_start = scheme_end + 3;
let rest = &url[authority_start..];
let Some(at_rel) = rest.find('@') else {
return url.to_string();
};
let at = authority_start + at_rel;
let credentials = &url[authority_start..at];
let Some(colon_rel) = credentials.find(':') else {
return url.to_string();
};
let password_start = authority_start + colon_rel + 1;
format!("{}****{}", &url[..password_start], &url[at..])
}
#[cfg(test)]
mod tests {
use super::{error_json, redact_database_url};
#[test]
fn redacts_password_in_database_url() {
assert_eq!(
redact_database_url("postgres://dev:secret@127.0.0.1:5432/appdb"),
"postgres://dev:****@127.0.0.1:5432/appdb"
);
}
#[test]
fn leaves_urls_without_password_unchanged() {
assert_eq!(
redact_database_url("sqlite:////tmp/db.sqlite"),
"sqlite:////tmp/db.sqlite"
);
}
#[test]
fn leaves_urls_without_credentials_unchanged() {
assert_eq!(
redact_database_url("postgres://127.0.0.1:5432/appdb"),
"postgres://127.0.0.1:5432/appdb"
);
}
#[test]
fn redacts_password_containing_colon() {
assert_eq!(
redact_database_url("postgres://dev:secret:extra@127.0.0.1:5432/appdb"),
"postgres://dev:****@127.0.0.1:5432/appdb"
);
}
#[test]
fn json_error_uses_stable_kind() {
let err = dbnest_core::DbnestError::InvalidArgument("bad input".into());
let value = error_json(&err);
assert_eq!(value["ok"], false);
assert_eq!(value["error"]["kind"], "invalid_argument");
assert_eq!(value["error"]["message"], "invalid argument: bad input");
}
}