don't
5
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(cred): replace anyhow with exn for error handling

Signed-off-by: tjh <x@tjh.dev>

tjh c7aee0c1 3672ba0a

+173 -62
+1 -2
Cargo.lock
··· 2047 2047 name = "gordian-cred" 2048 2048 version = "0.0.0" 2049 2049 dependencies = [ 2050 - "anyhow", 2051 - "axum", 2052 2050 "clap", 2053 2051 "data-encoding", 2054 2052 "directories", 2053 + "exn", 2055 2054 "gordian-auth", 2056 2055 "gordian-identity", 2057 2056 "gordian-types",
+1
Cargo.toml
··· 31 31 anyhow = "1.0.100" 32 32 axum = "0.8.4" 33 33 data-encoding = "2.9.0" 34 + exn = "0.3.0" 34 35 gix = { version = "0.78.0", features = ["max-performance"] } 35 36 reqwest = { version = "0.13.1", features = ["json"] } 36 37 serde = { version = "1.0.226", features = ["derive"] }
+1 -2
crates/gordian-cred/Cargo.toml
··· 13 13 gordian-auth.workspace = true 14 14 gordian-identity.workspace = true 15 15 16 - anyhow.workspace = true 17 - axum.workspace = true 16 + exn.workspace = true 18 17 reqwest.workspace = true 19 18 serde.workspace = true 20 19 serde_json.workspace = true
+18 -2
crates/gordian-cred/src/commands/auth.rs
··· 1 + use core::{error, fmt}; 2 + 3 + use exn::Exn; 4 + use exn::ResultExt as _; 1 5 use owo_colors::{OwoColorize, Stream}; 2 6 3 7 #[derive(clap::Args)] ··· 17 13 Status, 18 14 } 19 15 16 + #[derive(Debug)] 17 + pub struct AuthError; 18 + 19 + impl fmt::Display for AuthError { 20 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 21 + f.write_str("Failed to run command") 22 + } 23 + } 24 + 25 + impl error::Error for AuthError {} 26 + 20 27 impl crate::RunCommand for Auth { 21 - type Error = anyhow::Error; 28 + type Error = Exn<AuthError>; 29 + 22 30 fn run(self) -> Result<(), Self::Error> { 23 31 match self.command { 24 32 AuthCommand::Status => { 25 - let config = crate::config::load()?; 33 + let config = crate::config::load().or_raise(|| AuthError)?; 26 34 match config.active_account { 27 35 Some(active_account) => { 28 36 println!(
+50 -22
crates/gordian-cred/src/commands/git_credential.rs
··· 1 - use anyhow::Context as _; 1 + use core::{error, fmt}; 2 2 use data_encoding::{BASE32HEX_NOPAD, BASE64URL_NOPAD}; 3 + use exn::{Exn, ResultExt as _}; 3 4 use gordian_auth::jwt::{Algorithm, Curve, Header}; 5 + use gordian_types::OwnedDid; 4 6 use owo_colors::{OwoColorize, Stream::Stderr}; 5 7 use ssh_agent_client_rs::{Client, Identity}; 6 8 use ssh_key::public::{EcdsaPublicKey, KeyData}; 7 - use std::{collections::HashSet, path::Path}; 9 + use std::{borrow::Cow, collections::HashSet, path::Path}; 8 10 use time::OffsetDateTime; 9 11 use tokio::runtime::Builder; 10 12 ··· 29 27 Capability, 30 28 } 31 29 30 + #[derive(Debug)] 31 + pub struct CredentialError(Cow<'static, str>); 32 + 33 + impl CredentialError { 34 + fn new(message: impl Into<Cow<'static, str>>) -> Self { 35 + Self(message.into()) 36 + } 37 + } 38 + 39 + impl fmt::Display for CredentialError { 40 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 + f.write_str(&self.0) 42 + } 43 + } 44 + 45 + impl error::Error for CredentialError {} 46 + 32 47 impl crate::RunCommand for GitCredential { 33 - type Error = anyhow::Error; 48 + type Error = Exn<CredentialError>; 34 49 35 50 fn run(self) -> Result<(), Self::Error> { 36 51 match self.op { 37 52 Operation::Get => Builder::new_multi_thread() 38 53 .enable_all() 39 - .build()? 54 + .build() 55 + .or_raise(|| CredentialError::new("Failed to initialise async runtime"))? 40 56 .block_on(self.get()), 41 57 Operation::Erase => self.erase(), 42 58 Operation::Store => self.store(), ··· 65 45 66 46 impl GitCredential { 67 47 #[tracing::instrument] 68 - fn erase(self) -> anyhow::Result<()> { 69 - let stdin = read_stdin()?; 48 + fn erase(self) -> Result<(), Exn<CredentialError>> { 49 + let stdin = read_stdin().or_raise(|| CredentialError::new("Failed to erase credential"))?; 70 50 let wants = Wants::parse(&stdin); 71 51 tracing::trace!(?wants); 72 52 Ok(()) 73 53 } 74 54 75 55 #[tracing::instrument] 76 - fn store(self) -> anyhow::Result<()> { 77 - let stdin = read_stdin()?; 56 + fn store(self) -> Result<(), Exn<CredentialError>> { 57 + let stdin = read_stdin().or_raise(|| CredentialError::new("Failed to store credentail"))?; 78 58 let wants = Wants::parse(&stdin); 79 59 tracing::trace!(?wants); 80 60 Ok(()) 81 61 } 82 62 83 63 #[tracing::instrument] 84 - fn capability(self) -> anyhow::Result<()> { 64 + fn capability(self) -> Result<(), Exn<CredentialError>> { 85 65 println!("version 0"); 86 66 println!("capability authtype"); 87 67 println!("capability state"); ··· 90 70 } 91 71 92 72 #[tracing::instrument] 93 - async fn get(self) -> anyhow::Result<()> { 94 - let stdin = read_stdin()?; 73 + async fn get(self) -> Result<(), Exn<CredentialError>> { 74 + let err = || CredentialError::new("Failed to generate credential"); 75 + 76 + let stdin = read_stdin().or_raise(err)?; 95 77 let wants = Wants::parse(&stdin); 96 78 tracing::debug!(?wants); 97 79 ··· 107 85 return Ok(()); 108 86 }; 109 87 110 - let config = crate::config::load()?; 88 + let config = crate::config::load().or_raise(err)?; 111 89 tracing::debug!(?config); 112 90 let Some(account_did) = &config.active_account else { 113 91 eprintln!("No active account. Use gk login @handle first"); ··· 137 115 .collect(); 138 116 139 117 // // Find a matching key in the SSH agent. 140 - let agent_path = std::env::var("SSH_AUTH_SOCK").context("Failed to read SSH_AUTH_SOCK")?; 141 - let mut client = Client::connect(Path::new(agent_path.as_str()))?; 118 + let agent_path = std::env::var("SSH_AUTH_SOCK").or_raise(err)?; 119 + let mut client = Client::connect(Path::new(agent_path.as_str())).or_raise(err)?; 142 120 let Some(agent_identity) = client 143 - .list_all_identities()? 121 + .list_all_identities() 122 + .or_raise(err)? 144 123 .into_iter() 145 124 .find_map(|identity| { 146 125 keys.iter().find_map(|k| match &identity { ··· 151 128 }) 152 129 else { 153 130 println!("quit=true"); 154 - return Err(anyhow::anyhow!("Failed to find a matching identity")); 131 + exn::bail!(CredentialError::new("Failed to find matching identity")); 155 132 }; 156 133 157 134 eprintln!( 158 135 "Found key in agent: {}", 159 136 agent_identity 160 - .to_openssh()? 137 + .to_openssh() 138 + .or_raise(err)? 161 139 .if_supports_color(Stderr, |text| text.green()) 162 140 ); 163 141 164 142 // // We found a key, construct our JWT claims. 165 143 let iss = account_did.clone(); 166 - let aud = format!("did:web:{knot}").parse()?; 144 + let aud = format!("did:web:{knot}") 145 + .parse::<OwnedDid>() 146 + .or_raise(err)?; 167 147 let iat = OffsetDateTime::now_utc().unix_timestamp(); 168 148 let exp = iat + 45; 169 149 let jti: [u8; 16] = rand::random(); ··· 195 169 "{}", 196 170 format!( 197 171 "Unsupported key type for key: {}", 198 - agent_identity.to_openssh()? 172 + agent_identity.to_openssh().or_raise(err)? 199 173 ) 200 174 .if_supports_color(Stderr, |text| text.red()) 201 175 ); ··· 204 178 } 205 179 }; 206 180 207 - let header_encoded = BASE64URL_NOPAD.encode(&serde_json::to_vec(&header)?); 208 - let claims_encoded = BASE64URL_NOPAD.encode(&serde_json::to_vec(&claims)?); 181 + let header_encoded = BASE64URL_NOPAD.encode(&serde_json::to_vec(&header).or_raise(err)?); 182 + let claims_encoded = BASE64URL_NOPAD.encode(&serde_json::to_vec(&claims).or_raise(err)?); 209 183 210 184 // Construct the message part of the JWT. 211 185 let mut buffer = Vec::with_capacity(1 + header_encoded.len() + claims_encoded.len()); ··· 213 187 buffer.push(b'.'); 214 188 buffer.extend_from_slice(claims_encoded.as_bytes()); 215 189 216 - let signature = client.sign_with_ref(&Identity::PublicKey(agent_identity), &buffer)?; 190 + let signature = client 191 + .sign_with_ref(&Identity::PublicKey(agent_identity), &buffer) 192 + .or_raise(err)?; 217 193 let signature_encoded = BASE64URL_NOPAD.encode(signature.as_bytes()); 218 194 buffer.push(b'.'); 219 195 buffer.extend_from_slice(signature_encoded.as_bytes());
+43 -16
crates/gordian-cred/src/commands/git_setup.rs
··· 1 - use std::process::Command; 1 + use core::{error, fmt}; 2 + use std::{borrow::Cow, process::Command}; 2 3 3 - /// Setup the credential helper for use with git 4 + use exn::{Exn, OptionExt, ResultExt}; 5 + 6 + /// Install the credential helper for use with git 4 7 #[derive(Debug, clap::Args)] 5 - pub struct GitSetup { 8 + pub struct Install { 6 9 /// Hostnames to associate the credential helper with 7 - #[arg(long, short = 'H', default_values_t = default_hostnames())] 8 - hostname: Vec<String>, 10 + #[arg(default_values_t = default_hostnames())] 11 + host: Vec<String>, 9 12 } 10 13 11 14 fn default_hostnames() -> Vec<String> { ··· 18 15 ] 19 16 } 20 17 21 - impl crate::RunCommand for GitSetup { 22 - type Error = anyhow::Error; 18 + #[derive(Debug)] 19 + pub struct SetupError(Cow<'static, str>); 20 + 21 + impl SetupError { 22 + fn new(message: impl Into<Cow<'static, str>>) -> Self { 23 + Self(message.into()) 24 + } 25 + } 26 + 27 + impl fmt::Display for SetupError { 28 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 29 + f.write_str(&self.0) 30 + } 31 + } 32 + 33 + impl error::Error for SetupError {} 34 + 35 + impl crate::RunCommand for Install { 36 + type Error = Exn<SetupError>; 23 37 24 38 fn run(self) -> Result<(), Self::Error> { 25 - let me = std::env::current_exe()?; 26 - let command = me.to_str().ok_or(anyhow::anyhow!("Argggg"))?; 39 + const COMMAND: &str = "/usr/bin/git"; 40 + 41 + let err = || SetupError::new("Failed to setup credential helper"); 42 + 43 + let me = std::env::current_exe().or_raise(err)?; 44 + let command = me.to_str().ok_or_raise(err)?; 27 45 let helper = format!("!{command} git-credential"); 28 46 29 - for hostname in self.hostname { 47 + for hostname in self.host { 30 48 let key = format!("credential.{hostname}.helper"); 31 - Command::new("git") 49 + Command::new(COMMAND) 32 50 .args(["config", "unset", "--global", "--all", &key]) 33 - .status()?; 51 + .status() 52 + .or_raise(|| SetupError::new(format!("Failed to run command '{COMMAND}'")))?; 34 53 35 - Command::new("git") 54 + Command::new(COMMAND) 36 55 .args(["config", "set", "--global", &key, ""]) 37 - .status()?; 56 + .status() 57 + .or_raise(|| SetupError::new(format!("Failed to run command '{COMMAND}'")))?; 38 58 39 - Command::new("git") 59 + Command::new(COMMAND) 40 60 .args(["config", "set", "--global", "--append", &key, &helper]) 41 - .status()?; 61 + .status() 62 + .or_raise(|| SetupError::new(format!("Failed to run command '{COMMAND}'")))?; 42 63 } 43 64 44 65 Ok(())
+32 -8
crates/gordian-cred/src/config.rs
··· 1 + use core::{error, fmt}; 1 2 use directories::ProjectDirs; 3 + use exn::{Exn, OptionExt, ResultExt}; 2 4 use gordian_types::OwnedDid; 3 5 use serde::{Deserialize, Serialize}; 4 6 use std::{ 7 + borrow::Cow, 5 8 collections::{HashMap, HashSet}, 6 9 path::PathBuf, 7 10 }; ··· 25 22 pub keys: HashMap<OwnedDid, HashSet<PublicKey>>, 26 23 } 27 24 28 - fn config_directory() -> anyhow::Result<PathBuf> { 25 + #[derive(Debug)] 26 + pub struct ConfigError(Cow<'static, str>); 27 + 28 + impl ConfigError { 29 + fn new(message: impl Into<Cow<'static, str>>) -> Self { 30 + Self(message.into()) 31 + } 32 + } 33 + 34 + impl fmt::Display for ConfigError { 35 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 36 + f.write_str(&self.0) 37 + } 38 + } 39 + 40 + impl error::Error for ConfigError {} 41 + 42 + fn config_directory() -> Result<PathBuf, Exn<ConfigError>> { 43 + let err = || ConfigError::new("Could not get configuration directory"); 44 + 29 45 let path = ProjectDirs::from("dev", "tjh", env!("CARGO_BIN_NAME")) 30 - .ok_or(anyhow::anyhow!("failed to construct config path"))? 46 + .ok_or_raise(err)? 31 47 .config_dir() 32 48 .to_path_buf(); 33 49 34 50 // Ensure the directory exists. 35 - std::fs::create_dir_all(&path)?; 51 + std::fs::create_dir_all(&path).or_raise(err)?; 36 52 37 53 Ok(path) 38 54 } 39 55 40 - pub fn config_path() -> anyhow::Result<PathBuf> { 56 + pub fn config_path() -> Result<PathBuf, Exn<ConfigError>> { 41 57 Ok(config_directory()?.join("config.toml")) 42 58 } 43 59 44 - pub fn load() -> anyhow::Result<Configuration> { 60 + pub fn load() -> Result<Configuration, Exn<ConfigError>> { 45 61 match std::fs::read_to_string(config_path()?) { 46 62 Ok(buffer) => match toml::from_str(&buffer) { 47 63 Ok(config) => Ok(config), ··· 71 49 } 72 50 73 51 #[allow(unused)] 74 - pub fn save(config: &Configuration) -> anyhow::Result<()> { 75 - let buffer = toml::to_string_pretty(config)?; 76 - std::fs::write(config_path()?, buffer)?; 52 + pub fn save(config: &Configuration) -> Result<(), Exn<ConfigError>> { 53 + let err = || ConfigError::new("Could not write configuration"); 54 + 55 + let buffer = toml::to_string_pretty(config).or_raise(err)?; 56 + std::fs::write(config_path()?, buffer).or_raise(err)?; 77 57 Ok(()) 78 58 }
+27 -10
crates/gordian-cred/src/main.rs
··· 1 + use core::{error, fmt}; 2 + 1 3 mod commands; 2 4 mod config; 3 5 4 6 use clap::{Parser, Subcommand}; 5 - use commands::{Auth, GitCredential, GitSetup}; 7 + use commands::{Auth, GitCredential, Install}; 8 + use exn::{Exn, ResultExt as _}; 6 9 use tracing_subscriber::{EnvFilter, layer::SubscriberExt as _, util::SubscriberInitExt as _}; 7 10 8 11 pub trait RunCommand { 9 - type Error: core::fmt::Display; 12 + type Error; 13 + 10 14 fn run(self) -> Result<(), Self::Error>; 11 15 } 12 16 ··· 18 14 enum Command { 19 15 Auth(Auth), 20 16 GitCredential(GitCredential), 21 - GitSetup(GitSetup), 17 + Install(Install), 22 18 } 23 19 20 + #[derive(Debug)] 21 + struct Error; 22 + 23 + impl fmt::Display for Error { 24 + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 25 + f.write_str("Failed to run command") 26 + } 27 + } 28 + 29 + impl error::Error for Error {} 30 + 24 31 impl RunCommand for Command { 25 - type Error = anyhow::Error; 32 + type Error = Exn<Error>; 33 + 26 34 fn run(self) -> Result<(), Self::Error> { 35 + let err = || Error; 36 + 27 37 match self { 28 - Self::Auth(sc) => sc.run()?, 29 - Self::GitCredential(sc) => sc.run()?, 30 - Self::GitSetup(sc) => sc.run()?, 38 + Self::Auth(sc) => sc.run().or_raise(err)?, 39 + Self::GitCredential(sc) => sc.run().or_raise(err)?, 40 + Self::Install(sc) => sc.run().or_raise(err)?, 31 41 } 32 42 Ok(()) 33 43 } ··· 53 35 command: Command, 54 36 } 55 37 56 - fn main() -> anyhow::Result<()> { 38 + fn main() { 57 39 tracing_subscriber::registry() 58 40 .with(EnvFilter::from_default_env()) 59 41 .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr)) 60 42 .try_init() 61 43 .expect("Failed to initialise tracing subscriber"); 62 44 63 - Arguments::parse().command.run()?; 64 - Ok(()) 45 + Arguments::parse().command.run().unwrap(); 65 46 }