From 8730cfbab38d92572dfbe5a19585f0756d9d89ad Mon Sep 17 00:00:00 2001 From: Patrick Elsen Date: Mon, 9 Oct 2023 15:52:25 +0200 Subject: [PATCH 1/2] Rename Credentials::load() to load_or_init() This makes the intent of the function clear: when you read it being used, it is obvious that it might create a default credentials file rather than being a read-only load operation, without needing to read the documentation. --- src/credentials.rs | 6 ++---- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/credentials.rs b/src/credentials.rs index 56c1a6ea..bb587742 100644 --- a/src/credentials.rs +++ b/src/credentials.rs @@ -76,10 +76,8 @@ impl Credentials { .wrap_err("Failed to write credentials") } - /// Loads the credentials from the file system - /// - /// Note: Initializes the credential file if its not present - pub async fn load() -> eyre::Result { + /// Loads the credentials from the file system, or creates default if not present. + pub async fn load_or_init() -> eyre::Result { let Ok(credentials) = Self::read().await else { let credentials = Credentials::default(); credentials.write().await?; diff --git a/src/main.rs b/src/main.rs index 1b5a0ebb..cc80b0f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -137,7 +137,7 @@ async fn main() -> eyre::Result<()> { .unwrap(); let cli = Cli::parse(); - let credentials = Credentials::load().await?; + let credentials = Credentials::load_or_init().await?; match cli.command { Command::Init { lib, api, package } => { From 88df4fcfe7a32867c07334e66fcb300de8b82477 Mon Sep 17 00:00:00 2001 From: Patrick Elsen Date: Mon, 9 Oct 2023 16:24:05 +0200 Subject: [PATCH 2/2] Implements XDG config paths --- Cargo.lock | 75 ++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 2 +- src/credentials.rs | 76 ++++++++++++++++++++++++++++++++-------------- 3 files changed, 126 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c9ef5fe2..3069156e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,13 +193,13 @@ dependencies = [ "bytes", "clap", "color-eyre", + "dirs", "eyre", "flate2", "fs_extra", "futures", "git2", "hex", - "home", "human-panic", "predicates", "pretty_assertions", @@ -357,6 +357,27 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -429,7 +450,7 @@ checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "windows-sys", ] @@ -1042,6 +1063,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "os_info" version = "3.7.0" @@ -1083,7 +1110,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets", ] @@ -1295,6 +1322,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1304,6 +1340,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + [[package]] name = "regex" version = "1.9.5" @@ -1677,7 +1724,7 @@ checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix", "windows-sys", ] @@ -1688,6 +1735,26 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" +[[package]] +name = "thiserror" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.7" diff --git a/Cargo.toml b/Cargo.toml index 9818bdd1..a62fc2be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ flate2 = "1" futures = "0.3" git2 = { version = "0.18.1", optional = true } hex = "0.4.3" -home = "0.5.5" human-panic = "1" protoc-bin-vendored = { version = "3.0.0", optional = true } ring = "0.16" @@ -52,6 +51,7 @@ tracing-subscriber = "0.3" url = { version = "2.4", features = ["serde"] } walkdir = "2" async-recursion = "1.0.5" +dirs = "5.0.1" [dependencies.reqwest] version = "0.11" diff --git a/src/credentials.rs b/src/credentials.rs index bb587742..d75af245 100644 --- a/src/credentials.rs +++ b/src/credentials.rs @@ -14,13 +14,19 @@ use eyre::{Context, ContextCompat}; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, env, path::PathBuf}; +use std::{ + collections::HashMap, + env::{var, VarError}, + io::ErrorKind, + path::PathBuf, +}; use tokio::fs; use crate::registry::RegistryUri; /// Global configuration directory for `buffrs` pub const BUFFRS_HOME: &str = ".buffrs"; +pub const BUFFRS_DIR: &str = "buffrs"; /// Filename of the credential store pub const CREDENTIALS_FILE: &str = "credentials.toml"; @@ -32,46 +38,72 @@ pub struct Credentials { } impl Credentials { - fn location() -> eyre::Result { - let home = home::home_dir().wrap_err("Failed to locate home directory")?; + /// Get buffrs config directory. + fn buffrs_home() -> Result { + var("BUFFRS_HOME").map(PathBuf::from) + } + + /// XDG path: uses whichever config directory is appropriate for the platform. + /// + /// For example, on Linux this might be `~/.config/buffrs/credentials.toml`. + fn xdg_path() -> PathBuf { + Self::buffrs_home() + .unwrap_or_else(|_| dirs::config_dir().expect("get config dir").join(BUFFRS_DIR)) + .join(CREDENTIALS_FILE) + } - let home = env::var("BUFFRS_HOME").map(PathBuf::from).unwrap_or(home); + /// Legacy path: hard-coded to `~/.buffrs/credentials.toml`. + fn legacy_path() -> PathBuf { + Self::buffrs_home() + .unwrap_or_else(|_| dirs::home_dir().expect("get home dir").join(BUFFRS_HOME)) + .join(CREDENTIALS_FILE) + } - Ok(home.join(BUFFRS_HOME).join(CREDENTIALS_FILE)) + /// Possible locations for the credentials file + fn possible_locations() -> Vec { + vec![Self::xdg_path(), Self::legacy_path()] } /// Checks if the credentials exists pub async fn exists() -> eyre::Result { - fs::try_exists(Self::location()?) - .await - .wrap_err("Failed to detect credentials") + for location in &Self::possible_locations() { + if fs::try_exists(&location).await? { + return Ok(true); + } + } + + return Ok(false); } /// Reads the credentials from the file system pub async fn read() -> eyre::Result { - let toml = fs::read_to_string(Self::location()?) - .await - .wrap_err("Failed to read credentials")?; + for location in &Self::possible_locations() { + let contents = match fs::read_to_string(location).await { + Ok(string) => string, + Err(error) if error.kind() == ErrorKind::NotFound => continue, + Err(error) => return Err(error).wrap_err("opening credentials file"), + }; + + let raw: RawCredentialCollection = + toml::from_str(&contents).wrap_err("Failed to parse credentials")?; - let raw: RawCredentialCollection = - toml::from_str(&toml).wrap_err("Failed to parse credentials")?; + return Ok(raw.into()); + } - Ok(raw.into()) + eyre::bail!("cannot parse credentials") } /// Writes the credentials to the file system pub async fn write(&self) -> eyre::Result<()> { - fs::create_dir( - Self::location()? - .parent() - .wrap_err("Invalid credentials location")?, - ) - .await - .ok(); + let location = Self::xdg_path(); + + fs::create_dir(location.parent().wrap_err("Invalid credentials location")?) + .await + .ok(); let data: RawCredentialCollection = self.clone().into(); - fs::write(Self::location()?, toml::to_string(&data)?.into_bytes()) + fs::write(location, toml::to_string(&data)?.into_bytes()) .await .wrap_err("Failed to write credentials") }