History

feat: `helper::invoke(helper, action, context)` function that allows for more flexible helper invocation (#450)

Sebastian Thiel committed 4 years ago

Commit 64bc2ec1 parent af27d201 file · +55 −4Browse files

Unified · Split

git-credentials/src/helper.rs+55−4
@@ -6,6 +6,10 @@ pub enum Kind { · 5 unmodified lines
66 GitCredential,
77 }
88
9+/// Additional context to be passed to the credentials helper.
10+// TODO: fill in what's needed per configuration
11+pub struct Context;
12+
913 mod error {
1014 /// The error used in the [credentials helper][crate::helper()].
1115 #[derive(Debug, thiserror::Error)]
@@ -15,8 +19,8 @@ mod error { · 3 unmodified lines
1519 Io(#[from] std::io::Error),
1620 #[error("Could not find {name:?} in output of credentials helper")]
1721 KeyNotFound { name: String },
18− #[error("Credentials helper program failed with status code {code:?}")]
19− CredentialsHelperFailed { code: Option<i32> },
22+ #[error(transparent)]
23+ CredentialsHelperFailed { source: std::io::Error },
2024 }
2125 }
2226 pub use error::Error;
@@ -80,7 +84,7 @@ pub struct Outcome { · 57 unmodified lines
8084 }
8185
8286 pub(crate) mod function {
83− use crate::helper::{message, Action, Error, NextAction, Outcome, Result};
87+ use crate::helper::{message, Action, Context, Error, NextAction, Outcome, Result};
8488 use std::io::Read;
8589 use std::process::{Command, Stdio};
8690
@@ -125,7 +129,9 @@ pub(crate) mod function { · 38 unmodified lines
125129 .transpose()?;
126130 let status = child.wait()?;
127131 if !status.success() {
128− return Err(Error::CredentialsHelperFailed { code: status.code() });
132+ return Err(Error::CredentialsHelperFailed {
133+ source: std::io::Error::new(std::io::ErrorKind::Other, "credentials helper failed"),
134+ });
129135 }
130136
131137 match stdout {
@@ -150,7 +156,52 @@ pub(crate) mod function { · 18 unmodified lines
150156 }
151157 }
152158 }
159+
160+ /// Invoke the given `helper` with `action` in `context`.
161+ ///
162+ /// Usually the first call is performed with [`Action::Fill`] to obtain an identity, which subsequently can be used.
163+ /// On successful usage, use [`NextAction::approve()`], otherwise [`NextAction::reject()`].
164+ pub fn invoke(mut helper: impl crate::Helper, action: Action<'_>, _context: Context) -> Result {
165+ let (stdin, stdout) = helper.start(&action)?;
166+ action.send(stdin)?;
167+ let stdout = stdout
168+ .map(|mut stdout| {
169+ let mut buf = Vec::new();
170+ stdout.read_to_end(&mut buf).map(|_| buf)
171+ })
172+ .transpose()?;
173+ helper.finish().map_err(|err| {
174+ if err.kind() == std::io::ErrorKind::Other {
175+ Error::CredentialsHelperFailed { source: err }
176+ } else {
177+ err.into()
178+ }
179+ })?;
180+
181+ match stdout {
182+ None => Ok(None),
183+ Some(stdout) => {
184+ let kvs = message::decode(stdout.as_slice())?;
185+ let find = |name: &str| {
186+ kvs.iter()
187+ .find(|(k, _)| k == name)
188+ .ok_or_else(|| Error::KeyNotFound { name: name.into() })
189+ .map(|(_, n)| n.to_owned())
190+ };
191+ Ok(Some(Outcome {
192+ identity: git_sec::identity::Account {
193+ username: find("username")?,
194+ password: find("password")?,
195+ },
196+ next: NextAction {
197+ previous_output: stdout.into(),
198+ },
199+ }))
200+ }
201+ }
202+ }
153203 }
204+pub use function::invoke;
154205
155206 ///
156207 pub mod message {