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
6 GitCredential,6 GitCredential,
7}7}
8 8
9/// Additional context to be passed to the credentials helper.
10// TODO: fill in what's needed per configuration
11pub struct Context;
12
9mod error {13mod error {
10 /// The error used in the [credentials helper][crate::helper()].14 /// The error used in the [credentials helper][crate::helper()].
11 #[derive(Debug, thiserror::Error)]15 #[derive(Debug, thiserror::Error)]
@@ -15,8 +19,8 @@ mod error { · 3 unmodified lines
15 Io(#[from] std::io::Error),19 Io(#[from] std::io::Error),
16 #[error("Could not find {name:?} in output of credentials helper")]20 #[error("Could not find {name:?} in output of credentials helper")]
17 KeyNotFound { name: String },21 KeyNotFound { name: String },
18 #[error("Credentials helper program failed with status code {code:?}")]22 #[error(transparent)]
19 CredentialsHelperFailed { code: Option<i32> },23 CredentialsHelperFailed { source: std::io::Error },
20 }24 }
21}25}
22pub use error::Error;26pub use error::Error;
@@ -80,7 +84,7 @@ pub struct Outcome { · 57 unmodified lines
80}84}
81 85
82pub(crate) mod function {86pub(crate) mod function {
83 use crate::helper::{message, Action, Error, NextAction, Outcome, Result};87 use crate::helper::{message, Action, Context, Error, NextAction, Outcome, Result};
84 use std::io::Read;88 use std::io::Read;
85 use std::process::{Command, Stdio};89 use std::process::{Command, Stdio};
86 90
@@ -125,7 +129,9 @@ pub(crate) mod function { · 38 unmodified lines
125 .transpose()?;129 .transpose()?;
126 let status = child.wait()?;130 let status = child.wait()?;
127 if !status.success() {131 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 });
129 }135 }
130 136
131 match stdout {137 match stdout {
@@ -150,7 +156,52 @@ pub(crate) mod function { · 18 unmodified lines
150 }156 }
151 }157 }
152 }158 }
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 }
153}203}
204pub use function::invoke;
154 205
155///206///
156pub mod message {207pub mod message {