vertep_core/datamodel.rs
1//! The datamodel contract shared by every datamodel (`null`, `ecmascript`, later `cel` and
2//! `rust`), the datamodel-independent [`Value`], and the host-supplied registry.
3
4use alloc::boxed::Box;
5use alloc::string::String;
6use alloc::vec::Vec;
7
8use crate::ir::Chart;
9
10/// A datamodel-independent value, used for event data, snapshots and traces.
11#[derive(Clone, Debug, Default, PartialEq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub enum Value {
14 /// No value (`undefined` in ECMAScript).
15 #[default]
16 Undefined,
17 /// `null`.
18 Null,
19 /// A boolean.
20 Bool(bool),
21 /// A number.
22 Number(f64),
23 /// A string.
24 String(String),
25 /// An ordered array.
26 Array(Vec<Value>),
27 /// An object with keys in insertion order.
28 Object(Vec<(String, Value)>),
29}
30
31/// A failed datamodel operation. The interpreter turns it into `error.execution`.
32#[derive(Clone, Debug, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct EvalError {
35 /// A human-readable reason.
36 pub message: String,
37}
38
39impl EvalError {
40 /// An error with the given message.
41 pub fn new(message: impl Into<String>) -> Self {
42 EvalError {
43 message: message.into(),
44 }
45 }
46}
47
48impl core::fmt::Display for EvalError {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 f.write_str(&self.message)
51 }
52}
53
54impl core::error::Error for EvalError {}
55
56/// The `_event.type` field.
57#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
58#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
59pub enum EventType {
60 /// Raised by the platform (errors, `done.*`).
61 Platform,
62 /// Raised with `<raise>` or `<send target="#_internal">`.
63 Internal,
64 /// Received from outside the session.
65 #[default]
66 External,
67}
68
69/// An SCXML event as seen by the datamodel in `_event`.
70#[derive(Clone, Debug, Default, PartialEq)]
71#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
72pub struct Event {
73 /// `name`.
74 pub name: String,
75 /// `type`.
76 pub type_: EventType,
77 /// `sendid`.
78 pub sendid: Option<String>,
79 /// `origin`.
80 pub origin: Option<String>,
81 /// `origintype`.
82 pub origintype: Option<String>,
83 /// `originverified`: true only when the shell verified the sender's authentication against
84 /// its resolved DID document before the event entered the session, so `origin` is that
85 /// sender's DID. False for every other event, including runtime-assigned origins inside a
86 /// trusted cluster and anonymous senders. Absent in older journals and tapes, where it reads
87 /// as false.
88 #[cfg_attr(feature = "serde", serde(default))]
89 pub originverified: bool,
90 /// `invokeid`.
91 pub invokeid: Option<String>,
92 /// `data`.
93 pub data: Value,
94}
95
96impl Event {
97 /// An event with only a name and type.
98 pub fn named(name: impl Into<String>, type_: EventType) -> Self {
99 Event {
100 name: name.into(),
101 type_,
102 ..Event::default()
103 }
104 }
105
106 /// This event with `origin` set to a sender DID the shell verified, and the verified flag
107 /// set ([[openspec:did-identity#Authenticated Event Origin]]).
108 // @tg: implements:: [[openspec:did-identity#Authenticated Event Origin]]
109 pub fn with_verified_origin(mut self, sender: impl Into<String>) -> Self {
110 self.origin = Some(sender.into());
111 self.originverified = true;
112 self
113 }
114}
115
116/// Values for the read-only session system variables.
117#[derive(Clone, Debug, Default, PartialEq)]
118#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
119pub struct SessionInfo {
120 /// `_sessionid`.
121 pub sessionid: String,
122 /// `_name`.
123 pub name: Option<String>,
124 /// `_ioprocessors`.
125 pub ioprocessors: Value,
126}
127
128/// Host-supplied sources of nondeterminism for one input
129/// ([[openspec:datamodel#Deterministic Evaluation]]).
130#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
132pub struct Environment {
133 /// Current time in milliseconds since the Unix epoch, as recorded in the input.
134 pub now_ms: i64,
135 /// Seed for the datamodel's random number generator.
136 pub seed: u64,
137}
138
139/// Answers the `In(stateId)` predicate against the current configuration.
140pub trait StateQuery {
141 /// True exactly when the state with this identifier is active.
142 fn in_state(&self, id: &str) -> bool;
143
144 /// The identifiers of every active state in document order, when the caller can list
145 /// them. Datamodels that must answer `In()` from a callback they cannot borrow (such as an
146 /// embedded engine's native function) copy this before evaluating; `None` means only
147 /// [`StateQuery::in_state`] is available.
148 fn active_states(&self) -> Option<Vec<String>> {
149 None
150 }
151}
152
153/// The contract every datamodel implements. No operation may panic on bad input: failures are
154/// [`EvalError`]s, which the interpreter turns into `error.execution`.
155// @tg: implements:: [[openspec:datamodel#Datamodel Contract]]
156pub trait Datamodel {
157 /// The datamodel name as used in the `datamodel` attribute.
158 fn name(&self) -> &str;
159
160 /// Prepare a fresh session: set the system variables `_sessionid`, `_name` and
161 /// `_ioprocessors`. Called once before any other operation.
162 fn init_session(&mut self, info: &SessionInfo) -> Result<(), EvalError>;
163
164 /// Set the sources of nondeterminism for the input about to be processed.
165 fn set_environment(&mut self, env: &Environment);
166
167 /// Create a data item with an undefined value.
168 fn declare(&mut self, id: &str) -> Result<(), EvalError>;
169
170 /// Bind `_event` to the event currently being processed.
171 fn set_event(&mut self, event: &Event);
172
173 /// Whether `event`'s payload has the type the chart declares for it. A datamodel without
174 /// declared event types accepts every payload (the default). The interpreter does not call
175 /// this: a host calls it, through [`admit`], before it hands an event to the session.
176 fn check_event(&self, _event: &Event) -> Result<(), EvalError> {
177 Ok(())
178 }
179
180 /// Evaluate a condition. Must not change the datamodel ([[openspec:datamodel#Guard Purity]]).
181 fn eval_cond(&mut self, expr: &str, states: &dyn StateQuery) -> Result<bool, EvalError>;
182
183 /// Evaluate a value expression.
184 fn eval_value(&mut self, expr: &str, states: &dyn StateQuery) -> Result<Value, EvalError>;
185
186 /// Interpret inline content (of `<data>`, `<assign>`, `<content>`) as a value.
187 fn eval_content(&mut self, content: &str) -> Result<Value, EvalError>;
188
189 /// Assign the value of an expression to a location (`<assign location expr>`).
190 fn assign_expr(
191 &mut self,
192 location: &str,
193 expr: &str,
194 states: &dyn StateQuery,
195 ) -> Result<(), EvalError>;
196
197 /// Assign a value to a location. The location must already exist.
198 fn assign_value(&mut self, location: &str, value: Value) -> Result<(), EvalError>;
199
200 /// Bind a variable, declaring it when it does not exist (used by `foreach` item and index).
201 fn bind_var(&mut self, name: &str, value: Value) -> Result<(), EvalError>;
202
203 /// Whether `location` is a legal, existing location (used by `foreach` and `idlocation`).
204 fn is_location(&mut self, location: &str) -> bool;
205
206 /// Execute a script.
207 fn exec_script(&mut self, script: &str, states: &dyn StateQuery) -> Result<(), EvalError>;
208
209 /// Evaluate a `foreach` array expression to its items, shallow-copied, in index order.
210 fn foreach_items(
211 &mut self,
212 array: &str,
213 states: &dyn StateQuery,
214 ) -> Result<Vec<Value>, EvalError>;
215
216 /// Serialize the complete datamodel state ([[openspec:datamodel#Datamodel Snapshot]]).
217 fn snapshot(&self) -> Value;
218
219 /// Restore a state produced by [`Datamodel::snapshot`].
220 fn restore(&mut self, snapshot: &Value) -> Result<(), EvalError>;
221}
222
223/// Creates datamodels by name; supplied by the host
224/// ([[openspec:datamodel#Datamodel Registry]]).
225pub trait DatamodelRegistry {
226 /// A fresh datamodel instance for `name`, or `None` when it is not registered.
227 fn create(&self, name: &str) -> Option<Box<dyn Datamodel>>;
228
229 /// A fresh datamodel instance for `chart`, prepared from the whole chart: `None` when the
230 /// chart's datamodel is not registered, and `Some(Err(problems))` when the datamodel
231 /// refuses to instantiate the chart (a statically checked datamodel refuses a chart with
232 /// type errors). The default creates the datamodel by name and refuses nothing.
233 ///
234 /// Hosts create session datamodels through [`crate::null::create_datamodel_for`], which
235 /// calls this.
236 fn create_for(&self, chart: &Chart) -> Option<Result<Box<dyn Datamodel>, Vec<String>>> {
237 self.create(&chart.datamodel).map(Ok)
238 }
239
240 /// Every reason the chart's datamodel would refuse to instantiate `chart` (as
241 /// [`DatamodelRegistry::create_for`] would), without creating an instance; hosts call it
242 /// when a chart is registered. The default refuses nothing.
243 fn check(&self, _chart: &Chart) -> Result<(), Vec<String>> {
244 Ok(())
245 }
246}
247
248/// The event a host hands the interpreter for `event`: `event` itself when the datamodel
249/// accepts its payload ([`Datamodel::check_event`]), otherwise an `error.execution` platform
250/// event whose `message` names the event and the mismatch, so an event whose payload fails
251/// its declared type is never delivered.
252pub fn admit(dm: &dyn Datamodel, event: Event) -> Event {
253 match dm.check_event(&event) {
254 Ok(()) => event,
255 Err(e) => Event {
256 name: String::from("error.execution"),
257 type_: EventType::Platform,
258 sendid: event.sendid,
259 data: Value::Object(Vec::from([
260 (
261 String::from("kind"),
262 Value::String(String::from("execution")),
263 ),
264 (String::from("message"), Value::String(e.message)),
265 ])),
266 ..Event::default()
267 },
268 }
269}