Skip to content

Commit

Permalink
feat(snapbox): Add initial JSON support
Browse files Browse the repository at this point in the history
  • Loading branch information
Muscraft committed Aug 22, 2022
1 parent 9494040 commit e7f7556
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 3 deletions.
7 changes: 5 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions crates/snapbox/Cargo.toml
Expand Up @@ -49,6 +49,11 @@ path = ["tempfile", "walkdir", "dunce", "detect-encoding", "filetime"]
## Snapshotting of commands
cmd = ["os_pipe", "wait-timeout"]

## Snapshotting of json
json = ["structured-data"]
## Snapshotting of structured data
structured-data = ["serde_json", "indexmap", "serde"]

## Extra debugging information
debug = ["snapbox-macros/debug", "backtrace"]

Expand Down Expand Up @@ -89,3 +94,9 @@ yansi = { version = "0.5.0", optional = true }
concolor = { version = "0.0.8", optional = true }

document-features = { version = "0.2.3", optional = true }

serde = { version = "1.0", features = ["derive"], optional = true }

serde_json = { version = "1.0.85", optional = true }

indexmap = { version = "1.9.1", optional = true, features = ["serde-1"] }
103 changes: 102 additions & 1 deletion crates/snapbox/src/data.rs
@@ -1,3 +1,5 @@
#[cfg(feature = "structured-data")]
use serde::{Deserialize, Serialize};
/// Test fixture, actual output, or expected result
///
/// This provides conveniences for tracking the intended format (binary vs text).
Expand All @@ -10,12 +12,16 @@ pub struct Data {
enum DataInner {
Binary(Vec<u8>),
Text(String),
#[cfg(feature = "structured-data")]
Json(Value),
}

#[derive(Clone, Debug, PartialEq, Eq, Copy, Hash)]
pub enum DataFormat {
Binary,
Text,
#[cfg(feature = "json")]
Json,
}

impl Default for DataFormat {
Expand All @@ -24,6 +30,47 @@ impl Default for DataFormat {
}
}

#[cfg(feature = "structured-data")]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Value {
Null,
Bool(bool),
Number(serde_json::Number),
String(String),
Array(Vec<Value>),
// IndexMap is used as it keeps insertion order
Object(indexmap::IndexMap<String, Value>),
}

#[cfg(feature = "structured-data")]
impl Value {
fn normalize_paths(self) -> Self {
match self {
Value::Null => Value::Null,
Value::Bool(b) => Value::Bool(b),
Value::Number(num) => Value::Number(num),
Value::String(str) => {
let str = crate::utils::normalize_paths(&str);
Value::String(str)
}
Value::Array(arr) => {
let arr = arr
.into_iter()
.map(|value| value.normalize_paths())
.collect();
Value::Array(arr)
}
Value::Object(obj) => {
let obj = indexmap::IndexMap::from_iter(
obj.into_iter()
.map(|(str, value)| (str, value.normalize_paths())),
);
Value::Object(obj)
}
}
}
}

impl Data {
/// Mark the data as binary (no post-processing)
pub fn binary(raw: impl Into<Vec<u8>>) -> Self {
Expand All @@ -39,6 +86,13 @@ impl Data {
}
}

#[cfg(feature = "json")]
pub fn json(raw: impl Into<Value>) -> Self {
Self {
inner: DataInner::Json(raw.into()),
}
}

/// Empty test data
pub fn new() -> Self {
Self::text("")
Expand All @@ -61,11 +115,25 @@ impl Data {
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::text(data)
}
#[cfg(feature = "json")]
DataFormat::Json => {
let data = std::fs::read_to_string(&path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::json(serde_json::from_str::<Value>(&data).unwrap())
}
},
None => {
let data = std::fs::read(&path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
Self::binary(data).try_coerce(DataFormat::Text)
let data = Self::binary(data);
match path.extension() {
Some(ext) => match ext.to_str() {
#[cfg(feature = "json")]
Some("json") => data.try_coerce(DataFormat::Json),
_ => data.try_coerce(DataFormat::Text),
},
None => data.try_coerce(DataFormat::Text),
}
}
};
Ok(data)
Expand Down Expand Up @@ -107,6 +175,8 @@ impl Data {
Ok(data)
}
DataInner::Text(data) => Ok(data),
#[cfg(feature = "json")]
DataInner::Json(value) => Ok(serde_json::to_string_pretty(&value).unwrap()),
}
}

Expand All @@ -117,13 +187,17 @@ impl Data {
match &self.inner {
DataInner::Binary(_) => None,
DataInner::Text(data) => Some(data.to_owned()),
#[cfg(feature = "json")]
DataInner::Json(value) => Some(serde_json::to_string_pretty(value).unwrap()),
}
}

pub fn to_bytes(&self) -> Vec<u8> {
match &self.inner {
DataInner::Binary(data) => data.clone(),
DataInner::Text(data) => data.clone().into_bytes(),
#[cfg(feature = "json")]
DataInner::Json(value) => serde_json::to_vec(value).unwrap(),
}
}

Expand All @@ -145,6 +219,23 @@ impl Data {
}
}
DataInner::Text(data) => Self::text(data),
#[cfg(feature = "json")]
DataInner::Json(data) => match serde_json::to_string_pretty(&data) {
Ok(text) => Self::text(text),
Err(_) => Self::json(data),
},
},
#[cfg(feature = "json")]
DataFormat::Json => match self.inner {
DataInner::Binary(bin) => match serde_json::from_slice::<Value>(&bin) {
Ok(json) => Self::json(json),
Err(_) => Self::binary(bin),
},
DataInner::Text(text) => match serde_json::from_str::<Value>(&text) {
Ok(json) => Self::json(json),
Err(_) => Self::text(text),
},
DataInner::Json(json) => Self::json(json),
},
}
}
Expand All @@ -154,6 +245,8 @@ impl Data {
match &self.inner {
DataInner::Binary(_) => DataFormat::Binary,
DataInner::Text(_) => DataFormat::Text,
#[cfg(feature = "json")]
DataInner::Json(_) => DataFormat::Json,
}
}
}
Expand All @@ -163,6 +256,8 @@ impl std::fmt::Display for Data {
match &self.inner {
DataInner::Binary(data) => String::from_utf8_lossy(data).fmt(f),
DataInner::Text(data) => data.fmt(f),
#[cfg(feature = "json")]
DataInner::Json(data) => std::fmt::Debug::fmt(data, f),
}
}
}
Expand Down Expand Up @@ -222,6 +317,8 @@ impl Normalize for NormalizeNewlines {
let lines = crate::utils::normalize_lines(&text);
Data::text(lines)
}
#[cfg(feature = "json")]
DataInner::Json(json) => Data::json(json),
}
}
}
Expand All @@ -235,6 +332,8 @@ impl Normalize for NormalizePaths {
let lines = crate::utils::normalize_paths(&text);
Data::text(lines)
}
#[cfg(feature = "json")]
DataInner::Json(value) => Data::json(value.normalize_paths()),
}
}
}
Expand Down Expand Up @@ -263,6 +362,8 @@ impl Normalize for NormalizeMatches<'_> {
.normalize(&text, &self.pattern.render().unwrap());
Data::text(lines)
}
#[cfg(feature = "json")]
DataInner::Json(_) => todo!("unsure of how to do matches here"),
}
}
}
Expand Down

0 comments on commit e7f7556

Please sign in to comment.