Skip to main content

determa_state/
loader.rs

1//! Loading Determa State YAML: multi-document machine files (§9: first doc is the root)
2//! and contract files (§7).
3
4use crate::model::RawMachine;
5use crate::validate::Contract;
6use serde::Deserialize;
7
8/// A validation error with a structural path.
9#[derive(Debug, Clone, Default)]
10pub struct LoadError {
11    pub path: String,
12    pub message: String,
13}
14
15impl std::fmt::Display for LoadError {
16    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17        if self.path.is_empty() {
18            write!(f, "{}", self.message)
19        } else {
20            write!(f, "{}: {}", self.path, self.message)
21        }
22    }
23}
24
25/// Parse one or more `---`-separated machine definitions from YAML text.
26pub fn load_machines(src: &str) -> Result<Vec<RawMachine>, Vec<LoadError>> {
27    let de = serde_yaml::Deserializer::from_str(src);
28    let mut out = Vec::new();
29    let mut errs = Vec::new();
30    let mut doc_index = 0;
31    for doc in de {
32        match RawMachine::deserialize(doc) {
33            Ok(m) => out.push(m),
34            Err(e) => {
35                let pos = e.location().map(|l| l.line()).unwrap_or(0);
36                let path = if out.is_empty() {
37                    format!("doc[{}]", doc_index)
38                } else {
39                    format!("doc[{}]", doc_index)
40                };
41                errs.push(LoadError {
42                    path: format!("{path}:line {pos}"),
43                    message: format!("{e}"),
44                });
45            }
46        }
47        doc_index += 1;
48    }
49    if !errs.is_empty() {
50        return Err(errs);
51    }
52    if out.is_empty() {
53        return Err(vec![LoadError {
54            path: "machine".to_string(),
55            message: "no machine definitions found".to_string(),
56        }]);
57    }
58    Ok(out)
59}
60
61/// Parse a single machine definition from a pre-parsed JSON value — no YAML text.
62///
63/// A host can build a machine in code (e.g. with the `serde_json::json!` macro)
64/// and load it through the same deserialization path as `load_machines`, without
65/// serializing to a YAML string first. The first document of a multi-document
66/// machine is the root (SPEC §9).
67pub fn load_machine_from_value(value: serde_json::Value) -> Result<RawMachine, LoadError> {
68    serde_json::from_value(value).map_err(|e| LoadError {
69        path: "machine".to_string(),
70        message: format!("{e}"),
71    })
72}
73
74/// Parse machine definitions from pre-parsed JSON values (one per document).
75///
76/// The host supplies each document already split; the first is the root (SPEC §9).
77/// This is the value-based counterpart of `load_machines`.
78pub fn load_machines_from_values(
79    values: Vec<serde_json::Value>,
80) -> Result<Vec<RawMachine>, Vec<LoadError>> {
81    let mut out = Vec::new();
82    let mut errs = Vec::new();
83    for (i, value) in values.into_iter().enumerate() {
84        match serde_json::from_value::<RawMachine>(value) {
85            Ok(m) => out.push(m),
86            Err(e) => errs.push(LoadError {
87                path: format!("doc[{i}]"),
88                message: format!("{e}"),
89            }),
90        }
91    }
92    if !errs.is_empty() {
93        return Err(errs);
94    }
95    if out.is_empty() {
96        return Err(vec![LoadError {
97            path: "machine".to_string(),
98            message: "no machine definitions found".to_string(),
99        }]);
100    }
101    Ok(out)
102}
103
104/// Parse a single contract file.
105pub fn load_contract(src: &str) -> Result<Contract, LoadError> {
106    let de = serde_yaml::Deserializer::from_str(src);
107    Contract::deserialize(de).map_err(|e| LoadError {
108        path: "contract".to_string(),
109        message: format!("{e}"),
110    })
111}
112
113pub fn load_contracts(srcs: &[(&str, &str)]) -> Result<Vec<Contract>, LoadError> {
114    let mut out = Vec::new();
115    for (_name, src) in srcs {
116        out.push(load_contract(src)?);
117    }
118    Ok(out)
119}