use std::{
fmt::{self, Display, Formatter},
path::Path,
str::FromStr,
};
use compose_spec_macros::{DeserializeFromStr, SerializeDisplay};
use thiserror::Error;
#[derive(SerializeDisplay, DeserializeFromStr, Default, Clone, Debug, PartialEq, Eq, Hash)]
#[serde(expecting = r#""default" or "{id}={path}" string"#)]
pub enum SshAuth {
#[default]
Default,
Id(Id),
}
impl SshAuth {
#[must_use]
pub const fn as_id(&self) -> Option<&Id> {
if let Self::Id(v) = self {
Some(v)
} else {
None
}
}
#[must_use]
pub const fn id(&self) -> Option<&str> {
if let Self::Id(v) = self {
Some(v.id())
} else {
None
}
}
#[must_use]
pub const fn path(&self) -> Option<&Path> {
if let Self::Id(v) = self {
Some(v.path())
} else {
None
}
}
}
impl FromStr for SshAuth {
type Err = IdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"default" => Ok(Self::Default),
s => Ok(Self::Id(s.parse()?)),
}
}
}
impl Display for SshAuth {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::Default => f.write_str("default"),
Self::Id(id) => Display::fmt(id, f),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Id {
id: Box<str>,
path: Box<Path>,
}
impl Id {
pub fn new<I, P>(id: I, path: P) -> Result<Self, IdError>
where
I: AsRef<str>,
P: AsRef<Path>,
{
let id = id.as_ref();
let path = path.as_ref();
if id.is_empty() {
Err(IdError::EmptyId)
} else if path.as_os_str().is_empty() {
Err(IdError::MissingPath)
} else {
Ok(Self {
id: id.into(),
path: path.into(),
})
}
}
#[must_use]
pub const fn id(&self) -> &str {
&self.id
}
#[must_use]
pub const fn path(&self) -> &Path {
&self.path
}
}
impl FromStr for Id {
type Err = IdError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (id, path) = s.split_once('=').ok_or(IdError::MissingPath)?;
Self::new(id, path)
}
}
impl Display for Id {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let Self { id, path } = self;
write!(f, "{id}={}", path.display())
}
}
#[derive(Error, Debug, Clone, Copy, PartialEq, Eq)]
pub enum IdError {
#[error("ssh auth ID cannot be empty")]
EmptyId,
#[error("non-default ssh auth requires a path to a PEM file or to the ssh-agent socket")]
MissingPath,
}