dataflow_rs/function/config.rs
1//! `FunctionConfig`: a function's input, compiled once at build.
2
3use crate::base::{IssueCode, Result};
4use crate::expr::{CompileCx, ExprVisitor, FieldSpec, Output, Resolver};
5use datavalue::OwnedDataValue;
6use serde_json::Value;
7
8/// A function's `input`, compiled at build so a malformed one fails before any
9/// message runs. `#[derive(FunctionConfig)]` writes the implementation.
10///
11/// ```
12/// use dataflow_rs::Expr;
13/// use dataflow_rs::expr::{CompileCx, ExprEngine, Output};
14/// use dataflow_rs::function::FunctionConfig;
15/// use dataflow_rs::program::IssueCode;
16/// use serde_json::json;
17///
18/// #[derive(FunctionConfig)]
19/// struct Fetch {
20/// /// The URL to fetch.
21/// #[config(records)]
22/// url: Expr<String>,
23/// #[config(default = 5000)]
24/// timeout_ms: Expr<u64>,
25/// #[output(default = "data.response")]
26/// output: Output,
27/// }
28///
29/// let engine = ExprEngine::default();
30/// let mut issues = Vec::new();
31/// let mut cx = CompileCx::new(&engine, &mut issues);
32/// let fetch = Fetch::compile(&json!({"url": {"var": "data.url"}}), &mut cx).unwrap();
33/// assert_eq!(fetch.timeout_ms.constant(), Some(&5000));
34/// assert_eq!(fetch.output.constant().unwrap().to_string(), "data.response");
35/// assert!(fetch.url.constant().is_none());
36///
37/// assert!(Fetch::compile(&json!({"uri": "x"}), &mut cx).is_none());
38/// drop(cx);
39/// let codes: Vec<IssueCode> = issues.iter().map(|i| i.code).collect();
40/// assert_eq!(codes, [IssueCode::LikelyTypo, IssueCode::InputParse]);
41/// assert_eq!(issues[1].path.to_string(), "url");
42/// ```
43///
44/// Two outputs need one of them marked `#[output(primary)]`:
45///
46/// ```compile_fail
47/// use dataflow_rs::expr::Output;
48/// use dataflow_rs::function::FunctionConfig;
49///
50/// #[derive(FunctionConfig)]
51/// struct Split {
52/// ok: Output,
53/// failed: Output,
54/// }
55/// ```
56///
57/// The primary output, marked or the only one, is an `Output` or an
58/// `Option<Output>`, since `primary_output` lends it:
59///
60/// ```compile_fail
61/// use dataflow_rs::expr::Output;
62/// use dataflow_rs::function::FunctionConfig;
63///
64/// #[derive(FunctionConfig)]
65/// struct Fan {
66/// #[output(primary)]
67/// many: Vec<Output>,
68/// failed: Output,
69/// }
70/// ```
71///
72/// A field merely *named* `Output` is not one: the derive asserts the type's
73/// `ConfigField::IS_OUTPUT`, so an implementor that leaves it `false` is
74/// refused instead of silently compiling as a destination:
75///
76/// ```compile_fail
77/// use dataflow_rs::expr::{CompileCx, ConfigField, FieldKind, Resolver};
78/// use dataflow_rs::function::FunctionConfig;
79///
80/// struct Output;
81///
82/// impl ConfigField for Output {
83/// fn compile(_raw: &serde_json::Value, _cx: &mut CompileCx<'_>) -> Option<Self> {
84/// Some(Self)
85/// }
86/// fn resolve(
87/// &self,
88/// _r: &mut dyn Resolver,
89/// ) -> dataflow_rs::Result<Option<dataflow_rs::datavalue::OwnedDataValue>> {
90/// Ok(None)
91/// }
92/// fn kind() -> FieldKind {
93/// FieldKind::Output
94/// }
95/// }
96///
97/// #[derive(FunctionConfig)]
98/// struct Writes {
99/// out: Output,
100/// }
101/// ```
102///
103/// The derive refuses what it cannot honour. An unknown `config` key:
104///
105/// ```compile_fail
106/// # use dataflow_rs::function::FunctionConfig;
107/// #[derive(FunctionConfig)]
108/// struct Unknown {
109/// #[config(renamed = "k")]
110/// a: bool,
111/// }
112/// ```
113///
114/// A generic struct, even one whose parameter is a config field:
115///
116/// ```compile_fail
117/// # use dataflow_rs::expr::ConfigField;
118/// # use dataflow_rs::function::FunctionConfig;
119/// #[derive(FunctionConfig)]
120/// struct Generic<T: ConfigField> {
121/// a: T,
122/// }
123/// ```
124///
125/// A tuple struct:
126///
127/// ```compile_fail
128/// # use dataflow_rs::function::FunctionConfig;
129/// #[derive(FunctionConfig)]
130/// struct Tuple(bool);
131/// ```
132///
133/// `records` on a `serde` field, which holds no expression to record:
134///
135/// ```compile_fail
136/// # use dataflow_rs::function::FunctionConfig;
137/// #[derive(FunctionConfig)]
138/// struct Recorded {
139/// #[config(serde, records)]
140/// a: Option<bool>,
141/// }
142/// ```
143///
144/// A `serde` field whose type is not `Serialize`, which
145/// [`resolve`](Self::resolve) reads it through:
146///
147/// ```compile_fail
148/// # use dataflow_rs::function::FunctionConfig;
149/// #[derive(serde::Deserialize)]
150/// enum Mode {
151/// Fast,
152/// }
153///
154/// #[derive(FunctionConfig)]
155/// struct Unserialized {
156/// #[config(serde)]
157/// mode: Mode,
158/// }
159/// ```
160///
161/// A key given twice on one field:
162///
163/// ```compile_fail
164/// # use dataflow_rs::function::FunctionConfig;
165/// #[derive(FunctionConfig)]
166/// struct Renamed {
167/// #[config(rename = "a", rename = "b")]
168/// x: bool,
169/// }
170/// ```
171///
172/// A `default_json` that is not JSON, refused where it is written rather than
173/// when a definition is first compiled:
174///
175/// ```compile_fail
176/// # use dataflow_rs::function::FunctionConfig;
177/// #[derive(FunctionConfig)]
178/// struct BadDefault {
179/// #[config(default_json = "{not json")]
180/// extra: serde_json::Value,
181/// }
182/// ```
183///
184/// A hand-written config that does not say how it resolves:
185///
186/// ```compile_fail
187/// use dataflow_rs::expr::{CompileCx, ExprVisitor, FieldSpec};
188/// use dataflow_rs::function::FunctionConfig;
189///
190/// struct Bare;
191///
192/// impl FunctionConfig for Bare {
193/// fn compile(_input: &serde_json::Value, _cx: &mut CompileCx<'_>) -> Option<Self> {
194/// Some(Self)
195/// }
196/// fn visit(&self, _v: &mut dyn ExprVisitor) {}
197/// fn fields() -> &'static [FieldSpec] {
198/// &[]
199/// }
200/// }
201/// ```
202pub trait FunctionConfig: Sized + Send + Sync + 'static {
203 /// Whether this is [`Templated`](super::Templated), whose fields are
204 /// declared by the call's input rather than by a struct: the registry
205 /// checks its spec against that, and the erasure walks its primary
206 /// output by its own rule.
207 const TEMPLATED: bool = false;
208
209 /// Every issue in the config, each at its exact path below the
210 /// [`CompileCx`]'s root. `None` when any is Rejected.
211 fn compile(input: &Value, cx: &mut CompileCx<'_>) -> Option<Self>;
212
213 /// Walks the config's expressions; paths are relative to the config's root.
214 fn visit(&self, v: &mut dyn ExprVisitor);
215
216 /// The config's fields, for tooling.
217 fn fields() -> &'static [FieldSpec];
218
219 /// The output [`Ctx::emit_default`](crate::Ctx::emit_default) writes
220 /// through, and an answering layer answers with: the derive returns the
221 /// field marked `#[output(primary)]`, or the only output when there is
222 /// one (`None` for an `Option<Output>` the input did not set). A
223 /// hand-written config has none unless it says so.
224 fn primary_output(&self) -> Option<&Output> {
225 None
226 }
227
228 /// The config resolved for one call, through `r`: every field with a
229 /// value, set by the input or by its default, keyed by its authored key,
230 /// in declaration order; an `Option` left unset is absent. An expression
231 /// is evaluated, a document rendered, an output's path given as its
232 /// dotted text, a nested config resolved as an object, and a
233 /// `#[config(serde)]` field written as it serializes (unset when that is
234 /// `null`, as an `Option` left `None` is). The derive generates it; a
235 /// hand-written config writes its own, as it writes
236 /// [`compile`](Self::compile) and [`visit`](Self::visit), so one that
237 /// forgets it fails to compile rather than resolving to nothing.
238 ///
239 /// [`Ctx::input`](crate::Ctx::input) is this over the call's context,
240 /// with every field that reads a secret `null`.
241 ///
242 /// # Errors
243 ///
244 /// The first field's that fails: an evaluation's, or a serde field that
245 /// does not serialize.
246 fn resolve(&self, r: &mut dyn Resolver) -> Result<OwnedDataValue>;
247}
248
249/// The config of a function that takes no input: `{}` or `null`.
250impl FunctionConfig for () {
251 fn compile(input: &Value, cx: &mut CompileCx<'_>) -> Option<Self> {
252 let before = cx.rejected();
253 match input {
254 Value::Null => {}
255 Value::Object(map) => super::__private::unknown_keys(map, &[], cx),
256 _ => cx.issue(
257 IssueCode::InputParse,
258 "expected an object or null, this function takes no input",
259 ),
260 }
261 (cx.rejected() == before).then_some(())
262 }
263
264 fn visit(&self, _v: &mut dyn ExprVisitor) {}
265
266 fn fields() -> &'static [FieldSpec] {
267 &[]
268 }
269
270 /// No input, so nothing to resolve: `null`.
271 fn resolve(&self, _r: &mut dyn Resolver) -> Result<OwnedDataValue> {
272 Ok(OwnedDataValue::Null)
273 }
274}