use std::{borrow::Cow, fmt};
use crate::tor::control_client::{commands::TorCommand, error::TorClientError, parsers, response::ResponseLine};
pub fn get_conf(query: &str) -> KeyValueCommand<'_> {
KeyValueCommand::new("GETCONF", &[query])
}
pub fn get_info(key_name: &str) -> KeyValueCommand<'_> {
KeyValueCommand::new("GETINFO", &[key_name])
}
pub fn set_events<'a>(event_types: &[&'a str]) -> KeyValueCommand<'a> {
KeyValueCommand::new("SETEVENTS", event_types)
}
pub struct KeyValueCommand<'a> {
command: &'static str,
args: Vec<&'a str>,
}
impl<'a> KeyValueCommand<'a> {
pub fn new(command: &'static str, args: &[&'a str]) -> Self {
Self {
command,
args: args.to_vec(),
}
}
}
impl<'a> TorCommand for KeyValueCommand<'a> {
type Error = TorClientError;
type Output = Vec<Cow<'a, str>>;
fn to_command_string(&self) -> Result<String, Self::Error> {
Ok(format!("{} {}", self.command, self.args.join(" ")))
}
fn parse_responses(&self, mut responses: Vec<ResponseLine>) -> Result<Self::Output, Self::Error> {
if let Some(resp) = responses.iter().find(|v| v.is_err()) {
return Err(TorClientError::TorCommandFailed(resp.value.to_string()));
}
if let Some(last_line) = responses.last() {
if last_line.value == "OK" {
let _result = responses.pop();
}
}
let responses = responses
.iter()
.map(|resp| parsers::key_value(&resp.value))
.collect::<Vec<_>>();
if let Some(Err(err)) = responses.iter().find(|v| v.is_err()) {
return Err(err.clone().into());
}
Ok(responses
.iter()
.filter_map(|r| r.as_ref().ok())
.flat_map(|(_, values)| values.iter().map(|value| Cow::from(value.clone().into_owned())))
.collect())
}
}
impl fmt::Display for KeyValueCommand<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} (Args = {})",
self.command,
self.args.iter().fold(String::new(), |acc, a| format!("{acc}, {a}"))
)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn to_command_string() {
let command = KeyValueCommand::new("GETCONF", &["HiddenServicePort"]);
assert_eq!(command.to_command_string().unwrap(), "GETCONF HiddenServicePort");
let command = KeyValueCommand::new("GETINFO", &["net/listeners/socks"]);
assert_eq!(command.to_command_string().unwrap(), "GETINFO net/listeners/socks");
}
}