diff --git a/cli/src/config.rs b/cli/src/config.rs index 4a7d3ae..9f7516e 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -6,6 +6,7 @@ use crate::output::Output; use crate::sources::*; use rayon::prelude::*; +use simple_eyre::eyre::Result; #[derive(Debug, serde::Deserialize)] pub struct Entry { @@ -14,14 +15,18 @@ pub struct Entry { } impl Entry { - pub fn fetch(&self) -> (String, Vec) { - let mut stream: Vec<_> = self.keys.par_iter().map(|k| k.fetch()).flatten().collect(); + pub fn fetch(&self) -> Result<(String, Vec)> { + let mut stream: Vec = self + .keys + .par_iter() + .flat_map(|k| k.fetch().unwrap()) + .collect(); // Deduplicate keys, no need for duplicated entries stream.sort(); stream.dedup_by(|a, b| a.key_data() == b.key_data()); - (self.name.clone(), stream) + Ok((self.name.clone(), stream)) } } @@ -32,9 +37,7 @@ pub struct Config { } impl Config { - pub fn fetch(&self) -> Result { - let keys = self.entries.into_par_iter().map(Entry::fetch).collect(); - - Ok(Output { keys }) + pub fn fetch(&self) -> Result { + self.entries.into_par_iter().map(Entry::fetch).collect() } } diff --git a/cli/src/output/mod.rs b/cli/src/output/mod.rs index c080a96..63447cc 100644 --- a/cli/src/output/mod.rs +++ b/cli/src/output/mod.rs @@ -5,6 +5,8 @@ use std::collections::HashMap; use std::io::{self, prelude::*}; +use rayon::prelude::*; + #[derive(PartialEq, Eq, Debug, Copy, Clone)] pub enum Format { JSON, @@ -18,7 +20,7 @@ impl Format { match self { Format::JSON => { serde_json::to_writer_pretty(&mut *w, &output.keys).map_err(io::Error::other)?; - writeln!(w, "") + writeln!(w) } Format::TOML => write!(w, "{}", toml::to_string_pretty(&output.keys).unwrap()), Format::CSV => as_csv(w, output), @@ -55,6 +57,17 @@ pub struct Output { pub keys: HashMap>, } +impl FromParallelIterator<(String, Vec)> for Output { + fn from_par_iter(iter: T) -> Self + where + T: IntoParallelIterator)>, + { + Output { + keys: iter.into_par_iter().collect(), + } + } +} + // TODO: proper escaping fn as_csv(w: &mut W, output: &Output) -> io::Result<()> { for (name, keys) in &output.keys { diff --git a/cli/src/sources/atproto.rs b/cli/src/sources/atproto.rs index 9e81284..2f08daa 100644 --- a/cli/src/sources/atproto.rs +++ b/cli/src/sources/atproto.rs @@ -8,15 +8,16 @@ use std::str::FromStr; use super::helpers; use serde::Deserialize; +use simple_eyre::eyre::Result; use ssh_key::PublicKey; #[derive(Debug)] -pub struct DID { +pub struct Did { method: String, id: String, } -impl FromStr for DID { +impl FromStr for Did { type Err = (); fn from_str(input: &str) -> Result { @@ -33,14 +34,14 @@ impl FromStr for DID { return Err(()); } - Ok(DID { + Ok(Did { method: chunks[1].into(), id: chunks[2].into(), }) } } -impl fmt::Display for DID { +impl fmt::Display for Did { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "did:{}:{}", self.method, self.id) } @@ -82,7 +83,7 @@ impl FromStr for Handle { return Err(()); } - if (b'0'..=b'9').contains(&segments.last().unwrap().as_bytes()[0]) { + if segments.last().unwrap().as_bytes()[0].is_ascii_digit() { return Err(()); } @@ -100,7 +101,7 @@ impl fmt::Display for InvalidHandle { #[derive(Debug)] pub enum Identifier { - DID(DID), + Did(Did), Handle(Handle), } @@ -110,7 +111,7 @@ impl FromStr for Identifier { fn from_str(input: &str) -> Result { input .parse() - .map(Identifier::DID) + .map(Identifier::Did) .or_else(|_| input.parse().map(Identifier::Handle)) .map_err(|_| InvalidHandle(input.into())) } @@ -119,7 +120,7 @@ impl FromStr for Identifier { impl fmt::Display for Identifier { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { match *self { - Identifier::DID(ref did) => write!(f, "{}", did), + Identifier::Did(ref did) => write!(f, "{}", did), Identifier::Handle(ref handle) => write!(f, "{}", handle), } } @@ -146,14 +147,14 @@ impl FromStr for ATProto { fn legal_segment(segment: &str) -> bool { let bytes = segment.as_bytes(); - segment != "" - && bytes.into_iter().all(|&b| allowed_byte(b)) + !segment.is_empty() + && bytes.iter().all(|&b| allowed_byte(b)) && *bytes.first().unwrap() != b'-' && *bytes.last().unwrap() != b'-' } fn allowed_byte(c: u8) -> bool { - (b'0'..=b'9').contains(&c) || (b'a'..=b'z').contains(&c) || c == b'-' + c.is_ascii_digit() || c.is_ascii_lowercase() || c == b'-' } fn default_atproto() -> String { @@ -179,16 +180,18 @@ mod resp { key: String, } - impl Into for &Record { - fn into(self) -> PublicKey { - PublicKey::from_openssh(&self.value.key).unwrap() + impl TryFrom<&Record> for PublicKey { + type Error = ssh_key::Error; + + fn try_from(val: &Record) -> ssh_key::Result { + PublicKey::from_openssh(&val.value.key) } } } impl super::Fetch for ATProto { - fn fetch(&self) -> Vec { - let mut url = url::Url::parse(&self.host).unwrap(); + fn fetch(&self) -> Result> { + let mut url = url::Url::parse(&self.host)?; url.query_pairs_mut() .append_pair("repo", &self.handle.to_string()) @@ -197,14 +200,16 @@ impl super::Fetch for ATProto { url.set_path("xrpc/com.atproto.repo.listRecords"); let data = ureq::get(&url.to_string()) - .call() - .unwrap() + .call()? .body_mut() - .read_to_string() - .unwrap(); + .read_to_string()?; - let decoded: resp::Resp = serde_json::from_str(&data).unwrap(); + let decoded: resp::Resp = serde_json::from_str(&data)?; - decoded.records.iter().map(Into::into).collect() + decoded + .records + .iter() + .map(|val| val.try_into().map_err(Into::into)) + .collect() } } diff --git a/cli/src/sources/helpers.rs b/cli/src/sources/helpers.rs index 6684b5a..46eb34f 100644 --- a/cli/src/sources/helpers.rs +++ b/cli/src/sources/helpers.rs @@ -5,7 +5,7 @@ use std::fmt; use std::str::FromStr; -use serde::{de, Deserialize, Deserializer}; +use serde::{Deserialize, Deserializer, de}; pub fn string_or_struct<'de, T, D>(deserializer: D) -> Result where @@ -61,7 +61,7 @@ where { struct DeFromStr(std::marker::PhantomData); - impl<'de, T> de::Visitor<'de> for DeFromStr + impl de::Visitor<'_> for DeFromStr where T: FromStr, ::Err: fmt::Display, diff --git a/cli/src/sources/mod.rs b/cli/src/sources/mod.rs index fcd8e5b..5b0a327 100644 --- a/cli/src/sources/mod.rs +++ b/cli/src/sources/mod.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: EUPL-1.2 use serde::Deserialize; +use simple_eyre::eyre::Result; use ssh_key::PublicKey; use std::process::Command; @@ -13,7 +14,7 @@ mod helpers; pub use atproto::ATProto; pub trait Fetch: std::fmt::Debug { - fn fetch(&self) -> Vec; + fn fetch(&self) -> Result>; } #[derive(Debug, Deserialize)] @@ -32,7 +33,7 @@ pub enum Source { } impl Fetch for Source { - fn fetch(&self) -> Vec { + fn fetch(&self) -> Result> { match *self { Source::Raw(ref raw) => raw.fetch(), Source::Hosts(ref raw) => raw.fetch(), @@ -58,7 +59,7 @@ impl Fetch for Source { } } -fn normalize_sourcehut<'a>(s: &'a str) -> std::borrow::Cow<'a, str> { +fn normalize_sourcehut(s: &str) -> std::borrow::Cow { if s.starts_with("~") { s.into() } else { @@ -70,8 +71,8 @@ fn normalize_sourcehut<'a>(s: &'a str) -> std::borrow::Cow<'a, str> { pub struct Raw(Box<[PublicKey]>); impl Fetch for Raw { - fn fetch(&self) -> Vec { - self.0.clone().into() + fn fetch(&self) -> Result> { + Ok(self.0.clone().into()) } } @@ -79,12 +80,11 @@ impl Fetch for Raw { pub struct Hosts(pub Box<[String]>); impl Fetch for Hosts { - fn fetch(&self) -> Vec { + fn fetch(&self) -> Result> { // TODO: Check if we can do it in-process instead of shelling out to `ssh-keyscan` let result = Command::new("ssh-keyscan").args(&self.0).output().unwrap(); - std::str::from_utf8(&result.stdout) - .unwrap() + std::str::from_utf8(&result.stdout)? .trim() .split('\n') .map(str::trim) @@ -93,7 +93,7 @@ impl Fetch for Hosts { // Ignore first column as it contain hostname which is not // needed there .map(|line| line.split_once(' ').unwrap().1) - .map(|k| PublicKey::from_openssh(&k).unwrap()) + .map(|k| PublicKey::from_openssh(k).map_err(Into::into)) .collect() } } @@ -104,16 +104,14 @@ pub struct Http { } impl Fetch for Http { - fn fetch(&self) -> Vec { + fn fetch(&self) -> Result> { ureq::get(&self.url) - .call() - .unwrap() + .call()? .body_mut() - .read_to_string() - .unwrap() + .read_to_string()? .trim() .split('\n') - .map(|s| PublicKey::from_openssh(s).unwrap()) + .map(|s| PublicKey::from_openssh(s).map_err(Into::into)) .collect() } }