Skip to main content

bnto_core/data/edit_fields/
mod.rs

1// edit-fields — Set fields on each item from templates over its other fields.
2//
3// A generic data node: for every incoming file it adds or overwrites the
4// fields `values` names, each value a static one or a template over the
5// fields the file arrived with (`{{item.first}} {{item.last}}`). Files pass
6// through with their data untouched; only their metadata changes. It runs as
7// a batch and fills `{{item.*}}` itself, so a field an item lacks fails the
8// run naming the field and the item. (Inside a loop the executor fills them
9// from the loop's item first, and refuses a key that item lacks.) A dry run
10// sets fields too: nothing is written.
11
12mod fill;
13mod params;
14
15use crate::metadata::{InputCardinality, NodeCategory, NodeMetadata};
16use crate::processor::{BatchFile, BatchInput, NodeInput, NodeOutput, NodeProcessor, OutputFile};
17use crate::progress::ProgressReporter;
18use crate::{BntoError, ProcessContext};
19
20use params::{EditFieldsParams, build_edit_fields_parameters};
21
22/// Edit Fields processor — sets templated fields on each item.
23pub struct EditFields;
24
25impl EditFields {
26    pub fn new() -> Self {
27        Self
28    }
29}
30
31impl Default for EditFields {
32    fn default() -> Self {
33        Self::new()
34    }
35}
36
37impl NodeProcessor for EditFields {
38    fn name(&self) -> &str {
39        "edit-fields"
40    }
41
42    fn metadata(&self) -> NodeMetadata {
43        NodeMetadata {
44            node_type: "edit-fields".to_string(),
45            name: "Edit Fields".to_string(),
46            description: "Edit fields on each item, setting them from static values or templates \
47                          over its other fields, such as {{item.first}} {{item.last}}; files pass \
48                          through unchanged."
49                .to_string(),
50            category: NodeCategory::Data,
51            accepts: vec![],
52            platforms: vec![
53                "browser".to_string(),
54                "cli".to_string(),
55                "desktop".to_string(),
56            ],
57            parameters: build_edit_fields_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        EditFieldsParams::read(params).err().into_iter().collect()
65    }
66
67    /// Required by the trait; the executor always runs this node as a batch.
68    fn process(
69        &self,
70        input: NodeInput,
71        progress: &ProgressReporter,
72        ctx: &dyn ProcessContext,
73    ) -> Result<NodeOutput, BntoError> {
74        let file = BatchFile {
75            data: input.data,
76            filename: input.filename,
77            mime_type: input.mime_type,
78            metadata: serde_json::Map::new(),
79        };
80        let batch = BatchInput {
81            files: vec![file],
82            params: input.params,
83        };
84        self.process_batch(batch, progress, ctx)
85    }
86
87    fn resolves_items(&self) -> bool {
88        true
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 = EditFieldsParams::read(&input.params).map_err(BntoError::InvalidInput)?;
98        let files = input
99            .files
100            .into_iter()
101            .map(|f| edit_file(&params, f))
102            .collect::<Result<_, _>>()?;
103        Ok(NodeOutput {
104            files,
105            metadata: serde_json::Map::new(),
106        })
107    }
108}
109
110/// One file with its fields set, its data and name as they arrived.
111fn edit_file(params: &EditFieldsParams, f: BatchFile) -> Result<OutputFile, BntoError> {
112    let set = fill::fill_values(&params.values, &f.metadata)
113        .map_err(|why| BntoError::InvalidInput(format!("{}: {why}", f.filename)))?;
114    let mut metadata = if params.keep_only_set {
115        serde_json::Map::new()
116    } else {
117        f.metadata
118    };
119    metadata.extend(set);
120    Ok(OutputFile {
121        data: f.data,
122        filename: f.filename,
123        mime_type: f
124            .mime_type
125            .unwrap_or_else(|| "application/octet-stream".to_string()),
126        metadata,
127    })
128}
129
130#[cfg(test)]
131mod tests;