use serde_json::{Map, Value, json};
use crate::pipeline::nodes::shared::util::with_answer;
use crate::pipeline::NodeDefinition;
pub const SOURCE_KEYS: [&str; 9] =
["webhook", "manual", "schedule", "function", "mcp", "error", "room", "socket", "topic"];
pub fn answer_under(source: &str, payload: Value) -> Value {
let mut private = Map::new();
let envelope = match payload {
Value::Object(map) => {
let mut envelope = Map::new();
for (key, value) in map {
if key.starts_with("__zf_") {
private.insert(key, value);
} else {
envelope.insert(key, value);
}
}
Value::Object(envelope)
}
Value::Null => json!({}),
other => other,
};
let mut answer = Map::new();
answer.insert(source.to_string(), envelope);
with_answer(&Value::Object(private), Value::Object(answer))
}
pub fn envelope(metadata: &Value) -> &Value {
metadata.get("trigger").unwrap_or(&Value::Null)
}
pub mod function;
pub mod kv_subscribe;
pub mod manual;
pub mod mcp_trigger;
pub mod schedule;
pub mod weberror;
pub mod webhook;
pub mod ws_client;
pub fn definitions() -> Vec<NodeDefinition> {
vec![
function::definition(),
kv_subscribe::definition(),
webhook::definition(),
schedule::definition(),
manual::definition(),
mcp_trigger::definition(),
ws_client::definition(),
weberror::definition(),
]
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::answer_under;
#[test]
fn the_renderer_knows_every_trigger_envelope() {
assert_eq!(super::SOURCE_KEYS, crate::rwe::core::page_state::TRIGGER_SOURCES);
}
#[test]
fn the_envelope_goes_under_the_source_and_private_keys_stay_at_the_root() {
let out = answer_under(
"webhook",
json!({ "body": { "a": 1 }, "query": {}, "__zf_private_trace_redact": ["x"] }),
);
assert_eq!(
out,
json!({ "webhook": { "body": { "a": 1 }, "query": {} }, "__zf_private_trace_redact": ["x"] })
);
assert_eq!(answer_under("manual", json!(null)), json!({ "manual": {} }));
}
}
#[cfg(test)]
mod answer_tests {
use serde_json::json;
use crate::pipeline::nodes::{NodeExecutionInput, NodeHandler};
#[tokio::test]
async fn each_trigger_answers_under_its_source() {
let envelope = json!({ "body": { "a": 1 }, "message": "m" });
let nodes: Vec<(Box<dyn NodeHandler>, &str)> = vec![
(Box::new(super::webhook::Node::new(Default::default())), "webhook"),
(Box::new(super::manual::Node::new(Default::default())), "manual"),
(Box::new(super::schedule::Node::new(Default::default())), "schedule"),
(Box::new(super::function::Node::new(Default::default())), "function"),
(Box::new(super::mcp_trigger::Node::new(Default::default())), "mcp"),
(Box::new(super::weberror::Node::new(Default::default())), "error"),
(Box::new(super::ws_client::Node::new(Default::default())), "socket"),
(Box::new(super::kv_subscribe::Node::new(Default::default())), "topic"),
(Box::new(crate::pipeline::nodes::basic::ws::trigger::Node::new(Default::default())), "room"),
];
for (node, key) in nodes {
let out = node
.execute_async(NodeExecutionInput {
node_id: "t".into(),
input_pin: "in".into(),
payload: envelope.clone(),
metadata: json!({}),
bus: None,
})
.await
.unwrap();
assert_eq!(out.payload, json!({ key: envelope.clone() }), "{}", node.kind());
assert_eq!(crate::pipeline::nodes::answer_key(node.kind()).as_deref(), Some(key));
assert!(super::SOURCE_KEYS.contains(&key));
}
}
}