1use std::time::Duration;
2
3use sim_citizen::value_from_expr;
4use sim_kernel::{
5 CapabilityName, Consistency, Cx, Diagnostic, Error, EvalMode, EvalReply, EvalRequest, Expr,
6 ObjectCompat, ReadPolicy, Result, Severity, Symbol, Value,
7};
8use sim_value::capability_names_from_expr;
9
10use crate::helpers::parse_optional_duration;
11use crate::{FrameKind, ServerFrame};
12
13pub fn server_frame_from_request(
18 cx: &mut Cx,
19 codec: &Symbol,
20 request: EvalRequest,
21) -> Result<ServerFrame> {
22 let expr = request.as_expr(cx)?;
23 let mut frame = ServerFrame::from_expr(
24 cx,
25 codec.clone(),
26 FrameKind::Request,
27 &expr,
28 request.consistency,
29 request.required_capabilities.clone(),
30 request.trace,
31 )?;
32 frame.envelope.deadline = request.deadline;
33 Ok(frame)
34}
35
36pub fn server_frame_from_reply(
41 cx: &mut Cx,
42 codec: &Symbol,
43 reply: EvalReply,
44 consistency: Consistency,
45) -> Result<ServerFrame> {
46 let expr = reply.as_expr(cx)?;
47 let mut frame = ServerFrame::from_expr(
48 cx,
49 codec.clone(),
50 FrameKind::Response,
51 &expr,
52 consistency,
53 Vec::new(),
54 reply.trace.is_some(),
55 )?;
56 if let Some(trace) = reply.trace {
57 frame.envelope.trace = !matches!(trace.object().as_expr(cx)?, Expr::Nil);
58 }
59 Ok(frame)
60}
61
62pub fn eval_request_from_frame(cx: &mut Cx, frame: &ServerFrame) -> Result<EvalRequest> {
66 if frame.kind != FrameKind::Request {
67 return Err(Error::Eval(format!(
68 "expected request frame, found {}",
69 frame.kind.as_symbol()
70 )));
71 }
72 let expr = frame.decode_expr(cx, ReadPolicy::default())?;
73 eval_request_from_expr(cx, expr)
74}
75
76pub fn eval_reply_from_frame(cx: &mut Cx, frame: &ServerFrame) -> Result<EvalReply> {
80 if frame.kind == FrameKind::Error {
81 let detail = match frame.decode_expr(cx, ReadPolicy::default())? {
82 Expr::String(detail) => detail,
83 detail => format!("{detail:?}"),
84 };
85 return Err(Error::Eval(format!("remote evaluation failed: {detail}")));
86 }
87 if frame.kind != FrameKind::Response {
88 return Err(Error::Eval(format!(
89 "expected response frame, found {}",
90 frame.kind.as_symbol()
91 )));
92 }
93 let expr = frame.decode_expr(cx, ReadPolicy::default())?;
94 eval_reply_from_expr(cx, expr)
95}
96
97fn eval_request_from_expr(cx: &mut Cx, expr: Expr) -> Result<EvalRequest> {
98 let request_expr = required_table_field(&expr, "expr")?.clone();
99 let result_shape = parse_result_shape_expr(cx, required_table_field(&expr, "result-shape")?)?;
100 let required_capabilities = parse_capability_expr(required_table_field(&expr, "requires")?)?;
101 let deadline = parse_deadline_expr(required_table_field(&expr, "deadline")?)?;
102 let consistency = parse_consistency_expr(required_table_field(&expr, "consistency")?)?;
103 let mode = optional_table_value(&expr, "mode")
104 .map(parse_mode_expr)
105 .transpose()?
106 .unwrap_or(EvalMode::Eval);
107 let answer_limit = optional_table_value(&expr, "answer-limit")
108 .map(parse_optional_usize_expr)
109 .transpose()?
110 .flatten();
111 let stream_buffer = optional_table_value(&expr, "stream-buffer")
112 .map(parse_optional_usize_expr)
113 .transpose()?
114 .flatten();
115 let stream = optional_table_value(&expr, "stream")
116 .map(parse_bool_expr)
117 .transpose()?
118 .unwrap_or(false);
119 let trace = parse_bool_expr(required_table_field(&expr, "trace")?)?;
120 Ok(EvalRequest {
121 expr: request_expr,
122 result_shape,
123 required_capabilities,
124 deadline,
125 consistency,
126 mode,
127 answer_limit,
128 stream_buffer,
129 stream,
130 trace,
131 })
132}
133
134fn eval_reply_from_expr(cx: &mut Cx, expr: Expr) -> Result<EvalReply> {
135 let value = value_from_expr(cx, required_table_field(&expr, "value")?)?;
136 let diagnostics = parse_diagnostics_expr(required_table_field(&expr, "diagnostics")?)?;
137 let trace = parse_optional_value_expr(cx, required_table_field(&expr, "trace")?)?;
138 Ok(EvalReply {
139 value,
140 diagnostics,
141 trace,
142 })
143}
144
145fn required_table_field<'a>(expr: &'a Expr, key: &str) -> Result<&'a Expr> {
146 let Expr::Map(entries) = expr else {
147 return Err(Error::TypeMismatch {
148 expected: "table expression",
149 found: "non-table",
150 });
151 };
152 entries
153 .iter()
154 .find_map(|(entry_key, entry_value)| match entry_key {
155 Expr::Symbol(symbol) if symbol.name.as_ref() == key => Some(entry_value),
156 _ => None,
157 })
158 .ok_or_else(|| Error::Eval(format!("missing frame field {key}")))
159}
160
161fn optional_table_value<'a>(expr: &'a Expr, key: &str) -> Option<&'a Expr> {
162 let Expr::Map(entries) = expr else {
163 return None;
164 };
165 entries
166 .iter()
167 .find_map(|(entry_key, entry_value)| match entry_key {
168 Expr::Symbol(symbol) if symbol.name.as_ref() == key => Some(entry_value),
169 _ => None,
170 })
171}
172
173fn parse_result_shape_expr(cx: &mut Cx, expr: &Expr) -> Result<Option<sim_kernel::ShapeRef>> {
174 if matches!(expr, Expr::Nil) {
175 return Ok(None);
176 }
177 if let Expr::Symbol(symbol) = expr {
178 if let Ok(shape) = cx.resolve_shape(symbol) {
179 return Ok(Some(shape));
180 }
181 if symbol.name.as_ref() == "instance-shape"
182 && let Some(namespace) = &symbol.namespace
183 {
184 let class_symbol = parse_qualified_symbol(namespace);
185 if let Ok(class_value) = cx.resolve_class(&class_symbol)
186 && let Some(class) = class_value.object().as_class()
187 {
188 return Ok(Some(class.instance_shape(cx)?));
189 }
190 }
191 }
192 let value = cx.eval_expr(expr.clone())?;
193 if let Some(class) = value.object().as_class() {
194 return Ok(Some(class.instance_shape(cx)?));
195 }
196 Err(Error::TypeMismatch {
197 expected: "shape or class",
198 found: "non-shape",
199 })
200}
201
202fn parse_qualified_symbol(text: &str) -> Symbol {
203 match text.rsplit_once('/') {
204 Some((namespace, name)) => Symbol::qualified(namespace.to_owned(), name.to_owned()),
205 None => Symbol::new(text.to_owned()),
206 }
207}
208
209fn parse_capability_expr(expr: &Expr) -> Result<Vec<CapabilityName>> {
210 capability_names_from_expr(expr)
211}
212
213fn parse_deadline_expr(expr: &Expr) -> Result<Option<Duration>> {
214 parse_optional_duration(expr)
215}
216
217fn parse_consistency_expr(expr: &Expr) -> Result<Consistency> {
218 let name = match expr {
219 Expr::Symbol(symbol) => symbol.to_string(),
220 Expr::String(text) => text.clone(),
221 _ => {
222 return Err(Error::TypeMismatch {
223 expected: "consistency symbol or string",
224 found: "non-consistency",
225 });
226 }
227 };
228 match name.as_str() {
229 "local-only" => Ok(Consistency::LocalOnly),
230 "local-first" => Ok(Consistency::LocalFirst),
231 "remote-only" => Ok(Consistency::RemoteOnly),
232 _ => Err(Error::Eval(format!(
233 "unsupported realize consistency {name}"
234 ))),
235 }
236}
237
238fn parse_mode_expr(expr: &Expr) -> Result<EvalMode> {
239 let name = match expr {
240 Expr::Symbol(symbol) => symbol.to_string(),
241 Expr::String(text) => text.clone(),
242 _ => {
243 return Err(Error::TypeMismatch {
244 expected: "mode symbol or string",
245 found: "non-mode",
246 });
247 }
248 };
249 match name.as_str() {
250 "eval" => Ok(EvalMode::Eval),
251 "logic" => Ok(EvalMode::Logic),
252 _ => Err(Error::Eval(format!("unsupported realize mode {name}"))),
253 }
254}
255
256fn parse_optional_usize_expr(expr: &Expr) -> Result<Option<usize>> {
257 match expr {
258 Expr::Nil => Ok(None),
259 Expr::Number(number) => number
260 .canonical
261 .parse::<usize>()
262 .map(Some)
263 .map_err(|_| Error::Eval(format!("expected usize, found {}", number.canonical))),
264 Expr::String(text) => text
265 .parse::<usize>()
266 .map(Some)
267 .map_err(|_| Error::Eval(format!("expected usize, found {text}"))),
268 _ => Err(Error::TypeMismatch {
269 expected: "usize or nil",
270 found: "non-usize",
271 }),
272 }
273}
274
275fn parse_bool_expr(expr: &Expr) -> Result<bool> {
276 match expr {
277 Expr::Bool(value) => Ok(*value),
278 _ => Err(Error::TypeMismatch {
279 expected: "bool",
280 found: "non-bool",
281 }),
282 }
283}
284
285fn parse_diagnostics_expr(expr: &Expr) -> Result<Vec<Diagnostic>> {
286 match expr {
287 Expr::Nil => Ok(Vec::new()),
288 Expr::List(items) | Expr::Vector(items) => {
289 items.iter().map(parse_diagnostic_expr).collect()
290 }
291 _ => Err(Error::TypeMismatch {
292 expected: "diagnostic list",
293 found: "non-list",
294 }),
295 }
296}
297
298fn parse_diagnostic_expr(expr: &Expr) -> Result<Diagnostic> {
299 let severity = match required_table_field(expr, "severity")? {
300 Expr::Symbol(symbol) if symbol.name.as_ref() == "error" => Severity::Error,
301 Expr::Symbol(symbol) if symbol.name.as_ref() == "warning" => Severity::Warning,
302 Expr::Symbol(symbol) if symbol.name.as_ref() == "info" => Severity::Info,
303 Expr::Symbol(symbol) if symbol.name.as_ref() == "note" => Severity::Note,
304 _ => {
305 return Err(Error::TypeMismatch {
306 expected: "diagnostic severity symbol",
307 found: "non-severity",
308 });
309 }
310 };
311 let message = match required_table_field(expr, "message")? {
312 Expr::String(text) => text.clone(),
313 _ => {
314 return Err(Error::TypeMismatch {
315 expected: "diagnostic message string",
316 found: "non-string",
317 });
318 }
319 };
320 let code = match required_table_field(expr, "code")? {
321 Expr::Nil => None,
322 Expr::Symbol(symbol) => Some(symbol.clone()),
323 _ => {
324 return Err(Error::TypeMismatch {
325 expected: "diagnostic code symbol",
326 found: "non-symbol",
327 });
328 }
329 };
330 let related = parse_diagnostics_expr(required_table_field(expr, "related")?)?;
331 Ok(Diagnostic {
332 severity,
333 message,
334 source: None,
335 span: None,
336 code,
337 related,
338 })
339}
340
341fn parse_optional_value_expr(cx: &mut Cx, expr: &Expr) -> Result<Option<Value>> {
342 if matches!(expr, Expr::Nil) {
343 return Ok(None);
344 }
345 value_from_expr(cx, expr).map(Some)
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351
352 #[test]
353 fn remote_error_frames_preserve_the_server_diagnostic() {
354 let mut cx = crate::tests::cx();
355 let frame = ServerFrame::from_expr(
356 &mut cx,
357 Symbol::qualified("codec", "binary"),
358 FrameKind::Error,
359 &Expr::String("unknown symbol shared".to_owned()),
360 Consistency::RemoteOnly,
361 Vec::new(),
362 false,
363 )
364 .unwrap();
365
366 assert!(matches!(
367 eval_reply_from_frame(&mut cx, &frame),
368 Err(Error::Eval(message))
369 if message == "remote evaluation failed: unknown symbol shared"
370 ));
371 }
372}