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