Skip to main content

sim_lib_view/
render.rs

1//! Lens render and propose helpers.
2//!
3//! These tie the contracts to the scene and intent domains: a view's output
4//! must validate as a Scene, and an editor only sees an Intent that has already
5//! passed `codec:intent` validation. Both fail closed: an invalid scene or
6//! intent is a diagnostic, never a silent partial result.
7
8use sim_kernel::{Cx, Error, Expr, Result, Symbol};
9
10use crate::contract::{Draft, Operation};
11use crate::dispatch::LensRegistry;
12
13impl LensRegistry {
14    /// Render `value` through the named view lens, validating the emitted Scene.
15    pub fn render(&self, cx: &mut Cx, lens_id: &Symbol, value: &Expr) -> Result<Expr> {
16        let lens = self
17            .get(lens_id)
18            .ok_or_else(|| Error::HostError(format!("unknown lens {lens_id}")))?;
19        let view = lens
20            .view
21            .as_ref()
22            .ok_or_else(|| Error::HostError(format!("lens {lens_id} is not a view")))?;
23        let scene = view.encode(cx, value)?;
24        sim_lib_scene::validate_scene(&scene).map_err(|error| {
25            Error::HostError(format!("view {lens_id} produced an invalid scene: {error}"))
26        })?;
27        Ok(scene)
28    }
29
30    /// Fold an Intent into a draft through the named editor lens, after the
31    /// Intent passes structural validation.
32    pub fn propose(
33        &self,
34        cx: &mut Cx,
35        lens_id: &Symbol,
36        value: &Expr,
37        intent: &Expr,
38    ) -> Result<Draft> {
39        sim_lib_intent::validate_intent(intent)
40            .map_err(|error| Error::HostError(format!("invalid intent: {error}")))?;
41        let editor = self.editor_of(lens_id)?;
42        editor.decode(cx, value, intent)
43    }
44
45    /// Commit a committable draft through the named editor lens.
46    pub fn commit(&self, cx: &mut Cx, lens_id: &Symbol, draft: &Draft) -> Result<Operation> {
47        if !draft.committable {
48            return Err(Error::HostError(format!(
49                "draft is not committable ({} diagnostic(s))",
50                draft.diagnostics.len()
51            )));
52        }
53        let lens = self
54            .get(lens_id)
55            .ok_or_else(|| Error::HostError(format!("unknown lens {lens_id}")))?;
56        let editor = lens
57            .editor
58            .as_ref()
59            .ok_or_else(|| Error::HostError(format!("lens {lens_id} is not an editor")))?;
60        let mut operation = editor.commit(cx, draft)?;
61        for capability in &lens.meta.required_capabilities {
62            if !operation
63                .required_capabilities
64                .iter()
65                .any(|required| required == capability)
66            {
67                operation.required_capabilities.push(capability.clone());
68            }
69        }
70        Ok(operation)
71    }
72
73    fn editor_of(&self, lens_id: &Symbol) -> Result<std::sync::Arc<dyn crate::contract::Editor>> {
74        let lens = self
75            .get(lens_id)
76            .ok_or_else(|| Error::HostError(format!("unknown lens {lens_id}")))?;
77        lens.editor
78            .clone()
79            .ok_or_else(|| Error::HostError(format!("lens {lens_id} is not an editor")))
80    }
81}