Skip to main content

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}