use std::io::Write as _;
use crate::helper::{Credentials, Helper, HelperError};
use crate::query::Query;
pub struct HelperChain {
helpers: Vec<Box<dyn Helper>>,
}
impl HelperChain {
pub fn new(helpers: Vec<Box<dyn Helper>>) -> Self {
Self { helpers }
}
pub fn is_empty(&self) -> bool {
self.helpers.is_empty()
}
}
impl Helper for HelperChain {
fn fill(&self, query: &Query) -> Result<Option<Credentials>, HelperError> {
let mut last_err: Option<HelperError> = None;
for h in &self.helpers {
match h.fill(query) {
Ok(Some(c)) => return Ok(Some(c)),
Ok(None) => continue,
Err(e) => {
let mut err = std::io::stderr().lock();
let _ = writeln!(err, "credential fill error: {e}");
last_err = Some(e);
continue;
}
}
}
match last_err {
Some(e) => Err(e),
None => Ok(None),
}
}
fn approve(&self, query: &Query, creds: &Credentials) -> Result<(), HelperError> {
let mut first_err = None;
for h in &self.helpers {
if let Err(e) = h.approve(query, creds) {
first_err.get_or_insert(e);
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
fn reject(&self, query: &Query, creds: &Credentials) -> Result<(), HelperError> {
let mut first_err = None;
for h in &self.helpers {
if let Err(e) = h.reject(query, creds) {
first_err.get_or_insert(e);
}
}
match first_err {
Some(e) => Err(e),
None => Ok(()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
#[derive(Default)]
struct StaticHelper {
answer: Option<Credentials>,
approves: Mutex<Vec<(Query, Credentials)>>,
rejects: Mutex<Vec<(Query, Credentials)>>,
}
impl Helper for StaticHelper {
fn fill(&self, _q: &Query) -> Result<Option<Credentials>, HelperError> {
Ok(self.answer.clone())
}
fn approve(&self, q: &Query, c: &Credentials) -> Result<(), HelperError> {
self.approves.lock().unwrap().push((q.clone(), c.clone()));
Ok(())
}
fn reject(&self, q: &Query, c: &Credentials) -> Result<(), HelperError> {
self.rejects.lock().unwrap().push((q.clone(), c.clone()));
Ok(())
}
}
fn q() -> Query {
Query {
protocol: "https".into(),
host: "h".into(),
path: String::new(),
}
}
#[test]
fn fill_returns_first_match() {
let chain = HelperChain::new(vec![
Box::new(StaticHelper {
answer: None,
..Default::default()
}),
Box::new(StaticHelper {
answer: Some(Credentials::new("u", "p")),
..Default::default()
}),
]);
assert_eq!(chain.fill(&q()).unwrap(), Some(Credentials::new("u", "p")));
}
#[test]
fn approve_broadcasts_to_all_helpers() {
let chain = crate::CachingHelper::new();
let outer = HelperChain::new(vec![
Box::new(StaticHelper::default()),
Box::new(crate::CachingHelper::new()),
]);
let c = Credentials::new("u", "p");
outer.approve(&q(), &c).unwrap();
let _ = chain;
}
}