dataflow_rs/function/call.rs
1//! `Function`: the trait a host function implements, and what `prepare`
2//! is told.
3
4use super::{Ctx, FunctionConfig, FunctionSpec, Outcome};
5use crate::base::{Issue, Result};
6
7/// A host function: a typed config compiled once at build, and a call per
8/// task run.
9///
10/// `call` returns a `Send` future, so an implementation may be written with
11/// `async fn`. [`testing::call`](super::testing::call) runs one call of it
12/// outside any workflow:
13///
14/// ```
15/// # tokio::runtime::Builder::new_current_thread().build().unwrap().block_on(async {
16/// use dataflow_rs::datavalue::OwnedDataValue;
17/// use dataflow_rs::expr::Output;
18/// use dataflow_rs::function::{FunctionConfig, FunctionSpec, testing};
19/// use dataflow_rs::{Ctx, Expr, Function, Outcome, Registry, Result};
20/// use serde_json::json;
21///
22/// #[derive(FunctionConfig)]
23/// struct FetchConfig {
24/// url: Expr<String>,
25/// #[output(default = "data.response")]
26/// output: Output,
27/// }
28///
29/// struct Fetch;
30///
31/// impl Function for Fetch {
32/// type Config = FetchConfig;
33///
34/// fn spec(&self) -> FunctionSpec {
35/// FunctionSpec::default().with_category("io")
36/// }
37///
38/// async fn call(&self, ctx: &mut Ctx<'_>, cfg: &FetchConfig) -> Result<Outcome> {
39/// let url = ctx.eval(&cfg.url)?;
40/// // A real fetch awaits its response here.
41/// let response = OwnedDataValue::from(&json!({"url": &*url, "status": 200}));
42/// ctx.emit(&cfg.output, response)?;
43/// Ok(Outcome::Ok)
44/// }
45/// }
46///
47/// let mut registry = Registry::default();
48/// registry.register("fetch", Fetch);
49///
50/// let called = testing::call(Fetch, json!({"url": "https://example.com"})).await;
51/// assert_eq!(called.outcome.unwrap(), Outcome::Ok);
52/// let status = called.message.context().get(&"data.response.status".parse().unwrap());
53/// assert_eq!(status, Some(&OwnedDataValue::from(&json!(200))));
54/// # });
55/// ```
56pub trait Function: Send + Sync + 'static {
57 /// The function's `input`, compiled at build.
58 type Config: FunctionConfig;
59
60 /// What the function says about itself, for tooling. Called once, when
61 /// it is registered.
62 fn spec(&self) -> FunctionSpec {
63 FunctionSpec::default()
64 }
65
66 /// Checks or completes a compiled config at build, with what the config
67 /// alone cannot know. It runs only when the config compiled.
68 ///
69 /// Issue paths are relative to the config. The compiler prefixes the
70 /// task's `function.input` path and stamps the step, and a Rejected
71 /// issue fails the task's compile.
72 fn prepare(
73 &self,
74 _cfg: &mut Self::Config,
75 _p: &Prepare<'_>,
76 ) -> std::result::Result<(), Vec<Issue>> {
77 Ok(())
78 }
79
80 /// Runs one call.
81 fn call(
82 &self,
83 ctx: &mut Ctx<'_>,
84 cfg: &Self::Config,
85 ) -> impl Future<Output = Result<Outcome>> + Send;
86}
87
88/// What [`Function::prepare`] is told about the call site it prepares.
89///
90/// Its fields are private, behind getters, so it can grow.
91#[derive(Debug)]
92pub struct Prepare<'a> {
93 name: &'a str,
94 workflow_id: &'a str,
95 task_id: &'a str,
96 spec: &'a FunctionSpec,
97}
98
99impl<'a> Prepare<'a> {
100 /// What `prepare` is told about the task `task_id` of the workflow
101 /// `workflow_id`, which calls the function registered as `name` with
102 /// `spec`.
103 pub(crate) fn new(
104 name: &'a str,
105 workflow_id: &'a str,
106 task_id: &'a str,
107 spec: &'a FunctionSpec,
108 ) -> Self {
109 Self {
110 name,
111 workflow_id,
112 task_id,
113 spec,
114 }
115 }
116
117 /// The name the function is registered under.
118 pub fn name(&self) -> &'a str {
119 self.name
120 }
121
122 /// The id of the workflow the call site is in.
123 pub fn workflow_id(&self) -> &'a str {
124 self.workflow_id
125 }
126
127 /// The id of the task that calls the function.
128 pub fn task_id(&self) -> &'a str {
129 self.task_id
130 }
131
132 /// The function's spec, as it was when registered.
133 pub fn spec(&self) -> &'a FunctionSpec {
134 self.spec
135 }
136}