1use std::collections::BTreeMap;
16use std::sync::Arc;
17
18use sim_kernel::{
19 Consistency, Cx, DefaultFactory, EagerPolicy, Error, EvalFabricRef, EvalMode, EvalRequest,
20 Expr, Result, Symbol,
21};
22use sim_lib_stream_core::{PushResult, StreamEnvelope, StreamItem, StreamStats};
23use sim_lib_view::Operation;
24
25use crate::transport::{
26 ChangeEvent, SessionStatus, StreamInspectorRecord, Transport, TransportKind,
27};
28
29pub struct FabricTransport {
36 fabric: EvalFabricRef,
37 cx: Cx,
38 store: BTreeMap<Symbol, Expr>,
39 events: Vec<ChangeEvent>,
40 status: SessionStatus,
41}
42
43impl FabricTransport {
44 pub fn new(fabric: EvalFabricRef) -> Self {
46 Self {
47 fabric,
48 cx: Cx::new(Arc::new(EagerPolicy), Arc::new(DefaultFactory)),
49 store: BTreeMap::new(),
50 events: Vec::new(),
51 status: SessionStatus::Connected,
52 }
53 }
54
55 pub fn with(mut self, resource: Symbol, value: Expr) -> Self {
57 self.store.insert(resource, value);
58 self
59 }
60
61 pub fn set(&mut self, resource: Symbol, value: Expr) {
63 self.store.insert(resource, value);
64 }
65
66 fn no_streams(&self) -> Error {
67 Error::HostError("fabric transport does not provide streams".to_owned())
68 }
69}
70
71impl Transport for FabricTransport {
72 fn kind(&self) -> TransportKind {
73 TransportKind::Fabric
74 }
75
76 fn status(&self) -> SessionStatus {
77 self.status
78 }
79
80 fn read(&self, resource: &Symbol) -> Result<Expr> {
81 self.store
82 .get(resource)
83 .cloned()
84 .ok_or_else(|| Error::UnknownSymbol {
85 symbol: resource.clone(),
86 })
87 }
88
89 fn realize_operation(&mut self, resource: &Symbol, operation: &Operation) -> Result<Expr> {
90 let request = operation_to_request(operation);
91 let reply = self.fabric.realize(&mut self.cx, request)?;
92 let new_value = reply.value.object().as_expr(&mut self.cx)?;
93 validate_reply_shape(&mut self.cx, operation, &new_value)?;
94 self.store.insert(resource.clone(), new_value.clone());
95 self.events.push(ChangeEvent {
96 resource: resource.clone(),
97 });
98 Ok(new_value)
99 }
100
101 fn drain_events(&mut self) -> Vec<ChangeEvent> {
102 std::mem::take(&mut self.events)
103 }
104
105 fn stream_subscribe(&mut self, _stream_id: &Symbol) -> Result<StreamInspectorRecord> {
106 Err(self.no_streams())
107 }
108
109 fn stream_read(&mut self, _stream_id: &Symbol, _limit: usize) -> Result<Vec<StreamItem>> {
110 Err(self.no_streams())
111 }
112
113 fn stream_push(
114 &mut self,
115 _stream_id: &Symbol,
116 _envelope: StreamEnvelope,
117 ) -> Result<PushResult> {
118 Err(self.no_streams())
119 }
120
121 fn stream_cancel(&mut self, _stream_id: &Symbol) -> Result<()> {
122 Err(self.no_streams())
123 }
124
125 fn stream_stats(&self, _stream_id: &Symbol) -> Result<StreamStats> {
126 Err(self.no_streams())
127 }
128
129 fn stream_inspector(&self, _stream_id: &Symbol) -> Result<StreamInspectorRecord> {
130 Err(self.no_streams())
131 }
132}
133
134pub fn operation_to_request(operation: &Operation) -> EvalRequest {
142 EvalRequest {
143 expr: operation.form.clone(),
144 result_shape: operation.result_shape.clone(),
145 required_capabilities: operation.required_capabilities.clone(),
146 deadline: None,
147 consistency: Consistency::LocalFirst,
148 mode: EvalMode::Eval,
149 answer_limit: None,
150 stream_buffer: None,
151 stream: false,
152 trace: false,
153 }
154}
155
156fn validate_reply_shape(cx: &mut Cx, operation: &Operation, value: &Expr) -> Result<()> {
157 let Some(shape_value) = &operation.result_shape else {
158 return Ok(());
159 };
160 let Some(shape) = shape_value.object().as_shape() else {
161 return Err(Error::HostError(
162 "operation result_shape is not a Shape".to_owned(),
163 ));
164 };
165 let matched = shape.check_expr(cx, value)?;
166 if matched.accepted {
167 Ok(())
168 } else {
169 Err(Error::HostError(
170 "fabric reply failed operation result_shape".to_owned(),
171 ))
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use std::sync::Arc;
178
179 use sim_kernel::{
180 CapabilityName, Cx, Error, EvalFabric, EvalReply, EvalRequest, Expr, ExprKind,
181 NumberLiteral, Result, Symbol,
182 };
183 use sim_lib_intent::{Origin, intent};
184 use sim_lib_view::{
185 LensRegistry, Operation, UNIVERSAL_EDITOR_ID, UNIVERSAL_VIEW_ID, register_universal_default,
186 };
187 use sim_shape::{ExprKindShape, shape_value};
188
189 use super::{FabricTransport, operation_to_request};
190 use crate::session::Session;
191 use crate::transport::{Transport, TransportKind};
192
193 struct SetValueFabric;
196
197 impl EvalFabric for SetValueFabric {
198 fn realize(&self, cx: &mut Cx, request: EvalRequest) -> Result<EvalReply> {
199 let Expr::Map(entries) = &request.expr else {
200 return Err(Error::HostError("operation is not a map".to_owned()));
201 };
202 let value_expr = sim_value::access::entry_field(entries, "value").ok_or_else(|| {
203 Error::HostError("set-value operation is missing a 'value'".to_owned())
204 })?;
205 Ok(EvalReply {
206 value: cx.factory().expr(value_expr.clone())?,
207 diagnostics: Vec::new(),
208 trace: None,
209 })
210 }
211 }
212
213 struct StringReplyFabric;
214
215 impl EvalFabric for StringReplyFabric {
216 fn realize(&self, cx: &mut Cx, _request: EvalRequest) -> Result<EvalReply> {
217 Ok(EvalReply {
218 value: cx.factory().expr(Expr::String("wrong shape".to_owned()))?,
219 diagnostics: Vec::new(),
220 trace: None,
221 })
222 }
223 }
224
225 use sim_kernel::testing::eager_cx as cx;
226
227 fn registry() -> LensRegistry {
228 let mut registry = LensRegistry::new();
229 register_universal_default(&mut registry, false);
230 registry
231 }
232
233 use sim_value::build::keyword as sym;
234
235 fn number(value: &str) -> Expr {
236 Expr::Number(NumberLiteral {
237 domain: sym("i64"),
238 canonical: value.to_owned(),
239 })
240 }
241
242 fn doc() -> Expr {
243 Expr::Map(vec![
244 (Expr::Symbol(sym("a")), number("1")),
245 (Expr::Symbol(sym("b")), number("2")),
246 ])
247 }
248
249 fn edit_a_to_9() -> Expr {
250 intent(
251 "edit-field",
252 Origin::human(1),
253 vec![
254 ("target", doc()),
255 (
256 "path",
257 Expr::List(vec![Expr::Vector(vec![
258 Expr::Symbol(sym("k")),
259 Expr::Symbol(sym("a")),
260 ])]),
261 ),
262 ("value", number("9")),
263 ],
264 )
265 }
266
267 fn set_value_op(value: Expr) -> Expr {
268 Expr::Map(vec![
269 (Expr::Symbol(sym("op")), Expr::Symbol(sym("set-value"))),
270 (Expr::Symbol(sym("value")), value),
271 ])
272 }
273
274 fn number_shape() -> sim_kernel::ShapeRef {
275 shape_value(
276 Symbol::qualified("core", "Number"),
277 Arc::new(ExprKindShape::new(ExprKind::Number)),
278 )
279 }
280
281 #[test]
282 fn session_commits_an_edit_through_the_fabric_and_the_scene_diff_reconstructs() {
283 let mut cx = cx();
284 let registry = registry();
285 let transport = FabricTransport::new(Arc::new(SetValueFabric)).with(sym("doc"), doc());
286 let mut session = Session::new(transport);
287
288 let initial = session
289 .open(
290 &mut cx,
291 ®istry,
292 sym("pane-1"),
293 sym("doc"),
294 sym(UNIVERSAL_VIEW_ID),
295 sym(UNIVERSAL_EDITOR_ID),
296 )
297 .unwrap();
298 sim_lib_scene::validate_scene(&initial).expect("initial scene is valid");
299
300 session
302 .submit_intent(&mut cx, ®istry, &sym("pane-1"), &edit_a_to_9())
303 .unwrap();
304
305 let value = session.transport_mut().read(&sym("doc")).unwrap();
307 assert_eq!(sim_value::access::field(&value, "a"), Some(&number("9")));
308
309 let updates = session.pump(&mut cx, ®istry).unwrap();
311 assert_eq!(updates.len(), 1, "exactly the subscribed pane updates");
312 let update = &updates[0];
313 assert_eq!(update.pane, sym("pane-1"));
314 assert_ne!(update.scene, initial, "the Scene changed");
315 let rebuilt = sim_lib_scene::apply(&initial, &update.diff).unwrap();
316 assert_eq!(rebuilt, update.scene, "the diff reconstructs the new Scene");
317 }
318
319 #[test]
320 fn direct_realize_returns_the_new_value_and_records_one_event() {
321 let mut transport = FabricTransport::new(Arc::new(SetValueFabric));
322 assert_eq!(transport.kind(), TransportKind::Fabric);
323
324 let new_value = transport
325 .realize(&sym("x"), &set_value_op(number("42")))
326 .unwrap();
327 assert_eq!(new_value, number("42"));
328 assert_eq!(transport.read(&sym("x")).unwrap(), number("42"));
329
330 let events = transport.drain_events();
331 assert_eq!(events.len(), 1);
332 assert_eq!(events[0].resource, sym("x"));
333 assert!(transport.drain_events().is_empty());
334 }
335
336 #[test]
337 fn operation_to_request_preserves_shape_and_capability_requirements() {
338 let operation = Operation::new(set_value_op(number("42")))
339 .with_result_shape(number_shape())
340 .requiring(CapabilityName::new("web.write"));
341
342 let request = operation_to_request(&operation);
343
344 assert_eq!(request.expr, operation.form);
345 assert!(request.result_shape.is_some());
346 assert_eq!(
347 request
348 .required_capabilities
349 .iter()
350 .map(|capability| capability.as_str())
351 .collect::<Vec<_>>(),
352 vec!["web.write"]
353 );
354 }
355
356 #[test]
357 fn fabric_reply_must_match_the_operation_result_shape_before_storage_changes() {
358 let mut transport =
359 FabricTransport::new(Arc::new(StringReplyFabric)).with(sym("doc"), number("1"));
360 let operation = Operation::new(set_value_op(number("2"))).with_result_shape(number_shape());
361
362 let err = transport
363 .realize_operation(&sym("doc"), &operation)
364 .unwrap_err();
365
366 assert!(
367 err.to_string()
368 .contains("fabric reply failed operation result_shape"),
369 "unexpected error: {err}"
370 );
371 assert_eq!(transport.read(&sym("doc")).unwrap(), number("1"));
372 assert!(transport.drain_events().is_empty());
373 }
374}