Unified · Split
| @@ -6,6 +6,10 @@ pub enum Kind { · 5 unmodified lines | ||
| 6 | 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 | |
| 11 | +pub struct Context; | |
| 12 | + | |
| 9 | 13 | mod error { |
| 10 | 14 | /// The error used in the [credentials helper][crate::helper()]. |
| 11 | 15 | #[derive(Debug, thiserror::Error)] |
| @@ -15,8 +19,8 @@ mod error { · 3 unmodified lines | ||
| 15 | 19 | Io(#[from] std::io::Error), |
| 16 | 20 | #[error("Could not find {name:?} in output of credentials helper")] |
| 17 | 21 | 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 }, | |
| 20 | 24 | } |
| 21 | 25 | } |
| 22 | 26 | pub use error::Error; |
| @@ -80,7 +84,7 @@ pub struct Outcome { · 57 unmodified lines | ||
| 80 | 84 | } |
| 81 | 85 | |
| 82 | 86 | 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}; | |
| 84 | 88 | use std::io::Read; |
| 85 | 89 | use std::process::{Command, Stdio}; |
| 86 | 90 | |
| @@ -125,7 +129,9 @@ pub(crate) mod function { · 38 unmodified lines | ||
| 125 | 129 | .transpose()?; |
| 126 | 130 | let status = child.wait()?; |
| 127 | 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 | 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 | } |
| 204 | +pub use function::invoke; | |
| 154 | 205 | |
| 155 | 206 | /// |
| 156 | 207 | pub mod message { |