Skip to main content

dataflow_rs/function/
registry.rs

1//! `Registry`: the functions and layers a program compiles against.
2
3use super::builtins::BuiltinKind;
4use super::erased::{Erased, ErasedFunction};
5use super::templated::{default_problem, primary_problem};
6use super::{CallLayer, Function, FunctionConfig, FunctionSpec};
7use crate::base::{AuthoredPath, Error, ErrorKind, PathSeg};
8use crate::expr::{ExprEngine, FieldKind, FieldSpec, ValueKind};
9use std::collections::BTreeMap;
10use std::fmt;
11use std::str::FromStr;
12use std::sync::Arc;
13
14/// Functions and layers, plus the [`ExprEngine`] they compile against.
15///
16/// `Arc` inside: a clone is cheap and shares everything. Registering on a
17/// clone copies the table once and leaves the original as it was, which is
18/// how a host reroutes a name for some programs and not others.
19///
20/// ```
21/// use dataflow_rs::{Ctx, Function, Outcome, Registry, Result};
22///
23/// struct Ping;
24///
25/// impl Function for Ping {
26///     type Config = ();
27///     async fn call(&self, _ctx: &mut Ctx<'_>, _cfg: &()) -> Result<Outcome> {
28///         Ok(Outcome::Ok)
29///     }
30/// }
31///
32/// let mut registry = Registry::default();
33/// registry.register("ping", Ping);
34/// let names: Vec<&str> = registry.catalogue().map(|f| f.name).collect();
35/// assert_eq!(names[..2], ["map", "validation"]);
36/// assert_eq!(names.last(), Some(&"ping"));
37/// ```
38#[derive(Clone)]
39pub struct Registry {
40    inner: Arc<Inner>,
41}
42
43#[derive(Clone)]
44struct Inner {
45    exprs: ExprEngine,
46    /// Each built-in's spec, in [`BuiltinKind::ALL`] order.
47    builtin_specs: [FunctionSpec; BuiltinKind::ALL.len()],
48    hosts: BTreeMap<Box<str>, Arc<dyn ErasedFunction>>,
49    /// The first registered is the outermost.
50    layers: Vec<Arc<dyn CallLayer>>,
51}
52
53/// What a function name resolves to.
54#[derive(Clone, Copy)]
55pub(crate) enum Entry<'r> {
56    /// One of the eight built-ins, which run inline.
57    Builtin(BuiltinKind),
58    /// A registered host function.
59    Host(&'r dyn ErasedFunction),
60}
61
62impl Registry {
63    /// A registry holding the eight built-ins, compiling against `exprs`.
64    pub fn new(exprs: ExprEngine) -> Self {
65        Self {
66            inner: Arc::new(Inner {
67                exprs,
68                builtin_specs: BuiltinKind::ALL.map(BuiltinKind::spec),
69                hosts: BTreeMap::new(),
70                layers: Vec::new(),
71            }),
72        }
73    }
74
75    /// Registers `f` under `name`, asking it for its [`spec`](Function::spec)
76    /// once. A name registered before is replaced.
77    ///
78    /// # Panics
79    ///
80    /// On a reserved name: one in
81    /// [`BUILTIN_FUNCTIONS`](crate::def::BUILTIN_FUNCTIONS), since a built-in
82    /// cannot be shadowed, or `validate`, v3's alias for `validation`, which
83    /// the compat reader rewrites before any name is looked up. A host that
84    /// registers names from data checks them with
85    /// [`def::is_reserved_function`](crate::def::is_reserved_function) first.
86    ///
87    /// On a spec the function's config cannot honour, a host bug best found
88    /// once, here:
89    ///
90    /// - a [`Reference`](super::Reference) whose `field` is not an authored
91    ///   path, or does not reach a field of the config: it starts at one of
92    ///   the config's fields, and each segment below is a field of a nested
93    ///   config, an index into a list or a key of a map; nothing goes below a
94    ///   scalar, an expression or an output, and anything may go below a
95    ///   literal of any JSON, an object or an array, a document, or a
96    ///   runtime-declared set of fields;
97    /// - [`runtime_fields`](FunctionSpec::runtime_fields) on a config that is
98    ///   not [`Templated`](super::Templated), which would never compile them;
99    /// - a [`Templated`](super::Templated) config with no `runtime_fields`, which has nothing
100    ///   to compile;
101    /// - two config fields with the same name, which would make lookup and
102    ///   resolution disagree;
103    /// - a runtime field with a nested, list, map or templated kind, which
104    ///   [`Templated`](super::Templated) cannot compile;
105    /// - `runtime_fields` holding several outputs without exactly one marked
106    ///   [`primary`](FieldSpec::primary), which `#[derive(FunctionConfig)]`
107    ///   refuses at expansion: no output, or a guess, would be the one
108    ///   [`Ctx::emit_default`](crate::Ctx::emit_default) writes through;
109    /// - a field's declared [`default`](FieldSpec::default) that its
110    ///   [`kind`](FieldSpec::kind) cannot compile, which would otherwise
111    ///   surface as the author's `INPUT_PARSE` in every compile that leaves
112    ///   the key out.
113    pub fn register<F: Function>(&mut self, name: &str, f: F) {
114        if let Err(error) = self.try_register(name, f) {
115            panic!("{}", error.message());
116        }
117    }
118
119    /// [`register`](Self::register), refusing instead of panicking: a name
120    /// taken from data, or a `Function` implementation built at run time,
121    /// need not be a host bug.
122    ///
123    /// # Errors
124    ///
125    /// [`ErrorKind::Config`] for everything `register` panics on: a reserved
126    /// name, or a [`spec`](Function::spec) its config cannot honour.
127    pub fn try_register<F: Function>(&mut self, name: &str, f: F) -> Result<(), Error> {
128        if crate::def::is_reserved_function(name) {
129            return Err(Error::new(
130                ErrorKind::Config,
131                format!(
132                    "cannot register `{name}`: the name is reserved for the built-in functions \
133                     (def::BUILTIN_FUNCTIONS, and v3's alias for `validation`)"
134                ),
135            ));
136        }
137        let erased = Erased::new(f);
138        let templated = <F::Config as FunctionConfig>::TEMPLATED;
139        check_spec(
140            name,
141            erased.spec(),
142            erased.fields(),
143            templated,
144            &self.inner.exprs,
145        )
146        .map_err(|message| Error::new(ErrorKind::Config, message))?;
147        let erased: Arc<dyn ErasedFunction> = Arc::new(erased);
148        Arc::make_mut(&mut self.inner)
149            .hosts
150            .insert(Box::from(name), erased);
151        Ok(())
152    }
153
154    /// Adds a layer around every host call. The first registered is the
155    /// outermost.
156    pub fn layer<L: CallLayer>(&mut self, layer: L) {
157        Arc::make_mut(&mut self.inner).layers.push(Arc::new(layer));
158    }
159
160    /// Whether `self` and `other` are one registry: one is a clone of the
161    /// other, and neither has registered anything since.
162    pub(crate) fn ptr_eq(&self, other: &Self) -> bool {
163        Arc::ptr_eq(&self.inner, &other.inner)
164    }
165
166    /// The engine expressions compile against.
167    pub fn exprs(&self) -> &ExprEngine {
168        &self.inner.exprs
169    }
170
171    /// Every function: the eight built-ins in
172    /// [`BUILTIN_FUNCTIONS`](crate::def::BUILTIN_FUNCTIONS) order,
173    /// then the host functions by name.
174    pub fn catalogue(&self) -> impl Iterator<Item = FunctionInfo<'_>> {
175        let builtins = BuiltinKind::ALL
176            .into_iter()
177            .zip(&self.inner.builtin_specs)
178            .map(|(kind, spec)| FunctionInfo {
179                name: kind.name(),
180                builtin: true,
181                spec,
182                fields: kind.fields(),
183            });
184        let hosts = self.inner.hosts.iter().map(|(name, f)| FunctionInfo {
185            name,
186            builtin: false,
187            spec: f.spec(),
188            fields: f.fields(),
189        });
190        builtins.chain(hosts)
191    }
192
193    /// What `name` calls: a built-in, a host function, or nothing. v3's
194    /// alias `validate` is nothing here; the compat reader rewrites it first.
195    pub(crate) fn entry(&self, name: &str) -> Option<Entry<'_>> {
196        match BuiltinKind::from_name(name) {
197            Some(kind) => Some(Entry::Builtin(kind)),
198            None => self.inner.hosts.get(name).map(|f| Entry::Host(&**f)),
199        }
200    }
201
202    /// The registry's layers, outermost first.
203    pub(crate) fn layers(&self) -> &[Arc<dyn CallLayer>] {
204        &self.inner.layers
205    }
206}
207
208/// Panics on a spec `register` refuses: runtime fields on a config that is
209/// not [`Templated`](super::Templated), none on one that is, several outputs among them
210/// without exactly one primary, or a reference that does not reach a field
211/// through `fields`, the config's fields, and the kinds below them.
212fn check_spec(
213    name: &str,
214    spec: &FunctionSpec,
215    fields: &[FieldSpec],
216    templated: bool,
217    exprs: &ExprEngine,
218) -> Result<(), String> {
219    match (templated, spec.runtime_fields.is_some()) {
220        (false, true) => {
221            return Err(format!(
222                "cannot register `{name}`: its spec declares runtime fields, but its config is \
223                 not `Templated`, so it never compiles them"
224            ));
225        }
226        (true, false) => {
227            return Err(format!(
228                "cannot register `{name}`: its config is `Templated`, but its spec declares no \
229                 runtime fields, so every compile of it fails"
230            ));
231        }
232        _ => {}
233    }
234    if let Some(problem) = spec.runtime_fields.as_deref().and_then(primary_problem) {
235        return Err(format!("cannot register `{name}`: {problem}"));
236    }
237    for (index, field) in fields.iter().enumerate() {
238        if fields[..index].iter().any(|other| other.name == field.name) {
239            return Err(format!(
240                "cannot register `{name}`: its field `{}` is declared twice",
241                field.name
242            ));
243        }
244        if templated
245            && matches!(
246                field.kind,
247                FieldKind::Nested(_)
248                    | FieldKind::List(_)
249                    | FieldKind::Map(_)
250                    | FieldKind::Templated
251            )
252        {
253            return Err(format!(
254                "cannot register `{name}`: its runtime field `{}` has an unsupported kind; \
255                 use an expression, document, literal or output",
256                field.name
257            ));
258        }
259        if let Some(problem) = default_problem(exprs, field) {
260            return Err(format!("cannot register `{name}`: {problem}"));
261        }
262    }
263    for reference in &spec.references {
264        let field = &reference.field;
265        let path = AuthoredPath::from_str(field).map_err(|error| {
266            format!(
267                "cannot register `{name}`: its reference field `{field}` is not an authored \
268                 path ({error})"
269            )
270        })?;
271        if let Err(why) = reaches(fields, path.segs()) {
272            return Err(format!(
273                "cannot register `{name}`: its reference field `{field}` {why}"
274            ));
275        }
276    }
277    Ok(())
278}
279
280/// Whether `segs` reach a field: the first names one of `fields`, and
281/// each one below is walked through the kind above it. `Err` says why not.
282///
283/// A nested config's fields are named by key, a list's elements by index
284/// and a map's values by any key. A kind whose shape no spec describes, a
285/// literal of any JSON, an object or an array, a document, or a
286/// runtime-declared set of fields, takes anything below it; any other kind
287/// takes nothing.
288fn reaches(fields: &[FieldSpec], segs: &[PathSeg]) -> Result<(), String> {
289    let start = || "does not start at a field of its config".to_owned();
290    let Some((PathSeg::Key(first), rest)) = segs.split_first() else {
291        return Err(start());
292    };
293    let Some(spec) = fields.iter().find(|f| f.name == **first) else {
294        return Err(start());
295    };
296    let mut kind = spec.kind;
297    let mut at = AuthoredPath::root().join(&**first);
298    for seg in rest {
299        let refused = |why: String| {
300            Err(format!(
301                "does not reach a field of its config: `{at}` {why}"
302            ))
303        };
304        kind = match (kind, seg) {
305            (
306                FieldKind::Literal(ValueKind::Any | ValueKind::Object | ValueKind::Array)
307                | FieldKind::Interpolated
308                | FieldKind::Templated,
309                _,
310            ) => return Ok(()),
311            (FieldKind::Nested(fields), PathSeg::Key(key)) => {
312                match fields().iter().find(|f| f.name == **key) {
313                    Some(spec) => spec.kind,
314                    None => return refused(format!("has no field `{key}`")),
315                }
316            }
317            (FieldKind::List(element), PathSeg::Index(_)) => element(),
318            (FieldKind::Map(element), PathSeg::Key(_)) => element(),
319            (FieldKind::List(_), PathSeg::Key(key)) => {
320                return refused(format!("is a list, so `{key}` is not an index"));
321            }
322            (_, PathSeg::Index(index)) => {
323                return refused(format!("is not a list, so it has no `[{index}]`"));
324            }
325            (_, PathSeg::Key(key)) => {
326                return refused(format!("holds no fields, so it has no `{key}`"));
327            }
328        };
329        at.push(seg.clone());
330    }
331    Ok(())
332}
333
334impl Default for Registry {
335    /// The eight built-ins, compiling against [`ExprEngine::default`].
336    fn default() -> Self {
337        Self::new(ExprEngine::default())
338    }
339}
340
341impl fmt::Debug for Registry {
342    /// The function names, in catalogue order.
343    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
344        let names: Vec<&str> = self.catalogue().map(|info| info.name).collect();
345        f.debug_struct("Registry")
346            .field("functions", &names)
347            .finish_non_exhaustive()
348    }
349}
350
351/// One row of the [`Registry::catalogue`]: a function, as tooling sees it.
352#[derive(Clone, Copy, Debug)]
353#[non_exhaustive]
354pub struct FunctionInfo<'a> {
355    /// The name a task calls it by.
356    pub name: &'a str,
357    /// Whether it is one of the eight built-ins.
358    pub builtin: bool,
359    /// What it says about itself.
360    pub spec: &'a FunctionSpec,
361    /// Its config's fields.
362    pub fields: &'a [FieldSpec],
363}
364
365#[cfg(test)]
366mod tests {
367    use super::*;
368    use crate::base::Result;
369    use crate::def::BUILTIN_FUNCTIONS;
370    use crate::function::{BoxFuture, Ctx, Next, Outcome};
371
372    struct Ping;
373
374    impl Function for Ping {
375        type Config = ();
376        fn spec(&self) -> FunctionSpec {
377            FunctionSpec::default().with_description("ping")
378        }
379        async fn call(&self, _ctx: &mut Ctx<'_>, _cfg: &()) -> Result<Outcome> {
380            Ok(Outcome::Ok)
381        }
382    }
383
384    struct Layer;
385
386    impl CallLayer for Layer {
387        fn call<'a>(
388            &'a self,
389            ctx: &'a mut Ctx<'_>,
390            next: Next<'a>,
391        ) -> BoxFuture<'a, Result<Outcome>> {
392            next.run(ctx)
393        }
394    }
395
396    #[test]
397    fn a_builtin_name_resolves_to_its_kind() {
398        let registry = Registry::default();
399        for name in BUILTIN_FUNCTIONS {
400            match registry.entry(name) {
401                Some(Entry::Builtin(kind)) => assert_eq!(kind.name(), name),
402                _ => panic!("`{name}` is a built-in"),
403            }
404        }
405        // v3's alias is the compat reader's to rewrite.
406        assert!(registry.entry("validate").is_none());
407        assert!(registry.entry("ping").is_none());
408    }
409
410    #[test]
411    fn a_host_name_resolves_to_its_function() {
412        let mut registry = Registry::default();
413        registry.register("ping", Ping);
414        match registry.entry("ping") {
415            Some(Entry::Host(f)) => assert_eq!(f.spec().description, "ping"),
416            _ => panic!("`ping` is registered"),
417        }
418    }
419
420    #[test]
421    fn the_builtin_specs_are_computed_in_catalogue_order() {
422        let registry = Registry::default();
423        for (kind, info) in BuiltinKind::ALL.into_iter().zip(registry.catalogue()) {
424            assert_eq!(info.name, kind.name());
425            assert_eq!(info.spec.description, kind.spec().description);
426            assert_eq!(info.spec.category, kind.spec().category);
427        }
428    }
429
430    #[test]
431    fn a_clone_shares_the_table_until_it_is_written() {
432        let mut registry = Registry::default();
433        registry.layer(Layer);
434        let mut clone = registry.clone();
435        assert!(Arc::ptr_eq(&clone.inner, &registry.inner));
436        clone.layer(Layer);
437        assert!(!Arc::ptr_eq(&clone.inner, &registry.inner));
438        assert_eq!(registry.layers().len(), 1);
439        assert_eq!(clone.layers().len(), 2);
440    }
441}