mod params;
use crate::compare;
use crate::metadata::{InputCardinality, NodeCategory, NodeMetadata};
use crate::processor::{BatchFile, BatchInput, NodeInput, NodeOutput, NodeProcessor};
use crate::progress::ProgressReporter;
use crate::{BntoError, ProcessContext};
use super::pass_through::pass_through;
use super::placeholder;
use params::{AssertParams, build_assert_parameters};
pub struct Assert;
impl Assert {
pub fn new() -> Self {
Self
}
}
impl Default for Assert {
fn default() -> Self {
Self::new()
}
}
impl NodeProcessor for Assert {
fn name(&self) -> &str {
"assert"
}
fn metadata(&self) -> NodeMetadata {
NodeMetadata {
node_type: "assert".to_string(),
name: "Assert".to_string(),
description: "Assert a comparison on each item (equals, not equals, after, before); \
fail the run when it doesn't hold, otherwise pass files through \
unchanged."
.to_string(),
category: NodeCategory::Control,
accepts: vec![],
platforms: vec![
"browser".to_string(),
"cli".to_string(),
"desktop".to_string(),
],
parameters: build_assert_parameters(),
input_cardinality: InputCardinality::Batch,
requires: vec![],
}
}
fn validate(&self, params: &serde_json::Map<String, serde_json::Value>) -> Vec<String> {
AssertParams::read(params).err().into_iter().collect()
}
fn resolves_items(&self) -> bool {
true
}
fn process(
&self,
input: NodeInput,
progress: &ProgressReporter,
ctx: &dyn ProcessContext,
) -> Result<NodeOutput, BntoError> {
let file = BatchFile {
data: input.data,
filename: input.filename,
mime_type: input.mime_type,
metadata: serde_json::Map::new(),
};
let batch = BatchInput {
files: vec![file],
params: input.params,
};
self.process_batch(batch, progress, ctx)
}
fn process_batch(
&self,
input: BatchInput,
progress: &ProgressReporter,
ctx: &dyn ProcessContext,
) -> Result<NodeOutput, BntoError> {
let params = AssertParams::read(&input.params).map_err(BntoError::InvalidInput)?;
if ctx.dry_run() {
progress.report_output(&format!("Would check: {}", params.describe()));
return Ok(pass_through(input.files));
}
let reasons = failures(¶ms, &input.files);
if !reasons.is_empty() {
let lead = params.message.as_deref().unwrap_or("Assertion failed");
return Err(BntoError::ProcessingFailed(format!(
"{lead}: {}",
reasons.join("; ")
)));
}
Ok(pass_through(input.files))
}
}
fn failures(params: &AssertParams, files: &[BatchFile]) -> Vec<String> {
if !(placeholder::reads_file(¶ms.value) || placeholder::reads_file(¶ms.expected)) {
return compare::compare(¶ms.value, params.op, ¶ms.expected)
.err()
.into_iter()
.collect();
}
files
.iter()
.filter_map(|f| file_failure(params, f))
.collect()
}
fn file_failure(params: &AssertParams, file: &BatchFile) -> Option<String> {
let resolve = |text: &str| {
placeholder::resolve_file(text, &file.filename, &file.metadata)
.map_err(|key| format!("has no item \"{key}\""))
};
let checked = resolve(¶ms.value).and_then(|value| {
let expected = resolve(¶ms.expected)?;
compare::compare(&value, params.op, &expected)
});
checked.err().map(|why| format!("{}: {why}", file.filename))
}
#[cfg(test)]
mod tests;