pub mod channel_call;
pub mod connector_helpers;
pub mod http_call;
pub mod http_common;
pub mod publish_kafka;
pub mod schema;
pub mod secret_ref;
pub mod stub;
pub mod cache_read;
pub mod cache_write;
pub mod crypto;
pub mod data_query;
pub mod data_write;
pub mod db_read;
pub mod db_write;
pub mod jwt_sign;
pub mod jwt_verify;
pub mod mongo_aggregate;
pub mod mongo_common;
pub mod mongo_read;
pub mod mongo_write;
pub mod send_email;
pub mod storage_head;
pub mod storage_presign;
use dataflow_rs::engine::message::Message;
#[cfg(test)]
use serde_json::Value;
pub fn to_reqwest_method(method: &dataflow_rs::HttpMethod) -> reqwest::Method {
reqwest::Method::from_bytes(method.as_str().as_bytes()).unwrap_or(reqwest::Method::GET)
}
pub fn extract_channel(message: &Message) -> &str {
message
.metadata()
.get("channel")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
}
pub use dataflow_rs::{RetryPolicy, retry_with_attempts, retry_with_policy};
#[cfg(test)]
pub(crate) async fn run_test_tasks(
fns: std::collections::HashMap<String, dataflow_rs::BoxedFunctionHandler>,
tasks: Value,
data: Value,
) -> Result<Value, String> {
let workflow: dataflow_rs::Workflow = serde_json::from_value(serde_json::json!({
"id": "w", "name": "w", "condition": true, "tasks": tasks
}))
.map_err(|e| e.to_string())?;
let engine = dataflow_rs::Engine::new(vec![workflow], fns).map_err(|e| e.to_string())?;
let mut message = dataflow_rs::Message::from_value(&serde_json::json!({}));
if !data.is_null() {
dataflow_rs::engine::utils::set_nested_value(
&mut message.context,
"data",
dataflow_rs::datavalue::OwnedDataValue::from(&data),
);
}
engine
.process_message(&mut message)
.await
.map_err(|e| e.to_string())?;
if let Some(err) = message.errors().first() {
return Err(format!("{err:?}"));
}
Ok(message.data().into())
}
#[cfg(test)]
pub(crate) async fn run_test_task(
name: &str,
handler: dataflow_rs::BoxedFunctionHandler,
input: Value,
data: Value,
) -> Result<Value, String> {
let mut fns: std::collections::HashMap<String, dataflow_rs::BoxedFunctionHandler> =
Default::default();
fns.insert(name.to_string(), handler);
run_test_tasks(
fns,
serde_json::json!([{"id": "t", "name": "t", "function": {"name": name, "input": input}}]),
data,
)
.await
}
#[cfg(test)]
mod tests {
use super::*;
use dataflow_rs::engine::error::DataflowError;
use std::sync::Arc;
use std::time::Duration;
#[test]
fn every_http_method_maps_to_the_same_reqwest_verb() {
for method in dataflow_rs::HttpMethod::ALL {
assert_eq!(
to_reqwest_method(method).as_str(),
method.as_str(),
"{method} did not round-trip through reqwest::Method"
);
}
}
#[test]
fn test_extract_channel_with_channel() {
let mut message = Message::from_value(&serde_json::json!({"key": "val"}));
dataflow_rs::engine::utils::set_nested_value(
&mut message.context,
"metadata.channel",
dataflow_rs::datavalue::OwnedDataValue::from("orders".to_string()),
);
assert_eq!(extract_channel(&message), "orders");
}
#[test]
fn test_extract_channel_without_channel() {
let message = Message::from_value(&serde_json::json!({}));
assert_eq!(extract_channel(&message), "unknown");
}
#[tokio::test]
async fn test_retry_policy_honours_deadline() {
use std::sync::atomic::{AtomicUsize, Ordering};
let attempts = Arc::new(AtomicUsize::new(0));
let seen = attempts.clone();
let start = std::time::Instant::now();
let result = retry_with_policy(
RetryPolicy {
max_retries: 10,
retry_delay_ms: 200,
deadline: Some(Duration::from_millis(250)),
},
"deadline test",
|| {
let seen = seen.clone();
async move {
seen.fetch_add(1, Ordering::SeqCst);
Err::<Value, _>(DataflowError::Timeout("nope".into()))
}
},
)
.await;
assert!(result.is_err());
assert!(
start.elapsed() < Duration::from_secs(2),
"loop must stop at the deadline, took {:?}",
start.elapsed()
);
assert!(
attempts.load(Ordering::SeqCst) < 5,
"deadline must cut the attempt count, got {}",
attempts.load(Ordering::SeqCst)
);
}
}