summaryrefslogtreecommitdiffstats
path: root/src/tools/jsondocck
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/jsondocck')
-rw-r--r--src/tools/jsondocck/src/cache.rs70
-rw-r--r--src/tools/jsondocck/src/main.rs122
2 files changed, 90 insertions, 102 deletions
diff --git a/src/tools/jsondocck/src/cache.rs b/src/tools/jsondocck/src/cache.rs
index a188750c5..f9e542327 100644
--- a/src/tools/jsondocck/src/cache.rs
+++ b/src/tools/jsondocck/src/cache.rs
@@ -1,77 +1,31 @@
-use crate::error::CkError;
+use crate::config::Config;
use serde_json::Value;
use std::collections::HashMap;
-use std::io;
-use std::path::{Path, PathBuf};
+use std::path::Path;
use fs_err as fs;
#[derive(Debug)]
pub struct Cache {
- root: PathBuf,
- files: HashMap<PathBuf, String>,
- values: HashMap<PathBuf, Value>,
+ value: Value,
pub variables: HashMap<String, Value>,
- last_path: Option<PathBuf>,
}
impl Cache {
/// Create a new cache, used to read files only once and otherwise store their contents.
- pub fn new(doc_dir: &str) -> Cache {
+ pub fn new(config: &Config) -> Cache {
+ let root = Path::new(&config.doc_dir);
+ let filename = Path::new(&config.template).file_stem().unwrap();
+ let file_path = root.join(&Path::with_extension(Path::new(filename), "json"));
+ let content = fs::read_to_string(&file_path).expect("failed to read JSON file");
+
Cache {
- root: Path::new(doc_dir).to_owned(),
- files: HashMap::new(),
- values: HashMap::new(),
+ value: serde_json::from_str::<Value>(&content).expect("failed to convert from JSON"),
variables: HashMap::new(),
- last_path: None,
}
}
- fn resolve_path(&mut self, path: &String) -> PathBuf {
- if path != "-" {
- let resolve = self.root.join(path);
- self.last_path = Some(resolve.clone());
- resolve
- } else {
- self.last_path
- .as_ref()
- // FIXME: Point to a line number
- .expect("No last path set. Make sure to specify a full path before using `-`")
- .clone()
- }
- }
-
- fn read_file(&mut self, path: PathBuf) -> Result<String, io::Error> {
- if let Some(f) = self.files.get(&path) {
- return Ok(f.clone());
- }
-
- let file = fs::read_to_string(&path)?;
-
- self.files.insert(path, file.clone());
-
- Ok(file)
- }
-
- /// Get the text from a file. If called multiple times, the file will only be read once
- pub fn get_file(&mut self, path: &String) -> Result<String, io::Error> {
- let path = self.resolve_path(path);
- self.read_file(path)
- }
-
- /// Parse the JSON from a file. If called multiple times, the file will only be read once.
- pub fn get_value(&mut self, path: &String) -> Result<Value, CkError> {
- let path = self.resolve_path(path);
-
- if let Some(v) = self.values.get(&path) {
- return Ok(v.clone());
- }
-
- let content = self.read_file(path.clone())?;
- let val = serde_json::from_str::<Value>(&content)?;
-
- self.values.insert(path, val.clone());
-
- Ok(val)
+ pub fn value(&self) -> &Value {
+ &self.value
}
}
diff --git a/src/tools/jsondocck/src/main.rs b/src/tools/jsondocck/src/main.rs
index c44624666..76770fe36 100644
--- a/src/tools/jsondocck/src/main.rs
+++ b/src/tools/jsondocck/src/main.rs
@@ -17,7 +17,7 @@ fn main() -> Result<(), String> {
let config = parse_config(env::args().collect());
let mut failed = Vec::new();
- let mut cache = Cache::new(&config.doc_dir);
+ let mut cache = Cache::new(&config);
let commands = get_commands(&config.template)
.map_err(|_| format!("Jsondocck failed for {}", &config.template))?;
@@ -50,15 +50,17 @@ pub enum CommandKind {
Has,
Count,
Is,
+ IsMany,
Set,
}
impl CommandKind {
- fn validate(&self, args: &[String], command_num: usize, lineno: usize) -> bool {
+ fn validate(&self, args: &[String], lineno: usize) -> bool {
let count = match self {
- CommandKind::Has => (1..=3).contains(&args.len()),
- CommandKind::Count | CommandKind::Is => 3 == args.len(),
- CommandKind::Set => 4 == args.len(),
+ CommandKind::Has => (1..=2).contains(&args.len()),
+ CommandKind::IsMany => args.len() >= 2,
+ CommandKind::Count | CommandKind::Is => 2 == args.len(),
+ CommandKind::Set => 3 == args.len(),
};
if !count {
@@ -66,15 +68,10 @@ impl CommandKind {
return false;
}
- if args[0] == "-" && command_num == 0 {
- print_err(&format!("Tried to use the previous path in the first command"), lineno);
- return false;
- }
-
if let CommandKind::Count = self {
- if args[2].parse::<usize>().is_err() {
+ if args[1].parse::<usize>().is_err() {
print_err(
- &format!("Third argument to @count must be a valid usize (got `{}`)", args[2]),
+ &format!("Second argument to @count must be a valid usize (got `{}`)", args[2]),
lineno,
);
return false;
@@ -89,6 +86,7 @@ impl fmt::Display for CommandKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let text = match self {
CommandKind::Has => "has",
+ CommandKind::IsMany => "ismany",
CommandKind::Count => "count",
CommandKind::Is => "is",
CommandKind::Set => "set",
@@ -137,6 +135,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
"has" => CommandKind::Has,
"count" => CommandKind::Count,
"is" => CommandKind::Is,
+ "ismany" => CommandKind::IsMany,
"set" => CommandKind::Set,
_ => {
print_err(&format!("Unrecognized command name `@{}`", cmd), lineno);
@@ -177,7 +176,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
}
};
- if !cmd.validate(&args, commands.len(), lineno) {
+ if !cmd.validate(&args, lineno) {
errors = true;
continue;
}
@@ -195,26 +194,24 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
let result = match command.kind {
CommandKind::Has => {
match command.args.len() {
- // @has <path> = file existence
- 1 => cache.get_file(&command.args[0]).is_ok(),
- // @has <path> <jsonpath> = check path exists
- 2 => {
- let val = cache.get_value(&command.args[0])?;
- let results = select(&val, &command.args[1]).unwrap();
+ // @has <jsonpath> = check path exists
+ 1 => {
+ let val = cache.value();
+ let results = select(val, &command.args[0]).unwrap();
!results.is_empty()
}
- // @has <path> <jsonpath> <value> = check *any* item matched by path equals value
- 3 => {
- let val = cache.get_value(&command.args[0])?;
- let results = select(&val, &command.args[1]).unwrap();
- let pat = string_to_value(&command.args[2], cache);
+ // @has <jsonpath> <value> = check *any* item matched by path equals value
+ 2 => {
+ let val = cache.value().clone();
+ let results = select(&val, &command.args[0]).unwrap();
+ let pat = string_to_value(&command.args[1], cache);
let has = results.contains(&pat.as_ref());
// Give better error for when @has check fails
if !command.negated && !has {
return Err(CkError::FailedCheck(
format!(
"{} matched to {:?} but didn't have {:?}",
- &command.args[1],
+ &command.args[0],
results,
pat.as_ref()
),
@@ -227,19 +224,56 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
_ => unreachable!(),
}
}
- CommandKind::Count => {
- // @count <path> <jsonpath> <count> = Check that the jsonpath matches exactly [count] times
- assert_eq!(command.args.len(), 3);
- let expected: usize = command.args[2].parse().unwrap();
+ CommandKind::IsMany => {
+ // @ismany <path> <jsonpath> <value>...
+ let (query, values) = if let [query, values @ ..] = &command.args[..] {
+ (query, values)
+ } else {
+ unreachable!("Checked in CommandKind::validate")
+ };
+ let val = cache.value();
+ let got_values = select(val, &query).unwrap();
+ assert!(!command.negated, "`@!ismany` is not supported");
- let val = cache.get_value(&command.args[0])?;
- let results = select(&val, &command.args[1]).unwrap();
+ // Serde json doesn't implement Ord or Hash for Value, so we must
+ // use a Vec here. While in theory that makes setwize equality
+ // O(n^2), in practice n will never be large enought to matter.
+ let expected_values =
+ values.iter().map(|v| string_to_value(v, cache)).collect::<Vec<_>>();
+ if expected_values.len() != got_values.len() {
+ return Err(CkError::FailedCheck(
+ format!(
+ "Expected {} values, but `{}` matched to {} values ({:?})",
+ expected_values.len(),
+ query,
+ got_values.len(),
+ got_values
+ ),
+ command,
+ ));
+ };
+ for got_value in got_values {
+ if !expected_values.iter().any(|exp| &**exp == got_value) {
+ return Err(CkError::FailedCheck(
+ format!("`{}` has match {:?}, which was not expected", query, got_value),
+ command,
+ ));
+ }
+ }
+ true
+ }
+ CommandKind::Count => {
+ // @count <jsonpath> <count> = Check that the jsonpath matches exactly [count] times
+ assert_eq!(command.args.len(), 2);
+ let expected: usize = command.args[1].parse().unwrap();
+ let val = cache.value();
+ let results = select(val, &command.args[0]).unwrap();
let eq = results.len() == expected;
if !command.negated && !eq {
return Err(CkError::FailedCheck(
format!(
"`{}` matched to `{:?}` with length {}, but expected length {}",
- &command.args[1],
+ &command.args[0],
results,
results.len(),
expected
@@ -251,17 +285,17 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
}
}
CommandKind::Is => {
- // @has <path> <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
- assert_eq!(command.args.len(), 3);
- let val = cache.get_value(&command.args[0])?;
- let results = select(&val, &command.args[1]).unwrap();
- let pat = string_to_value(&command.args[2], cache);
+ // @has <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
+ assert_eq!(command.args.len(), 2);
+ let val = cache.value().clone();
+ let results = select(&val, &command.args[0]).unwrap();
+ let pat = string_to_value(&command.args[1], cache);
let is = results.len() == 1 && results[0] == pat.as_ref();
if !command.negated && !is {
return Err(CkError::FailedCheck(
format!(
"{} matched to {:?}, but expected {:?}",
- &command.args[1],
+ &command.args[0],
results,
pat.as_ref()
),
@@ -272,16 +306,16 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
}
}
CommandKind::Set => {
- // @set <name> = <path> <jsonpath>
- assert_eq!(command.args.len(), 4);
+ // @set <name> = <jsonpath>
+ assert_eq!(command.args.len(), 3);
assert_eq!(command.args[1], "=", "Expected an `=`");
- let val = cache.get_value(&command.args[2])?;
- let results = select(&val, &command.args[3]).unwrap();
+ let val = cache.value().clone();
+ let results = select(&val, &command.args[2]).unwrap();
assert_eq!(
results.len(),
1,
"Expected 1 match for `{}` (because of @set): matched to {:?}",
- command.args[3],
+ command.args[2],
results
);
match results.len() {
@@ -294,7 +328,7 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
_ => {
panic!(
"Got multiple results in `@set` for `{}`: {:?}",
- &command.args[3], results
+ &command.args[2], results,
);
}
}