bnto_core/control/assert/
mod.rs1mod params;
11
12use crate::compare;
13use crate::metadata::{InputCardinality, NodeCategory, NodeMetadata};
14use crate::processor::{BatchFile, BatchInput, NodeInput, NodeOutput, NodeProcessor};
15use crate::progress::ProgressReporter;
16use crate::{BntoError, ProcessContext};
17
18use super::pass_through::pass_through;
19use super::placeholder;
20use params::{AssertParams, build_assert_parameters};
21
22pub struct Assert;
24
25impl Assert {
26 pub fn new() -> Self {
27 Self
28 }
29}
30
31impl Default for Assert {
32 fn default() -> Self {
33 Self::new()
34 }
35}
36
37impl NodeProcessor for Assert {
38 fn name(&self) -> &str {
39 "assert"
40 }
41
42 fn metadata(&self) -> NodeMetadata {
43 NodeMetadata {
44 node_type: "assert".to_string(),
45 name: "Assert".to_string(),
46 description: "Assert a comparison on each item (equals, not equals, after, before); \
47 fail the run when it doesn't hold, otherwise pass files through \
48 unchanged."
49 .to_string(),
50 category: NodeCategory::Control,
51 accepts: vec![],
52 platforms: vec![
53 "browser".to_string(),
54 "cli".to_string(),
55 "desktop".to_string(),
56 ],
57 parameters: build_assert_parameters(),
58 input_cardinality: InputCardinality::Batch,
59 requires: vec![],
60 }
61 }
62
63 fn validate(&self, params: &serde_json::Map<String, serde_json::Value>) -> Vec<String> {
64 AssertParams::read(params).err().into_iter().collect()
65 }
66
67 fn resolves_items(&self) -> bool {
68 true
69 }
70
71 fn process(
73 &self,
74 input: NodeInput,
75 progress: &ProgressReporter,
76 ctx: &dyn ProcessContext,
77 ) -> Result<NodeOutput, BntoError> {
78 let file = BatchFile {
79 data: input.data,
80 filename: input.filename,
81 mime_type: input.mime_type,
82 metadata: serde_json::Map::new(),
83 };
84 let batch = BatchInput {
85 files: vec![file],
86 params: input.params,
87 };
88 self.process_batch(batch, progress, ctx)
89 }
90
91 fn process_batch(
92 &self,
93 input: BatchInput,
94 progress: &ProgressReporter,
95 ctx: &dyn ProcessContext,
96 ) -> Result<NodeOutput, BntoError> {
97 let params = AssertParams::read(&input.params).map_err(BntoError::InvalidInput)?;
98 if ctx.dry_run() {
99 progress.report_output(&format!("Would check: {}", params.describe()));
100 return Ok(pass_through(input.files));
101 }
102 let reasons = failures(¶ms, &input.files);
103 if !reasons.is_empty() {
104 let lead = params.message.as_deref().unwrap_or("Assertion failed");
105 return Err(BntoError::ProcessingFailed(format!(
106 "{lead}: {}",
107 reasons.join("; ")
108 )));
109 }
110 Ok(pass_through(input.files))
111 }
112}
113
114fn failures(params: &AssertParams, files: &[BatchFile]) -> Vec<String> {
117 if !(placeholder::reads_file(¶ms.value) || placeholder::reads_file(¶ms.expected)) {
118 return compare::compare(¶ms.value, params.op, ¶ms.expected)
119 .err()
120 .into_iter()
121 .collect();
122 }
123 files
124 .iter()
125 .filter_map(|f| file_failure(params, f))
126 .collect()
127}
128
129fn file_failure(params: &AssertParams, file: &BatchFile) -> Option<String> {
131 let resolve = |text: &str| {
132 placeholder::resolve_file(text, &file.filename, &file.metadata)
133 .map_err(|key| format!("has no item \"{key}\""))
134 };
135 let checked = resolve(¶ms.value).and_then(|value| {
136 let expected = resolve(¶ms.expected)?;
137 compare::compare(&value, params.op, &expected)
138 });
139 checked.err().map(|why| format!("{}: {why}", file.filename))
140}
141
142#[cfg(test)]
143mod tests;