use std::sync::Arc;
use async_trait::async_trait;
use dataflow_rs::TemplateCompiler;
use dataflow_rs::engine::error::DataflowError;
use dataflow_rs::engine::functions::AsyncFunctionHandler;
use dataflow_rs::engine::task_context::TaskContext;
use dataflow_rs::engine::task_outcome::TaskOutcome;
use serde::de::DeserializeOwned;
use serde_json::Value;
use super::connector_helpers::{
ConnectorCall, apply_output, require_connector, require_str_field, resolve_output_path,
};
use super::templated_input::TemplatedInput;
use crate::connector::{ConnectorRegistry, ConnectorTarget};
use crate::engine::HandlerError;
pub trait ConnectorInput: DeserializeOwned + Send + Sync + 'static {
fn connector(&self, handler: &'static str) -> Result<&str, DataflowError>;
fn output(&self, handler: &'static str, ctx: &TaskContext<'_>)
-> Result<String, DataflowError>;
fn compile(
&mut self,
_handler: &'static str,
_c: &TemplateCompiler,
) -> dataflow_rs::Result<()> {
Ok(())
}
}
impl ConnectorInput for TemplatedInput {
fn connector(&self, handler: &'static str) -> Result<&str, DataflowError> {
require_str_field(self.raw(), "connector", handler)
}
fn output(
&self,
handler: &'static str,
ctx: &TaskContext<'_>,
) -> Result<String, DataflowError> {
resolve_output_path(self, handler, ctx)
}
fn compile(&mut self, handler: &'static str, c: &TemplateCompiler) -> dataflow_rs::Result<()> {
TemplatedInput::compile(self, handler, c)
}
}
impl ConnectorInput for dataflow_rs::engine::functions::HttpCallConfig {
fn connector(&self, handler: &'static str) -> Result<&str, DataflowError> {
literal_connector(&self.connector, handler)
}
fn output(
&self,
_handler: &'static str,
ctx: &TaskContext<'_>,
) -> Result<String, DataflowError> {
Ok(self
.resolve_response_path(ctx)?
.unwrap_or_else(|| "data".to_string()))
}
}
impl ConnectorInput for dataflow_rs::engine::functions::PublishKafkaConfig {
fn connector(&self, handler: &'static str) -> Result<&str, DataflowError> {
literal_connector(&self.connector, handler)
}
fn output(
&self,
_handler: &'static str,
_ctx: &TaskContext<'_>,
) -> Result<String, DataflowError> {
Ok("data".to_string())
}
}
fn literal_connector<'t>(
template: &'t dataflow_rs::Template,
handler: &'static str,
) -> Result<&'t str, DataflowError> {
template.as_json().as_str().ok_or_else(|| {
DataflowError::Validation(format!(
"{handler} 'connector' must be a literal connector name — a computed connector is not resolvable before the connector is looked up"
))
})
}
pub struct Produced {
pub value: Option<Value>,
pub outcome: TaskOutcome,
}
impl From<Value> for Produced {
fn from(value: Value) -> Self {
Self {
value: Some(value),
outcome: TaskOutcome::Success,
}
}
}
impl Produced {
pub fn nothing() -> Self {
Self {
value: None,
outcome: TaskOutcome::Success,
}
}
pub fn with_outcome(value: Value, outcome: TaskOutcome) -> Self {
Self {
value: Some(value),
outcome,
}
}
}
#[async_trait]
pub trait ConnectorHandler: Send + Sync + 'static {
const NAME: &'static str;
type Kind: ConnectorTarget;
type Input: ConnectorInput;
type Parsed: Send;
fn registry(&self) -> &Arc<ConnectorRegistry>;
fn parse(
&self,
call: &ConnectorCall<'_>,
input: &Self::Input,
ctx: &TaskContext<'_>,
) -> Result<Self::Parsed, HandlerError>;
fn gate(
_parsed: &Self::Parsed,
_conn: &<Self::Kind as ConnectorTarget>::Config,
_connector: &str,
) -> Result<(), HandlerError> {
Ok(())
}
async fn run(
&self,
parsed: Self::Parsed,
conn: &<Self::Kind as ConnectorTarget>::Config,
call: &ConnectorCall<'_>,
input: &Self::Input,
ctx: &mut TaskContext<'_>,
) -> Result<Produced, HandlerError>;
fn parse_input(input: &Value) -> dataflow_rs::Result<Self::Input> {
serde_json::from_value(input.clone()).map_err(DataflowError::from_serde)
}
fn compile_input(_input: &mut Self::Input, _c: &TemplateCompiler) -> dataflow_rs::Result<()> {
Ok(())
}
}
pub struct Connector<H>(pub H);
#[async_trait]
impl<H: ConnectorHandler> AsyncFunctionHandler for Connector<H> {
type Input = H::Input;
fn parse_input(input: &Value) -> dataflow_rs::Result<Self::Input> {
H::parse_input(input)
}
fn compile_input(input: &mut Self::Input, c: &TemplateCompiler) -> dataflow_rs::Result<()> {
input.compile(H::NAME, c)?;
H::compile_input(input, c)
}
async fn execute(
&self,
ctx: &mut TaskContext<'_>,
input: &Self::Input,
) -> dataflow_rs::Result<TaskOutcome> {
let call = ConnectorCall::begin(H::NAME, input, ctx)?;
let parsed = self.0.parse(&call, input, ctx).map_err(|e| {
let e: dataflow_rs::DataflowError = e.into();
e
})?;
let registry = self.0.registry();
call.run(registry, async {
let config = call.resolve(registry, None).await?;
let conn = require_connector::<H::Kind>(&config, call.connector)?;
H::gate(&parsed, conn, call.connector).map_err(dataflow_rs::DataflowError::from)?;
let produced = self
.0
.run(parsed, conn, &call, input, ctx)
.await
.map_err(dataflow_rs::DataflowError::from)?;
if let Some(value) = produced.value {
apply_output(ctx, &call.output, value);
}
Ok(produced.outcome)
})
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::connector::ConnectorRegistry;
struct Probe(Arc<ConnectorRegistry>);
#[async_trait]
impl ConnectorHandler for Probe {
const NAME: &'static str = "probe";
type Kind = crate::connector::kind::Cache;
type Input = TemplatedInput;
type Parsed = String;
fn registry(&self) -> &Arc<ConnectorRegistry> {
&self.0
}
fn parse(
&self,
call: &ConnectorCall<'_>,
input: &TemplatedInput,
_ctx: &TaskContext<'_>,
) -> Result<Self::Parsed, HandlerError> {
Ok(call.require_str(input, "key")?.to_string())
}
async fn run(
&self,
_parsed: Self::Parsed,
_conn: &crate::connector::CacheConnectorConfig,
_call: &ConnectorCall<'_>,
_input: &TemplatedInput,
_ctx: &mut TaskContext<'_>,
) -> Result<Produced, HandlerError> {
Ok(Produced::nothing())
}
}
#[tokio::test]
async fn the_literal_prologue_is_reported_before_anything_message_dependent() {
let handler = Connector(Probe(Arc::new(ConnectorRegistry::new(Default::default()))));
let datalogic = Arc::new(dataflow_rs::datalogic_rs::Engine::new());
let mut message = dataflow_rs::Message::from_value(&serde_json::json!({}));
let mut ctx = TaskContext::new(&mut message, &datalogic);
let err = handler
.execute(&mut ctx, &TemplatedInput::from(serde_json::json!({})))
.await
.expect_err("a task naming no connector cannot run");
let msg = err.to_string();
assert!(
msg.contains("connector"),
"the literal prologue must be reported first: {msg}"
);
assert!(
!msg.contains("'key'"),
"the message-dependent field must not pre-empt it: {msg}"
);
}
}