use std::io::Read;
#[non_exhaustive]
pub enum StdinData {
Bytes(Vec<u8>),
Reader(Box<dyn Read + Send + 'static>),
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
AsyncReader(Box<dyn tokio::io::AsyncRead + Send + Unpin + 'static>),
}
impl StdinData {
pub fn from_reader<R: Read + Send + 'static>(reader: R) -> Self {
Self::Reader(Box::new(reader))
}
#[cfg(feature = "tokio")]
#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
pub fn from_async_reader<R: tokio::io::AsyncRead + Send + Unpin + 'static>(
reader: R,
) -> Self {
Self::AsyncReader(Box::new(reader))
}
}
impl std::fmt::Debug for StdinData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bytes(b) => f.debug_struct("Bytes").field("len", &b.len()).finish(),
Self::Reader(_) => f.debug_struct("Reader").finish_non_exhaustive(),
#[cfg(feature = "tokio")]
Self::AsyncReader(_) => f.debug_struct("AsyncReader").finish_non_exhaustive(),
}
}
}
impl From<Vec<u8>> for StdinData {
fn from(v: Vec<u8>) -> Self {
Self::Bytes(v)
}
}
impl From<&[u8]> for StdinData {
fn from(s: &[u8]) -> Self {
Self::Bytes(s.to_vec())
}
}
impl From<&Vec<u8>> for StdinData {
fn from(v: &Vec<u8>) -> Self {
Self::Bytes(v.clone())
}
}
impl From<String> for StdinData {
fn from(s: String) -> Self {
Self::Bytes(s.into_bytes())
}
}
impl From<&str> for StdinData {
fn from(s: &str) -> Self {
Self::Bytes(s.as_bytes().to_vec())
}
}
impl From<&String> for StdinData {
fn from(s: &String) -> Self {
Self::Bytes(s.as_bytes().to_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn from_vec_u8() {
let data: StdinData = vec![1, 2, 3].into();
match data {
StdinData::Bytes(b) => assert_eq!(b, vec![1, 2, 3]),
_ => panic!("expected Bytes"),
}
}
#[test]
fn from_byte_slice() {
let data: StdinData = (&b"hello"[..]).into();
match data {
StdinData::Bytes(b) => assert_eq!(b, b"hello"),
_ => panic!("expected Bytes"),
}
}
#[test]
fn from_string() {
let data: StdinData = String::from("hello").into();
match data {
StdinData::Bytes(b) => assert_eq!(b, b"hello"),
_ => panic!("expected Bytes"),
}
}
#[test]
fn from_str() {
let data: StdinData = "hello".into();
match data {
StdinData::Bytes(b) => assert_eq!(b, b"hello"),
_ => panic!("expected Bytes"),
}
}
#[test]
fn from_reader_produces_reader_variant() {
let data = StdinData::from_reader(Cursor::new(vec![1, 2, 3]));
assert!(matches!(data, StdinData::Reader(_)));
}
#[test]
fn debug_impl_redacts_reader_contents() {
let bytes_dbg = format!("{:?}", StdinData::Bytes(vec![0; 100]));
assert!(bytes_dbg.contains("100"));
let reader_dbg = format!(
"{:?}",
StdinData::from_reader(Cursor::new(vec![0u8; 100]))
);
assert!(reader_dbg.contains("Reader"));
assert!(!reader_dbg.contains("100"));
}
#[cfg(feature = "tokio")]
#[test]
fn from_async_reader_produces_async_reader_variant() {
use tokio::io::AsyncRead;
struct Empty;
impl AsyncRead for Empty {
fn poll_read(
self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
_buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
std::task::Poll::Ready(Ok(()))
}
}
let data = StdinData::from_async_reader(Empty);
assert!(matches!(data, StdinData::AsyncReader(_)));
}
}