diff --git a/crates/snapbox/src/assert.rs b/crates/snapbox/src/assert.rs index bf60cd70..152ba22e 100644 --- a/crates/snapbox/src/assert.rs +++ b/crates/snapbox/src/assert.rs @@ -7,6 +7,7 @@ use std::io::stderr; use crate::filters::{Filter as _, FilterNewlines, FilterPaths, FilterRedactions}; use crate::Action; +use crate::IntoData; /// Snapshot assertion against a file's contents /// @@ -61,9 +62,9 @@ impl Assert { /// Assert::new().eq(file!["output.txt"], actual); /// ``` #[track_caller] - pub fn eq(&self, expected: impl Into, actual: impl Into) { - let expected = expected.into(); - let actual = actual.into(); + pub fn eq(&self, expected: impl IntoData, actual: impl IntoData) { + let expected = expected.into_data(); + let actual = actual.into_data(); if let Err(err) = self.try_eq(expected, actual, Some(&"In-memory")) { err.panic(); } diff --git a/crates/snapbox/src/cmd.rs b/crates/snapbox/src/cmd.rs index 1df82fbb..7dd26ed6 100644 --- a/crates/snapbox/src/cmd.rs +++ b/crates/snapbox/src/cmd.rs @@ -3,6 +3,8 @@ #[cfg(feature = "color")] use anstream::panic; +use crate::IntoData; + /// Process spawning for testing of non-interactive commands #[derive(Debug)] pub struct Command { @@ -243,8 +245,8 @@ impl Command { /// .assert() /// .stdout_eq("42"); /// ``` - pub fn stdin(mut self, stream: impl Into) -> Self { - self.stdin = Some(stream.into()); + pub fn stdin(mut self, stream: impl IntoData) -> Self { + self.stdin = Some(stream.into_data()); self } @@ -621,14 +623,14 @@ impl OutputAssert { /// .stdout_eq(file!["stdout.log"]); /// ``` #[track_caller] - pub fn stdout_eq(self, expected: impl Into) -> Self { - let expected = expected.into(); + pub fn stdout_eq(self, expected: impl IntoData) -> Self { + let expected = expected.into_data(); self.stdout_eq_inner(expected) } #[track_caller] fn stdout_eq_inner(self, expected: crate::Data) -> Self { - let actual = crate::Data::from(self.output.stdout.as_slice()); + let actual = self.output.stdout.as_slice().into_data(); if let Err(err) = self.config.try_eq(expected, actual, Some(&"stdout")) { err.panic(); } @@ -671,14 +673,14 @@ impl OutputAssert { /// .stderr_eq(file!["stderr.log"]); /// ``` #[track_caller] - pub fn stderr_eq(self, expected: impl Into) -> Self { - let expected = expected.into(); + pub fn stderr_eq(self, expected: impl IntoData) -> Self { + let expected = expected.into_data(); self.stderr_eq_inner(expected) } #[track_caller] fn stderr_eq_inner(self, expected: crate::Data) -> Self { - let actual = crate::Data::from(self.output.stderr.as_slice()); + let actual = self.output.stderr.as_slice().into_data(); if let Err(err) = self.config.try_eq(expected, actual, Some(&"stderr")) { err.panic(); } diff --git a/crates/snapbox/src/data/mod.rs b/crates/snapbox/src/data/mod.rs index 6120acea..c41f1e4a 100644 --- a/crates/snapbox/src/data/mod.rs +++ b/crates/snapbox/src/data/mod.rs @@ -42,6 +42,84 @@ impl ToDebug for D { } } +/// Convert to [`Data`] with modifiers for `expected` data +pub trait IntoData: Sized { + /// Remove default [`filters`][crate::filters] from this `expected` result + fn raw(self) -> Data { + self.into_data().raw() + } + + /// Initialize as [`format`][DataFormat] or [`Error`][DataFormat::Error] + /// + /// This is generally used for `expected` data + /// + /// ```rust + /// # #[cfg(feature = "json")] { + /// use snapbox::prelude::*; + /// use snapbox::str; + /// + /// let expected = str![[r#"{"hello": "world"}"#]] + /// .is(snapbox::data::DataFormat::Json); + /// assert_eq!(expected.format(), snapbox::data::DataFormat::Json); + /// # } + /// ``` + fn is(self, format: DataFormat) -> Data { + self.into_data().is(format) + } + + /// Convert to [`Data`], applying defaults + fn into_data(self) -> Data; +} + +impl IntoData for Data { + fn into_data(self) -> Data { + self + } +} + +impl IntoData for &'_ Data { + fn into_data(self) -> Data { + self.clone() + } +} + +impl IntoData for Vec { + fn into_data(self) -> Data { + Data::binary(self) + } +} + +impl IntoData for &'_ [u8] { + fn into_data(self) -> Data { + self.to_owned().into_data() + } +} + +impl IntoData for String { + fn into_data(self) -> Data { + Data::text(self) + } +} + +impl IntoData for &'_ String { + fn into_data(self) -> Data { + self.to_owned().into_data() + } +} + +impl IntoData for &'_ str { + fn into_data(self) -> Data { + self.to_owned().into_data() + } +} + +impl IntoData for Inline { + fn into_data(self) -> Data { + let trimmed = self.trimmed(); + super::Data::text(trimmed).with_source(self) + } +} + /// Declare an expected value for an assert from a file /// /// This is relative to the source file the macro is run from @@ -80,6 +158,8 @@ macro_rules! file { /// Declare an expected value from within Rust source /// +/// Output type: [`Inline`], see [`IntoData`] for operations +/// /// ``` /// # use snapbox::str; /// str![[" @@ -87,10 +167,6 @@ macro_rules! file { /// "]]; /// str![r#"{"Foo": 92}"#]; /// ``` -/// -/// Leading indentation is stripped. -/// -/// See [`Inline::is`] for declaring the data to be of a certain format. #[macro_export] macro_rules! str { [$data:literal] => { $crate::str![[$data]] }; @@ -140,6 +216,7 @@ pub(crate) enum DataInner { /// - [`str!`] for inline snapshots /// - [`file!`] for external snapshots /// - [`ToDebug`] for verifying a debug representation +/// - [`IntoData`] for modifying `expected` impl Data { /// Mark the data as binary (no post-processing) pub fn binary(raw: impl Into>) -> Self { @@ -594,42 +671,6 @@ impl Default for Data { } } -impl<'d> From<&'d Data> for Data { - fn from(other: &'d Data) -> Self { - other.clone() - } -} - -impl From> for Data { - fn from(other: Vec) -> Self { - Self::binary(other) - } -} - -impl<'b> From<&'b [u8]> for Data { - fn from(other: &'b [u8]) -> Self { - other.to_owned().into() - } -} - -impl From for Data { - fn from(other: String) -> Self { - Self::text(other) - } -} - -impl<'s> From<&'s String> for Data { - fn from(other: &'s String) -> Self { - other.clone().into() - } -} - -impl<'s> From<&'s str> for Data { - fn from(other: &'s str) -> Self { - other.to_owned().into() - } -} - #[cfg(feature = "detect-encoding")] fn is_binary(data: &[u8]) -> bool { match content_inspector::inspect(data) { diff --git a/crates/snapbox/src/data/runtime.rs b/crates/snapbox/src/data/runtime.rs index a0208da4..132f7345 100644 --- a/crates/snapbox/src/data/runtime.rs +++ b/crates/snapbox/src/data/runtime.rs @@ -349,8 +349,8 @@ impl PathRuntime { mod tests { use super::*; use crate::assert_eq; + use crate::prelude::*; use crate::str; - use crate::ToDebug as _; #[test] fn test_format_patch() { diff --git a/crates/snapbox/src/data/source.rs b/crates/snapbox/src/data/source.rs index 26ad94c7..10e9b9d9 100644 --- a/crates/snapbox/src/data/source.rs +++ b/crates/snapbox/src/data/source.rs @@ -79,32 +79,7 @@ pub struct Inline { } impl Inline { - /// Initialize `Self` as [`format`][crate::data::DataFormat] or [`Error`][crate::data::DataFormat::Error] - /// - /// This is generally used for `expected` data - /// - /// ```rust - /// # #[cfg(feature = "json")] { - /// use snapbox::str; - /// - /// let expected = str![[r#"{"hello": "world"}"#]] - /// .is(snapbox::data::DataFormat::Json); - /// assert_eq!(expected.format(), snapbox::data::DataFormat::Json); - /// # } - /// ``` - pub fn is(self, format: super::DataFormat) -> super::Data { - let data: super::Data = self.into(); - data.is(format) - } - - /// Remove default [`filters`][crate::filters] from this `expected` result - pub fn raw(self) -> super::Data { - let mut data: super::Data = self.into(); - data.filters = super::FilterSet::empty().newlines(); - data - } - - fn trimmed(&self) -> String { + pub(crate) fn trimmed(&self) -> String { let mut data = self.data; if data.contains('\n') { data = data.strip_prefix('\n').unwrap_or(data); @@ -120,13 +95,6 @@ impl std::fmt::Display for Inline { } } -impl From for super::Data { - fn from(inline: Inline) -> Self { - let trimmed = inline.trimmed(); - super::Data::text(trimmed).with_source(inline) - } -} - /// Position within Rust source code, see [`Inline`] #[doc(hidden)] #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/crates/snapbox/src/lib.rs b/crates/snapbox/src/lib.rs index d9248896..ef6f6ce8 100644 --- a/crates/snapbox/src/lib.rs +++ b/crates/snapbox/src/lib.rs @@ -112,6 +112,7 @@ pub use action::Action; pub use action::DEFAULT_ACTION_ENV; pub use assert::Assert; pub use data::Data; +pub use data::IntoData; pub use data::ToDebug; pub use error::Error; pub use filters::Redactions; @@ -119,6 +120,12 @@ pub use snapbox_macros::debug; pub type Result = std::result::Result; +/// Easier access to common traits +pub mod prelude { + pub use crate::IntoData; + pub use crate::ToDebug; +} + /// Check if a value is the same as an expected value /// /// By default [`filters`] are applied, including: @@ -145,7 +152,7 @@ pub type Result = std::result::Result; /// assert_eq(file!["output.txt"], actual); /// ``` #[track_caller] -pub fn assert_eq(expected: impl Into, actual: impl Into) { +pub fn assert_eq(expected: impl IntoData, actual: impl IntoData) { Assert::new() .action_env(DEFAULT_ACTION_ENV) .eq(expected, actual); diff --git a/src/runner.rs b/src/runner.rs index 1adcb237..5449bbbb 100644 --- a/src/runner.rs +++ b/src/runner.rs @@ -15,6 +15,7 @@ use rayon::prelude::*; use snapbox::data::DataFormat; use snapbox::filters::{Filter as _, FilterNewlines, FilterPaths, FilterRedactions}; use snapbox::path::FileType; +use snapbox::IntoData; #[derive(Debug)] pub(crate) struct Runner { @@ -580,12 +581,12 @@ impl Output { self.spawn.status = SpawnStatus::Ok; self.stdout = Some(Stream { stream: Stdio::Stdout, - content: output.stdout.into(), + content: output.stdout.into_data(), status: StreamStatus::Ok, }); self.stderr = Some(Stream { stream: Stdio::Stderr, - content: output.stderr.into(), + content: output.stderr.into_data(), status: StreamStatus::Ok, }); self