dataflow_rs/run/builder.rs
1//! `EngineBuilder`: an engine from definitions, for a host that does not
2//! quarantine.
3
4use super::engine::{Engine, EngineConfig};
5use crate::base::Issue;
6use crate::def::{ParseOptions, Parsed, WorkflowDef, parse, parse_owned};
7use crate::function::{CallLayer, Function, Registry};
8use crate::program::CompileOptions;
9use serde_json::Value;
10use std::fmt;
11
12/// A definition given to the builder: parsed, with the issues its parse
13/// reported, which a rejection keeps, or refused by `parse` with its issues.
14enum Pending {
15 Parsed(Box<WorkflowDef>, Vec<Issue>),
16 Refused(Vec<Issue>),
17}
18
19/// Builds an [`Engine`] from definitions: functions registered on a
20/// [`Registry`], the workflows, and the config. [`build`](Self::build) fails
21/// if any workflow is rejected, with every rejected definition's issues.
22///
23/// A host that quarantines, and runs the workflows that compile, uses a
24/// [`Compiler`](crate::program::Compiler) instead.
25///
26/// ```
27/// use dataflow_rs::Engine;
28/// use dataflow_rs::program::IssueCode;
29/// use serde_json::json;
30///
31/// let issues = Engine::builder()
32/// .workflow_json(&json!({"id": "w", "tasks": [
33/// {"id": "t", "function": {"name": "fetch"}}
34/// ]}))
35/// .build()
36/// .unwrap_err();
37/// assert_eq!(issues[0].code, IssueCode::UnknownFunction);
38/// ```
39pub struct EngineBuilder {
40 registry: Registry,
41 options: CompileOptions,
42 config: EngineConfig,
43 pending: Vec<Pending>,
44}
45
46impl EngineBuilder {
47 /// A builder over `Registry::default()`, with the default options and
48 /// config and no workflows.
49 pub(crate) fn new() -> Self {
50 Self {
51 registry: Registry::default(),
52 options: CompileOptions::default(),
53 config: EngineConfig::default(),
54 pending: Vec::new(),
55 }
56 }
57
58 /// Replaces the registry, and every function registered so far with it.
59 #[must_use]
60 pub fn registry(mut self, registry: Registry) -> Self {
61 self.registry = registry;
62 self
63 }
64
65 /// Registers `f` under `name` on the registry, as
66 /// [`Registry::register`], whose panics it shares.
67 #[must_use]
68 pub fn register<F: Function>(mut self, name: &str, f: F) -> Self {
69 self.registry.register(name, f);
70 self
71 }
72
73 /// Adds a layer around every host call on the registry, as
74 /// [`Registry::layer`]: the first added is the outermost.
75 #[must_use]
76 pub fn layer<L: CallLayer>(mut self, l: L) -> Self {
77 self.registry.layer(l);
78 self
79 }
80
81 /// Sets the options the workflows compile with.
82 #[must_use]
83 pub fn options(mut self, options: CompileOptions) -> Self {
84 self.options = options;
85 self
86 }
87
88 /// Sets the engine's config.
89 #[must_use]
90 pub fn config(mut self, config: EngineConfig) -> Self {
91 self.config = config;
92 self
93 }
94
95 /// Adds a parsed definition.
96 #[must_use]
97 pub fn workflow(mut self, def: WorkflowDef) -> Self {
98 self.pending
99 .push(Pending::Parsed(Box::new(def), Vec::new()));
100 self
101 }
102
103 /// Adds a definition to parse with [`ParseOptions::default`], the compat
104 /// reader on. One `parse` refuses makes [`build`](Self::build) fail with
105 /// its parse issues, and one it reads but the compile rejects with its
106 /// parse issues, then its compile issues.
107 #[must_use]
108 pub fn workflow_json(self, raw: &Value) -> Self {
109 self.parsed(parse(raw, ParseOptions::default()))
110 }
111
112 /// Adds an owned definition, avoiding the compatibility reader's copy.
113 /// Otherwise identical to [`Self::workflow_json`].
114 #[must_use]
115 pub fn workflow_json_owned(self, raw: Value) -> Self {
116 self.parsed(parse_owned(raw, ParseOptions::default()))
117 }
118
119 fn parsed(mut self, parsed: Parsed) -> Self {
120 self.pending.push(match parsed.def {
121 Some(def) => Pending::Parsed(Box::new(def), parsed.issues),
122 None => Pending::Refused(parsed.issues),
123 });
124 self
125 }
126
127 /// Compiles every definition and links them into the engine's program.
128 ///
129 /// # Errors
130 ///
131 /// Every issue of every definition that `parse` or
132 /// [`compile_one`](crate::program::Compiler::compile_one) rejected, in
133 /// input order, a rejected definition's parse issues before its
134 /// compile issues. On success the other issues are dropped: a host that
135 /// wants them calls [`Compiler::check`](crate::program::Compiler::check).
136 pub fn build(self) -> std::result::Result<Engine, Vec<Issue>> {
137 let compiler = self.registry.compiler(self.options);
138 let mut workflows = Vec::with_capacity(self.pending.len());
139 let mut rejected = Vec::new();
140 for pending in self.pending {
141 match pending {
142 Pending::Parsed(def, mut parsed) => match compiler.compile_one(&def) {
143 Ok(compiled) => workflows.push(compiled.workflow),
144 Err(mut issues) => {
145 rejected.append(&mut parsed);
146 rejected.append(&mut issues);
147 }
148 },
149 Pending::Refused(mut issues) => rejected.append(&mut issues),
150 }
151 }
152 if !rejected.is_empty() {
153 return Err(rejected);
154 }
155 Ok(Engine::new(compiler.link(workflows), self.config))
156 }
157}
158
159impl fmt::Debug for EngineBuilder {
160 /// The registry, the options, the config and how many definitions it
161 /// holds.
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.debug_struct("EngineBuilder")
164 .field("registry", &self.registry)
165 .field("options", &self.options)
166 .field("config", &self.config)
167 .field("workflows", &self.pending.len())
168 .finish()
169 }
170}