use std::ffi::OsString;
use bstr::BString;
#[derive(Debug, Copy, Clone)]
pub enum Action {
Get,
Store,
Erase,
}
impl TryFrom<OsString> for Action {
type Error = Error;
fn try_from(value: OsString) -> Result<Self, Self::Error> {
Ok(match value.to_str() {
Some("fill" | "get") => Action::Get,
Some("approve" | "store") => Action::Store,
Some("reject" | "erase") => Action::Erase,
_ => return Err(Error::ActionInvalid { name: value }),
})
}
}
impl Action {
pub fn as_str(&self) -> &'static str {
match self {
Action::Get => "get",
Action::Store => "store",
Action::Erase => "erase",
}
}
}
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Action named {name:?} is invalid, need 'get', 'store', 'erase' or 'fill', 'approve', 'reject'")]
ActionInvalid { name: OsString },
#[error("The first argument must be the action to perform")]
ActionMissing,
#[error(transparent)]
Helper {
source: Box<dyn std::error::Error + Send + Sync + 'static>,
},
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Context(#[from] crate::protocol::context::decode::Error),
#[error("Credentials for {url:?} could not be obtained")]
CredentialsMissing { url: BString },
#[error("The url wasn't provided in input - the git credentials protocol mandates this")]
UrlMissing,
}
pub(crate) mod function {
use std::ffi::OsString;
use crate::{
program::main::{Action, Error},
protocol::Context,
};
pub fn main<CredentialsFn, E>(
args: impl IntoIterator<Item = OsString>,
mut stdin: impl std::io::Read,
stdout: impl std::io::Write,
credentials: CredentialsFn,
) -> Result<(), Error>
where
CredentialsFn: FnOnce(Action, Context) -> Result<Option<Context>, E>,
E: std::error::Error + Send + Sync + 'static,
{
let action: Action = args.into_iter().next().ok_or(Error::ActionMissing)?.try_into()?;
let mut buf = Vec::<u8>::with_capacity(512);
stdin.read_to_end(&mut buf)?;
let ctx = Context::from_bytes(&buf)?;
if ctx.url.is_none() {
return Err(Error::UrlMissing);
}
let res = credentials(action, ctx).map_err(|err| Error::Helper { source: Box::new(err) })?;
match (action, res) {
(Action::Get, None) => {
return Err(Error::CredentialsMissing {
url: Context::from_bytes(&buf)?.url.expect("present and checked above"),
})
}
(Action::Get, Some(ctx)) => ctx.write_to(stdout)?,
(Action::Erase | Action::Store, None) => {}
(Action::Erase | Action::Store, Some(_)) => {
panic!("BUG: credentials helper must not return context for erase or store actions")
}
}
Ok(())
}
}