Skip to main content

vpg/
nvim_dbee.rs

1use std::fs::OpenOptions;
2use std::os::unix::fs::PermissionsExt;
3use std::path::Path;
4use std::path::PathBuf;
5
6use serde::Deserialize;
7use serde::Serialize;
8
9use crate::pgpass::PgpassEntry;
10
11/// Saves or updates the `nvim-dbee` connections file with the provided [`PgpassEntry`], setting secure permissions.
12///
13/// # Errors
14/// - A filesystem operation (open/read/write/remove) fails.
15/// - JSON serialization or deserialization fails.
16pub fn save_new_nvim_dbee_conns_file(updated_pg_pass_entry: &PgpassEntry, conns_path: &Path) -> rootcause::Result<()> {
17    let conns = get_updated_conns(updated_pg_pass_entry, conns_path)?;
18
19    let mut tmp_path = PathBuf::from(conns_path);
20    tmp_path.set_file_name("conns.json.tmp");
21    std::fs::write(&tmp_path, serde_json::to_string(&conns)?)?;
22    std::fs::rename(&tmp_path, conns_path)?;
23
24    let file = OpenOptions::new().read(true).open(conns_path)?;
25    let mut permissions = file.metadata()?.permissions();
26    permissions.set_mode(0o600);
27    file.set_permissions(permissions)?;
28
29    Ok(())
30}
31
32/// Get updated conns.
33///
34/// # Errors
35/// - A filesystem operation (open/read/write/remove) fails.
36/// - JSON serialization or deserialization fails.
37fn get_updated_conns(updated_pg_pass_entry: &PgpassEntry, conns_path: &Path) -> rootcause::Result<Vec<NvimDbeeConn>> {
38    let updated_conn = NvimDbeeConn::from(updated_pg_pass_entry);
39
40    // Returns just the updated conn if the conns file does not exist.
41    if !conns_path.try_exists()? {
42        return Ok(vec![updated_conn]);
43    }
44
45    let conns_file_content = std::fs::read_to_string(conns_path)?;
46    // Returns just the updated conn if the conns file exists but it's empty.
47    if conns_file_content.is_empty() {
48        return Ok(vec![updated_conn]);
49    }
50
51    // Returns the already present conns with the updated one added in case it was missing.
52    let mut conns: Vec<NvimDbeeConn> = serde_json::from_str(&conns_file_content)?;
53    let mut seen = false;
54    for conn in &mut conns {
55        if conn.name == updated_conn.name {
56            conn.url = updated_pg_pass_entry.connection_params.db_url();
57            seen = true;
58        }
59    }
60    if !seen {
61        conns.push(updated_conn);
62    }
63    Ok(conns)
64}
65
66#[derive(Clone, Debug, Deserialize, Serialize)]
67struct NvimDbeeConn {
68    id: String,
69    name: String,
70    r#type: String,
71    url: String,
72}
73
74impl From<&PgpassEntry> for NvimDbeeConn {
75    fn from(value: &PgpassEntry) -> Self {
76        Self {
77            id: value.metadata.alias.clone(),
78            name: value.metadata.alias.clone(),
79            url: value.connection_params.db_url(),
80            r#type: "postgres".to_string(),
81        }
82    }
83}