use std::num::NonZeroU32;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use super::tracing_init::{self, ReloadHandle};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OptionEffect {
None,
SetDepth(NonZeroU32),
}
pub(crate) async fn handle_option<W>(
args: &str,
reload: Option<&ReloadHandle>,
writer: &mut W,
) -> std::io::Result<OptionEffect>
where
W: AsyncWrite + Unpin,
{
let (response, effect): (&[u8], OptionEffect) = match parse_option(args) {
Some(OptionRequest::Verbosity(n)) if n >= 2 => {
if let Some(handle) = reload {
let _ = tracing_init::raise_to_info(handle);
}
(b"ok\n", OptionEffect::None)
}
Some(OptionRequest::Depth(n)) => (b"ok\n", OptionEffect::SetDepth(n)),
_ => (b"unsupported\n", OptionEffect::None),
};
writer.write_all(response).await?;
writer.flush().await?;
Ok(effect)
}
#[derive(Debug, PartialEq, Eq)]
enum OptionRequest {
Verbosity(i32),
Depth(NonZeroU32),
}
fn parse_option(args: &str) -> Option<OptionRequest> {
let mut parts = args.split_whitespace();
let key = parts.next()?;
let value = parts.next()?;
if parts.next().is_some() {
return None;
}
match key {
"verbosity" => value.parse::<i32>().ok().map(OptionRequest::Verbosity),
"depth" => value.parse::<NonZeroU32>().ok().map(OptionRequest::Depth),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_recognises_verbosity() {
assert_eq!(
parse_option("verbosity 2"),
Some(OptionRequest::Verbosity(2))
);
assert_eq!(
parse_option("verbosity 0"),
Some(OptionRequest::Verbosity(0))
);
assert_eq!(
parse_option("verbosity -1"),
Some(OptionRequest::Verbosity(-1))
);
}
#[test]
fn parse_recognises_depth() {
assert_eq!(
parse_option("depth 1"),
Some(OptionRequest::Depth(NonZeroU32::new(1).unwrap()))
);
assert_eq!(
parse_option("depth 42"),
Some(OptionRequest::Depth(NonZeroU32::new(42).unwrap()))
);
}
#[test]
fn parse_rejects_depth_zero_and_negative() {
assert_eq!(parse_option("depth 0"), None);
assert_eq!(parse_option("depth -1"), None);
assert_eq!(parse_option("depth foo"), None);
}
#[test]
fn parse_rejects_unknown_keys() {
assert_eq!(parse_option("progress true"), None);
assert_eq!(parse_option("dry-run true"), None);
}
#[test]
fn parse_rejects_malformed_lines() {
assert_eq!(parse_option(""), None);
assert_eq!(parse_option("verbosity"), None);
assert_eq!(parse_option("verbosity foo"), None);
assert_eq!(parse_option("verbosity 2 extra"), None);
assert_eq!(parse_option("depth"), None);
assert_eq!(parse_option("depth 1 extra"), None);
}
#[tokio::test]
async fn responds_ok_for_verbosity_two() {
let mut buf: Vec<u8> = Vec::new();
let effect = handle_option("verbosity 2", None, &mut buf).await.unwrap();
assert_eq!(&buf, b"ok\n");
assert_eq!(effect, OptionEffect::None);
}
#[tokio::test]
async fn responds_unsupported_for_low_verbosity() {
let mut buf: Vec<u8> = Vec::new();
let effect = handle_option("verbosity 1", None, &mut buf).await.unwrap();
assert_eq!(&buf, b"unsupported\n");
assert_eq!(effect, OptionEffect::None);
}
#[tokio::test]
async fn responds_unsupported_for_unknown_option() {
let mut buf: Vec<u8> = Vec::new();
let effect = handle_option("progress true", None, &mut buf)
.await
.unwrap();
assert_eq!(&buf, b"unsupported\n");
assert_eq!(effect, OptionEffect::None);
}
#[tokio::test]
async fn responds_unsupported_for_malformed() {
let mut buf: Vec<u8> = Vec::new();
let effect = handle_option("verbosity foo", None, &mut buf)
.await
.unwrap();
assert_eq!(&buf, b"unsupported\n");
assert_eq!(effect, OptionEffect::None);
}
#[tokio::test]
async fn responds_ok_and_returns_depth_for_valid_depth() {
let mut buf: Vec<u8> = Vec::new();
let effect = handle_option("depth 5", None, &mut buf).await.unwrap();
assert_eq!(&buf, b"ok\n");
assert_eq!(effect, OptionEffect::SetDepth(NonZeroU32::new(5).unwrap()));
}
#[tokio::test]
async fn responds_unsupported_for_depth_zero() {
let mut buf: Vec<u8> = Vec::new();
let effect = handle_option("depth 0", None, &mut buf).await.unwrap();
assert_eq!(&buf, b"unsupported\n");
assert_eq!(effect, OptionEffect::None);
}
}