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
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}