1use lex_bytecode::vm::{EffectHandler, Vm};
8use lex_bytecode::{Program, Value};
9use std::path::PathBuf;
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Mutex, OnceLock};
12use std::sync::Arc;
13use std::time::{SystemTime, UNIX_EPOCH};
14
15use crate::builtins::{call_pure_builtin, is_pure_call};
16use crate::policy::Policy;
17
18pub trait IoSink: Send {
21 fn print_line(&mut self, s: &str);
22}
23
24pub struct StdoutSink;
25impl IoSink for StdoutSink {
26 fn print_line(&mut self, s: &str) {
27 use std::io::Write;
28 println!("{s}");
29 let _ = std::io::stdout().flush();
30 }
31}
32
33#[derive(Default)]
34pub struct CapturedSink { pub lines: Vec<String> }
35impl IoSink for CapturedSink {
36 fn print_line(&mut self, s: &str) { self.lines.push(s.to_string()); }
37}
38
39pub type StreamRegistry =
42 std::collections::HashMap<String, Box<dyn Iterator<Item = String> + Send>>;
43
44pub struct DefaultHandler {
45 policy: Policy,
46 pub sink: Box<dyn IoSink>,
47 pub read_root: Option<PathBuf>,
50 pub budget_remaining: Arc<AtomicU64>,
57 pub budget_ceiling: Option<u64>,
61 pub program: Option<Arc<Program>>,
65 pub chat_registry: Option<Arc<crate::ws::ChatRegistry>>,
69 pub mcp_clients: crate::mcp_client::McpClientCache,
75 pub streams: Arc<std::sync::Mutex<StreamRegistry>>,
82 pub next_stream_id: Arc<std::sync::atomic::AtomicU64>,
84 arena_stack: Vec<(u64, crate::arena::Arena)>,
97 next_scope_id: u64,
102 pub program_args: Vec<String>,
105}
106
107impl DefaultHandler {
108 pub fn new(policy: Policy) -> Self {
109 let ceiling = policy.budget;
113 let initial = ceiling.unwrap_or(u64::MAX);
114 Self {
115 policy,
116 sink: Box::new(StdoutSink),
117 read_root: None,
118 budget_remaining: Arc::new(AtomicU64::new(initial)),
119 budget_ceiling: ceiling,
120 program: None,
121 chat_registry: None,
122 mcp_clients: crate::mcp_client::McpClientCache::with_capacity(16),
123 streams: Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
124 next_stream_id: Arc::new(std::sync::atomic::AtomicU64::new(0)),
125 arena_stack: Vec::new(),
126 next_scope_id: 1,
127 program_args: Vec::new(),
128 }
129 }
130
131 pub fn active_arena(&self) -> Option<&crate::arena::Arena> {
137 self.arena_stack.last().map(|(_, a)| a)
138 }
139
140 pub fn arena_stack_depth(&self) -> usize {
143 self.arena_stack.len()
144 }
145
146 pub fn with_program(mut self, program: Arc<Program>) -> Self {
147 self.program = Some(program); self
148 }
149
150 pub fn with_chat_registry(mut self, registry: Arc<crate::ws::ChatRegistry>) -> Self {
151 self.chat_registry = Some(registry); self
152 }
153
154 pub fn with_sink(mut self, sink: Box<dyn IoSink>) -> Self {
155 self.sink = sink; self
156 }
157
158 pub fn with_read_root(mut self, root: PathBuf) -> Self {
159 self.read_root = Some(root); self
160 }
161
162 pub fn with_program_args(mut self, args: Vec<String>) -> Self {
163 self.program_args = args; self
164 }
165
166 fn ensure_kind_allowed(&self, kind: &str) -> Result<(), String> {
167 if self.policy.allow_effects.contains(kind) {
168 Ok(())
169 } else {
170 Err(format!("effect `{kind}` not in --allow-effects"))
171 }
172 }
173
174 fn resolve_read_path(&self, p: &str) -> PathBuf {
175 match &self.read_root {
176 Some(root) => root.join(p.trim_start_matches('/')),
177 None => PathBuf::from(p),
178 }
179 }
180
181 fn dispatch_log(&mut self, op: &str, args: Vec<Value>) -> Result<Value, String> {
182 match op {
183 "debug" | "info" | "warn" | "error" => {
184 let msg = expect_str(args.first())?;
185 let level = match op {
186 "debug" => LogLevel::Debug,
187 "info" => LogLevel::Info,
188 "warn" => LogLevel::Warn,
189 _ => LogLevel::Error,
190 };
191 emit_log(level, msg);
192 Ok(Value::Unit)
193 }
194 "set_level" => {
195 let s = expect_str(args.first())?;
196 match parse_log_level(s) {
197 Some(l) => {
198 log_state().lock().unwrap().level = l;
199 Ok(ok(Value::Unit))
200 }
201 None => Ok(err(Value::Str(format!(
202 "log.set_level: unknown level `{s}`; expected debug|info|warn|error").into()))),
203 }
204 }
205 "set_format" => {
206 let s = expect_str(args.first())?;
207 let fmt = match s {
208 "text" => LogFormat::Text,
209 "json" => LogFormat::Json,
210 other => return Ok(err(Value::Str(format!(
211 "log.set_format: unknown format `{other}`; expected text|json").into()))),
212 };
213 log_state().lock().unwrap().format = fmt;
214 Ok(ok(Value::Unit))
215 }
216 "set_sink" => {
217 let path = expect_str(args.first())?;
218 if path == "-" {
219 log_state().lock().unwrap().sink = LogSink::Stderr;
220 return Ok(ok(Value::Unit));
221 }
222 if let Err(e) = self.ensure_fs_write_path(path) {
223 return Ok(err(Value::Str(e.into())));
224 }
225 match std::fs::OpenOptions::new()
226 .create(true).append(true).open(path)
227 {
228 Ok(f) => {
229 log_state().lock().unwrap().sink = LogSink::File(std::sync::Arc::new(Mutex::new(f)));
230 Ok(ok(Value::Unit))
231 }
232 Err(e) => Ok(err(Value::Str(format!("log.set_sink `{path}`: {e}").into()))),
233 }
234 }
235 other => Err(format!("unsupported log.{other}")),
236 }
237 }
238
239 fn dispatch_process(&mut self, op: &str, args: Vec<Value>) -> Result<Value, String> {
240 match op {
241 "spawn" => {
242 let cmd = expect_str(args.first())?.to_string();
243 let raw_args = match args.get(1) {
244 Some(Value::List(items)) => items.clone(),
245 _ => return Err("process.spawn: args must be List[Str]".into()),
246 };
247 let str_args: Result<Vec<String>, String> = raw_args.iter().map(|v| match v {
248 Value::Str(s) => Ok(s.to_string()),
249 other => Err(format!("process.spawn: arg must be Str, got {other:?}")),
250 }).collect();
251 let str_args = str_args?;
252 let opts = match args.get(2) {
253 Some(Value::Record { fields: r, .. }) => r.clone(),
254 _ => return Err("process.spawn: missing or invalid opts record".into()),
255 };
256
257 if !self.policy.allow_proc.is_empty() {
259 let basename = std::path::Path::new(&cmd)
260 .file_name()
261 .and_then(|s| s.to_str())
262 .unwrap_or(&cmd);
263 if !self.policy.allow_proc.iter().any(|a| a == basename) {
264 return Ok(err(Value::Str(format!(
265 "process.spawn: `{cmd}` not in --allow-proc {:?}",
266 self.policy.allow_proc
267 ).into())));
268 }
269 }
270
271 let mut command = std::process::Command::new(&cmd);
272 command.args(&str_args);
273 command.stdin(std::process::Stdio::piped());
274 command.stdout(std::process::Stdio::piped());
275 command.stderr(std::process::Stdio::piped());
276
277 if let Some(Value::Variant { name, args: vargs }) = opts.get("cwd") {
278 if name == "Some" {
279 if let Some(Value::Str(s)) = vargs.first() {
280 command.current_dir(s);
281 }
282 }
283 }
284 if let Some(Value::Map(env)) = opts.get("env") {
285 for (k, v) in env {
286 if let (lex_bytecode::MapKey::Str(ks), Value::Str(vs)) = (k, v) {
287 command.env(ks, vs);
288 }
289 }
290 }
291
292 let stdin_payload: Option<Vec<u8>> = match opts.get("stdin") {
293 Some(Value::Variant { name, args: vargs }) if name == "Some" => {
294 match vargs.first() {
295 Some(Value::Bytes(b)) => Some(b.clone()),
296 _ => None,
297 }
298 }
299 _ => None,
300 };
301
302 let mut child = match command.spawn() {
303 Ok(c) => c,
304 Err(e) => return Ok(err(Value::Str(format!("process.spawn `{cmd}`: {e}").into()))),
305 };
306
307 if let Some(payload) = stdin_payload {
308 if let Some(mut stdin) = child.stdin.take() {
309 use std::io::Write;
310 let _ = stdin.write_all(&payload);
311 }
313 }
314
315 let stdout = child.stdout.take().map(std::io::BufReader::new);
316 let stderr = child.stderr.take().map(std::io::BufReader::new);
317 let handle = next_process_handle();
318 process_registry().lock().unwrap().insert(handle, ProcessState {
319 child,
320 stdout,
321 stderr,
322 });
323 Ok(ok(Value::Int(handle as i64)))
324 }
325 "read_stdout_line" => Self::read_line_op(args, true),
326 "read_stderr_line" => Self::read_line_op(args, false),
327 "wait" => {
328 let h = expect_process_handle(args.first())?;
329 let arc = process_registry().lock().unwrap()
333 .touch_get(h)
334 .ok_or_else(|| "process.wait: closed or unknown ProcessHandle".to_string())?;
335 let status = {
336 let mut state = arc.lock().unwrap();
337 state.child.wait().map_err(|e| format!("process.wait: {e}"))?
338 };
339 process_registry().lock().unwrap().remove(h);
343 let mut rec = indexmap::IndexMap::new();
344 rec.insert("code".into(), Value::Int(status.code().unwrap_or(-1) as i64));
345 #[cfg(unix)]
346 {
347 use std::os::unix::process::ExitStatusExt;
348 rec.insert("signaled".into(), Value::Bool(status.signal().is_some()));
349 }
350 #[cfg(not(unix))]
351 {
352 rec.insert("signaled".into(), Value::Bool(false));
353 }
354 Ok(Value::record_dynamic(rec))
355 }
356 "kill" => {
357 let h = expect_process_handle(args.first())?;
358 let _signal = expect_str(args.get(1))?;
359 let arc = process_registry().lock().unwrap()
360 .touch_get(h)
361 .ok_or_else(|| "process.kill: closed or unknown ProcessHandle".to_string())?;
362 let mut state = arc.lock().unwrap();
363 match state.child.kill() {
366 Ok(_) => Ok(ok(Value::Unit)),
367 Err(e) => Ok(err(Value::Str(format!("process.kill: {e}").into()))),
368 }
369 }
370 "run" => {
371 let cmd = expect_str(args.first())?.to_string();
372 let raw_args = match args.get(1) {
373 Some(Value::List(items)) => items.clone(),
374 _ => return Err("process.run: args must be List[Str]".into()),
375 };
376 let str_args: Result<Vec<String>, String> = raw_args.iter().map(|v| match v {
377 Value::Str(s) => Ok(s.to_string()),
378 other => Err(format!("process.run: arg must be Str, got {other:?}")),
379 }).collect();
380 let str_args = str_args?;
381 if !self.policy.allow_proc.is_empty() {
382 let basename = std::path::Path::new(&cmd)
383 .file_name()
384 .and_then(|s| s.to_str())
385 .unwrap_or(&cmd);
386 if !self.policy.allow_proc.iter().any(|a| a == basename) {
387 return Ok(err(Value::Str(format!(
388 "process.run: `{cmd}` not in --allow-proc {:?}",
389 self.policy.allow_proc
390 ).into())));
391 }
392 }
393 match std::process::Command::new(&cmd).args(&str_args).output() {
394 Ok(o) => {
395 let mut rec = indexmap::IndexMap::new();
396 rec.insert("stdout".into(), Value::Str(
397 String::from_utf8_lossy(&o.stdout).into_owned().into()));
398 rec.insert("stderr".into(), Value::Str(
399 String::from_utf8_lossy(&o.stderr).into_owned().into()));
400 rec.insert("exit_code".into(), Value::Int(
401 o.status.code().unwrap_or(-1) as i64));
402 Ok(ok(Value::record_dynamic(rec)))
403 }
404 Err(e) => Ok(err(Value::Str(format!("process.run `{cmd}`: {e}").into()))),
405 }
406 }
407 other => Err(format!("unsupported process.{other}")),
408 }
409 }
410
411 fn read_line_op(args: Vec<Value>, is_stdout: bool) -> Result<Value, String> {
417 let h = expect_process_handle(args.first())?;
418 let arc = process_registry().lock().unwrap()
419 .touch_get(h)
420 .ok_or_else(|| format!(
421 "process.read_{}_line: closed or unknown ProcessHandle",
422 if is_stdout { "stdout" } else { "stderr" }))?;
423 let mut state = arc.lock().unwrap();
424 let reader_opt = if is_stdout {
425 state.stdout.as_mut().map(|r| -> &mut dyn std::io::BufRead { r })
426 } else {
427 state.stderr.as_mut().map(|r| -> &mut dyn std::io::BufRead { r })
428 };
429 let reader = match reader_opt {
430 Some(r) => r,
431 None => return Ok(none()),
432 };
433 let mut line = String::new();
434 match reader.read_line(&mut line) {
435 Ok(0) => Ok(none()),
436 Ok(_) => {
437 if line.ends_with('\n') { line.pop(); }
438 if line.ends_with('\r') { line.pop(); }
439 Ok(some(Value::Str(line.into())))
440 }
441 Err(e) => Err(format!("process.read_*_line: {e}")),
442 }
443 }
444
445 fn dispatch_fs(&mut self, op: &str, args: Vec<Value>) -> Result<Value, String> {
446 match op {
447 "exists" => {
448 let path = expect_str(args.first())?.to_string();
449 if let Err(e) = self.ensure_fs_walk_path(&path) {
450 return Ok(err(Value::Str(e.into())));
451 }
452 Ok(Value::Bool(std::path::Path::new(&path).exists()))
453 }
454 "is_file" => {
455 let path = expect_str(args.first())?.to_string();
456 if let Err(e) = self.ensure_fs_walk_path(&path) {
457 return Ok(err(Value::Str(e.into())));
458 }
459 Ok(Value::Bool(std::path::Path::new(&path).is_file()))
460 }
461 "is_dir" => {
462 let path = expect_str(args.first())?.to_string();
463 if let Err(e) = self.ensure_fs_walk_path(&path) {
464 return Ok(err(Value::Str(e.into())));
465 }
466 Ok(Value::Bool(std::path::Path::new(&path).is_dir()))
467 }
468 "stat" => {
469 let path = expect_str(args.first())?.to_string();
470 if let Err(e) = self.ensure_fs_walk_path(&path) {
471 return Ok(err(Value::Str(e.into())));
472 }
473 match std::fs::metadata(&path) {
474 Ok(md) => {
475 let mtime = md.modified()
476 .ok()
477 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
478 .map(|d| d.as_secs() as i64)
479 .unwrap_or(0);
480 let mut rec = indexmap::IndexMap::new();
481 rec.insert("size".into(), Value::Int(md.len() as i64));
482 rec.insert("mtime".into(), Value::Int(mtime));
483 rec.insert("is_dir".into(), Value::Bool(md.is_dir()));
484 rec.insert("is_file".into(), Value::Bool(md.is_file()));
485 Ok(ok(Value::record_dynamic(rec)))
486 }
487 Err(e) => Ok(err(Value::Str(format!("fs.stat `{path}`: {e}").into()))),
488 }
489 }
490 "list_dir" => {
491 let path = expect_str(args.first())?.to_string();
492 if let Err(e) = self.ensure_fs_walk_path(&path) {
493 return Ok(err(Value::Str(e.into())));
494 }
495 match std::fs::read_dir(&path) {
496 Ok(rd) => {
497 let mut entries: Vec<Value> = Vec::new();
498 for ent in rd {
499 match ent {
500 Ok(e) => {
501 let p = e.path();
502 entries.push(Value::Str(p.to_string_lossy().into_owned().into()));
503 }
504 Err(e) => return Ok(err(Value::Str(format!("fs.list_dir: {e}").into()))),
505 }
506 }
507 Ok(ok(Value::List(entries.into())))
508 }
509 Err(e) => Ok(err(Value::Str(format!("fs.list_dir `{path}`: {e}").into()))),
510 }
511 }
512 "walk" => {
513 let path = expect_str(args.first())?.to_string();
514 if let Err(e) = self.ensure_fs_walk_path(&path) {
515 return Ok(err(Value::Str(e.into())));
516 }
517 let mut paths: Vec<Value> = Vec::new();
518 for ent in walkdir::WalkDir::new(&path) {
519 match ent {
520 Ok(e) => paths.push(Value::Str(
521 e.path().to_string_lossy().into_owned().into())),
522 Err(e) => return Ok(err(Value::Str(format!("fs.walk: {e}").into()))),
523 }
524 }
525 Ok(ok(Value::List(paths.into())))
526 }
527 "glob" => {
528 let pattern = expect_str(args.first())?.to_string();
529 let entries = match glob::glob(&pattern) {
534 Ok(e) => e,
535 Err(e) => return Ok(err(Value::Str(format!("fs.glob: {e}").into()))),
536 };
537 let mut paths: Vec<Value> = Vec::new();
538 for ent in entries {
539 match ent {
540 Ok(p) => {
541 let s = p.to_string_lossy().into_owned();
542 if self.policy.allow_fs_read.is_empty()
543 || self.policy.allow_fs_read.iter().any(|root| p.starts_with(root))
544 {
545 paths.push(Value::Str(s.into()));
546 }
547 }
548 Err(e) => return Ok(err(Value::Str(format!("fs.glob: {e}").into()))),
549 }
550 }
551 Ok(ok(Value::List(paths.into())))
552 }
553 "mkdir_p" => {
554 let path = expect_str(args.first())?.to_string();
555 if let Err(e) = self.ensure_fs_write_path(&path) {
556 return Ok(err(Value::Str(e.into())));
557 }
558 match std::fs::create_dir_all(&path) {
559 Ok(_) => Ok(ok(Value::Unit)),
560 Err(e) => Ok(err(Value::Str(format!("fs.mkdir_p `{path}`: {e}").into()))),
561 }
562 }
563 "remove" => {
564 let path = expect_str(args.first())?.to_string();
565 if let Err(e) = self.ensure_fs_write_path(&path) {
566 return Ok(err(Value::Str(e.into())));
567 }
568 let p = std::path::Path::new(&path);
569 let result = if p.is_dir() {
570 std::fs::remove_dir_all(p)
571 } else {
572 std::fs::remove_file(p)
573 };
574 match result {
575 Ok(_) => Ok(ok(Value::Unit)),
576 Err(e) => Ok(err(Value::Str(format!("fs.remove `{path}`: {e}").into()))),
577 }
578 }
579 "copy" => {
580 let src = expect_str(args.first())?.to_string();
581 let dst = expect_str(args.get(1))?.to_string();
582 if let Err(e) = self.ensure_fs_walk_path(&src) {
583 return Ok(err(Value::Str(e.into())));
584 }
585 if let Err(e) = self.ensure_fs_write_path(&dst) {
586 return Ok(err(Value::Str(e.into())));
587 }
588 match std::fs::copy(&src, &dst) {
589 Ok(_) => Ok(ok(Value::Unit)),
590 Err(e) => Ok(err(Value::Str(format!("fs.copy {src} -> {dst}: {e}").into()))),
591 }
592 }
593 other => Err(format!("unsupported fs.{other}")),
594 }
595 }
596
597 fn ensure_fs_walk_path(&self, path: &str) -> Result<(), String> {
602 if self.policy.allow_fs_read.is_empty() {
603 return Ok(());
604 }
605 let p = std::path::Path::new(path);
606 if self.policy.allow_fs_read.iter().any(|a| p.starts_with(a)) {
607 Ok(())
608 } else {
609 Err(format!("fs path `{path}` outside --allow-fs-read"))
610 }
611 }
612
613 fn ensure_fs_write_path(&self, path: &str) -> Result<(), String> {
616 if self.policy.allow_fs_write.is_empty() {
617 return Ok(());
618 }
619 let p = std::path::Path::new(path);
620 if self.policy.allow_fs_write.iter().any(|a| p.starts_with(a)) {
621 Ok(())
622 } else {
623 Err(format!("fs path `{path}` outside --allow-fs-write"))
624 }
625 }
626
627 fn ensure_host_allowed(&self, url: &str) -> Result<(), String> {
631 if self.policy.allow_net_host.is_empty() { return Ok(()); }
632 let host = extract_host(url).unwrap_or("");
633 if self.policy.allow_net_host.iter().any(|h| host == h) {
634 Ok(())
635 } else {
636 Err(format!(
637 "net call to host `{host}` not in --allow-net-host {:?}",
638 self.policy.allow_net_host,
639 ))
640 }
641 }
642}
643
644fn extract_host(url: &str) -> Option<&str> {
645 let rest = url
646 .strip_prefix("http://")
647 .or_else(|| url.strip_prefix("https://"))
648 .or_else(|| url.strip_prefix("redis://"))
649 .or_else(|| url.strip_prefix("rediss://"))
650 .map(|r| r.split_once('@').map(|(_, after)| after).unwrap_or(r))?;
652 let host_port = match rest.find('/') {
653 Some(i) => &rest[..i],
654 None => rest,
655 };
656 Some(match host_port.rsplit_once(':') {
657 Some((h, _)) => h,
658 None => host_port,
659 })
660}
661
662impl EffectHandler for DefaultHandler {
663 fn enter_request_scope(&mut self) -> u64 {
667 let id = self.next_scope_id;
668 self.next_scope_id = self.next_scope_id.wrapping_add(1);
669 self.arena_stack.push((id, crate::arena::Arena::new()));
670 id
671 }
672
673 fn exit_request_scope(&mut self, scope_id: u64) {
679 if let Some(pos) = self.arena_stack.iter().position(|(id, _)| *id == scope_id) {
680 self.arena_stack.truncate(pos);
685 }
686 }
687
688 fn note_call_budget(&mut self, cost: u64) -> Result<(), String> {
693 let Some(ceiling) = self.budget_ceiling else { return Ok(()); };
696 loop {
702 let cur = self.budget_remaining.load(Ordering::SeqCst);
703 if cost > cur {
704 let used = ceiling.saturating_sub(cur);
705 return Err(format!(
706 "budget exceeded: requested {cost}, used so far {used}, ceiling {ceiling}"));
707 }
708 let next = cur - cost;
709 if self.budget_remaining.compare_exchange(cur, next,
712 Ordering::SeqCst, Ordering::SeqCst).is_ok() {
713 return Ok(());
714 }
715 }
716 }
717
718 fn dispatch(&mut self, kind: &str, op: &str, args: Vec<Value>) -> Result<Value, String> {
719 if is_pure_call(kind, op) {
723 return call_pure_builtin(kind, op, args);
724 }
725 if kind == "process" {
729 self.ensure_kind_allowed("proc")?;
730 return self.dispatch_process(op, args);
731 }
732 if kind == "log" {
733 let effect_kind = match op {
736 "debug" | "info" | "warn" | "error" => "log",
737 "set_level" | "set_format" => "io",
738 "set_sink" => {
739 self.ensure_kind_allowed("io")?;
740 self.ensure_kind_allowed("fs_write")?;
741 return self.dispatch_log(op, args);
742 }
743 other => return Err(format!("unsupported log.{other}")),
744 };
745 self.ensure_kind_allowed(effect_kind)?;
746 return self.dispatch_log(op, args);
747 }
748 if kind == "fs" {
749 let effect_kind = match op {
750 "exists" | "is_file" | "is_dir" | "stat"
751 | "list_dir" | "walk" | "glob" => "fs_walk",
752 "mkdir_p" | "remove" => "fs_write",
753 "copy" => {
754 self.ensure_kind_allowed("fs_walk")?;
755 self.ensure_kind_allowed("fs_write")?;
756 return self.dispatch_fs(op, args);
757 }
758 other => return Err(format!("unsupported fs.{other}")),
759 };
760 self.ensure_kind_allowed(effect_kind)?;
761 return self.dispatch_fs(op, args);
762 }
763 if kind == "datetime" && op == "now" {
770 self.ensure_kind_allowed("time")?;
771 if let Ok(s) = std::env::var("LEX_TEST_NOW") {
773 if let Ok(secs) = s.trim().parse::<i64>() {
774 return Ok(Value::Int(secs.saturating_mul(1_000_000_000)));
775 }
776 }
777 let now = chrono::Utc::now();
778 let nanos = now.timestamp_nanos_opt().unwrap_or(i64::MAX);
779 return Ok(Value::Int(nanos));
780 }
781 if kind == "crypto" && op == "random" {
782 self.ensure_kind_allowed("random")?;
783 let n = expect_int(args.first())?;
784 if !(0..=1_048_576).contains(&n) {
785 return Err("crypto.random: n must be in 0..=1048576".into());
786 }
787 use rand::{rngs::SysRng, TryRng};
788 let mut buf = vec![0u8; n as usize];
789 SysRng.try_fill_bytes(&mut buf)
790 .map_err(|e| format!("crypto.random: OS RNG: {e}"))?;
791 return Ok(Value::Bytes(buf));
792 }
793 if kind == "crypto" && op == "random_str_hex" {
798 self.ensure_kind_allowed("random")?;
799 let n = expect_int(args.first())?;
800 if !(0..=1_048_576).contains(&n) {
801 return Err("crypto.random_str_hex: n must be in 0..=1048576".into());
802 }
803 use rand::{rngs::SysRng, TryRng};
804 let mut buf = vec![0u8; n as usize];
805 SysRng.try_fill_bytes(&mut buf)
806 .map_err(|e| format!("crypto.random_str_hex: OS RNG: {e}"))?;
807 return Ok(Value::Str(hex::encode(&buf).into()));
808 }
809 if kind == "crypto" && op == "p256_generate" {
822 self.ensure_kind_allowed("random")?;
823 use p256::ecdsa::SigningKey;
824 use rand::{rngs::SysRng, TryRng};
825 for _ in 0..16 {
826 let mut buf = [0u8; 32];
827 SysRng.try_fill_bytes(&mut buf)
828 .map_err(|e| format!("crypto.p256_generate: OS RNG: {e}"))?;
829 if let Ok(sk) = SigningKey::from_slice(&buf) {
830 return Ok(ok(Value::Bytes(sk.to_bytes().to_vec())));
831 }
832 }
833 return Ok(err(Value::Str(
834 "crypto.p256_generate: failed to sample a valid scalar".into())));
835 }
836 if kind == "crypto" && op == "secp256k1_generate" {
843 self.ensure_kind_allowed("random")?;
844 use k256::ecdsa::SigningKey;
845 use rand::{rngs::SysRng, TryRng};
846 for _ in 0..16 {
847 let mut buf = [0u8; 32];
848 SysRng.try_fill_bytes(&mut buf)
849 .map_err(|e| format!("crypto.secp256k1_generate: OS RNG: {e}"))?;
850 if let Ok(sk) = SigningKey::from_slice(&buf) {
851 return Ok(ok(Value::Bytes(sk.to_bytes().to_vec())));
852 }
853 }
854 return Ok(err(Value::Str(
855 "crypto.secp256k1_generate: failed to sample a valid scalar".into())));
856 }
857 if kind == "agent" {
870 let effect_kind = match op {
871 "local_complete" => "llm_local",
872 "cloud_complete" => "llm_cloud",
873 "cloud_stream" => "llm_cloud",
874 "send_a2a" => "a2a",
875 "call_mcp" => "mcp",
876 other => return Err(format!("unsupported agent.{other}")),
877 };
878 self.ensure_kind_allowed(effect_kind)?;
879 return match op {
887 "call_mcp" => Ok(self.dispatch_call_mcp(args)),
888 "local_complete" => Ok(dispatch_llm_local(args)),
889 "cloud_complete" => Ok(dispatch_llm_cloud(args)),
890 "cloud_stream" => Ok(self.dispatch_cloud_stream(args)),
891 _ => Ok(ok(Value::Str(format!("<{effect_kind} stub>").into()))),
892 };
893 }
894 if kind == "stream" {
895 self.ensure_kind_allowed("stream")?;
902 return match op {
903 "next" => Ok(self.dispatch_stream_next(args)),
904 "collect" => Ok(self.dispatch_stream_collect(args)),
905 other => Err(format!("unsupported stream.{other}")),
906 };
907 }
908 if kind == "http" && matches!(op, "send" | "get" | "post" | "stream_lines") {
909 self.ensure_kind_allowed("net")?;
910 return match op {
911 "send" => {
912 let req = expect_record(args.first())?;
913 Ok(http_send_record(self, req))
914 }
915 "get" => {
916 let url = expect_str(args.first())?.to_string();
917 self.ensure_host_allowed(&url)?;
918 Ok(http_send_simple("GET", &url, None, "", None))
919 }
920 "post" => {
921 let url = expect_str(args.first())?.to_string();
922 let body = expect_bytes(args.get(1))?.clone();
923 let content_type = expect_str(args.get(2))?.to_string();
924 self.ensure_host_allowed(&url)?;
925 Ok(http_send_simple("POST", &url, Some(body), &content_type, None))
926 }
927 "stream_lines" => {
928 let url = expect_str(args.first())?.to_string();
929 let headers_val = args.get(1).cloned().unwrap_or(Value::Map(Default::default()));
930 let body = expect_str(args.get(2))?.to_string();
931 self.ensure_host_allowed(&url)?;
932 Ok(http_stream_lines_impl(self, &url, &headers_val, &body))
933 }
934 _ => unreachable!(),
935 };
936 }
937 if kind == "arrow" && op == "read_csv" {
943 self.ensure_kind_allowed("fs_read")?;
944 let path = expect_str(args.first())?.to_string();
945 let resolved = self.resolve_read_path(&path);
946 if !self.policy.allow_fs_read.is_empty()
947 && !self.policy.allow_fs_read.iter().any(|a| resolved.starts_with(a))
948 {
949 return Err(format!("arrow.read_csv: `{path}` outside --allow-fs-read"));
950 }
951 return match crate::arrow::read_csv_at(&resolved) {
952 Ok(v) => Ok(ok(v)),
953 Err(e) => Ok(err(Value::Str(e.into()))),
954 };
955 }
956 if kind == "arrow" && (op == "read_parquet" || op == "read_parquet_cols") {
960 self.ensure_kind_allowed("fs_read")?;
961 let path = expect_str(args.first())?.to_string();
962 let resolved = self.resolve_read_path(&path);
963 if !self.policy.allow_fs_read.is_empty()
964 && !self.policy.allow_fs_read.iter().any(|a| resolved.starts_with(a))
965 {
966 return Err(format!("arrow.{op}: `{path}` outside --allow-fs-read"));
967 }
968 let r = if op == "read_parquet" {
969 crate::arrow::read_parquet_at(&resolved)
970 } else {
971 let cols = match args.get(1) {
972 Some(Value::List(items)) => {
973 let mut out = Vec::with_capacity(items.len());
974 for v in items.iter() {
975 match v {
976 Value::Str(s) => out.push(s.to_string()),
977 other => return Err(format!(
978 "arrow.read_parquet_cols: column name not Str: {other:?}")),
979 }
980 }
981 out
982 }
983 other => return Err(format!(
984 "arrow.read_parquet_cols: expected List[Str], got {other:?}")),
985 };
986 crate::arrow::read_parquet_cols_at(&resolved, &cols)
987 };
988 return match r {
989 Ok(v) => Ok(ok(v)),
990 Err(e) => Ok(err(Value::Str(e.into()))),
991 };
992 }
993 if kind == "arrow" && (op == "write_parquet" || op == "write_csv") {
996 self.ensure_kind_allowed("fs_write")?;
997 let table_v = args.first().cloned().unwrap_or(Value::Unit);
998 let rb = match &table_v {
999 Value::ArrowTable(t) => Arc::clone(t),
1000 other => return Err(format!("arrow.{op}: first arg must be arrow.Table, got {other:?}")),
1001 };
1002 let path = expect_str(args.get(1))?.to_string();
1003 if let Err(e) = self.ensure_fs_write_path(&path) {
1004 return Ok(err(Value::Str(format!("arrow.{op}: {e}").into())));
1005 }
1006 let r = if op == "write_parquet" {
1007 crate::arrow::write_parquet_at(&rb, std::path::Path::new(&path))
1008 } else {
1009 crate::arrow::write_csv_at(&rb, std::path::Path::new(&path))
1010 };
1011 return match r {
1012 Ok(_) => Ok(ok(Value::Unit)),
1013 Err(e) => Ok(err(Value::Str(e.into()))),
1014 };
1015 }
1016 if kind == "net" && op == "default_opts" {
1021 return Ok(ServeOpts::lex_defaults().to_value());
1022 }
1023 if kind == "tls" {
1031 return match op {
1032 "from_pem_files" => {
1033 self.ensure_kind_allowed("fs_read")?;
1034 dispatch_tls_from_pem_files(self, args)
1035 }
1036 "self_signed" => dispatch_tls_self_signed(args),
1037 other => Err(format!("unsupported tls.{other}")),
1038 };
1039 }
1040 if kind == "redis" {
1044 self.ensure_kind_allowed("net")?;
1045 } else if kind == "rand" {
1046 self.ensure_kind_allowed("random")?;
1050 } else {
1051 self.ensure_kind_allowed(kind)?;
1052 }
1053 match (kind, op) {
1054 ("io", "print") => {
1055 let line = expect_str(args.first())?;
1056 self.sink.print_line(line);
1057 Ok(Value::Unit)
1058 }
1059 ("io", "read") => {
1060 let path = expect_str(args.first())?.to_string();
1061 let resolved = self.resolve_read_path(&path);
1062 if !self.policy.allow_fs_read.is_empty()
1069 && !self.policy.allow_fs_read.iter().any(|a| resolved.starts_with(a))
1070 {
1071 return Err(format!("read of `{path}` outside --allow-fs-read"));
1072 }
1073 match std::fs::read_to_string(&resolved) {
1074 Ok(s) => Ok(ok(Value::Str(s.into()))),
1075 Err(e) => Ok(err(Value::Str(format!("{e}").into()))),
1076 }
1077 }
1078 ("io", "readline") => {
1079 use std::io::BufRead;
1080 let stdin = std::io::stdin();
1081 let mut line = String::new();
1082 match stdin.lock().read_line(&mut line) {
1083 Ok(0) => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1084 Ok(_) => {
1085 if line.ends_with('\n') { line.pop(); }
1086 if line.ends_with('\r') { line.pop(); }
1087 Ok(Value::Variant { name: "Some".into(), args: vec![Value::Str(line.into())] })
1088 }
1089 Err(_) => Ok(Value::Variant { name: "None".into(), args: vec![] }),
1090 }
1091 }
1092 ("io", "argv") => {
1093 let list: Vec<Value> = self.program_args.iter()
1094 .map(|s| Value::Str(s.as_str().into()))
1095 .collect();
1096 Ok(Value::List(list.into()))
1097 }
1098 ("io", "write") => {
1099 let path = expect_str(args.first())?.to_string();
1100 let contents = expect_str(args.get(1))?.to_string();
1101 if !self.policy.allow_fs_write.is_empty() {
1105 let raw = std::env::current_dir()
1106 .map(|cwd| cwd.join(&path))
1107 .unwrap_or_else(|_| std::path::PathBuf::from(&path));
1108 let p = std::fs::canonicalize(&raw).unwrap_or_else(|_| {
1112 raw.parent()
1113 .and_then(|par| std::fs::canonicalize(par).ok())
1114 .map(|par| par.join(raw.file_name().unwrap_or_default()))
1115 .unwrap_or(raw)
1116 });
1117 let allowed = self.policy.allow_fs_write.iter().any(|a| {
1118 let ca = std::fs::canonicalize(a).unwrap_or_else(|_| a.clone());
1119 p.starts_with(&ca)
1120 });
1121 if !allowed {
1122 return Err(format!("write to `{path}` outside --allow-fs-write"));
1123 }
1124 }
1125 match std::fs::write(&path, contents) {
1126 Ok(_) => Ok(ok(Value::Unit)),
1127 Err(e) => Ok(err(Value::Str(format!("{e}").into()))),
1128 }
1129 }
1130 ("time", "now") => {
1131 if let Ok(s) = std::env::var("LEX_TEST_NOW") {
1133 if let Ok(secs) = s.trim().parse::<i64>() {
1134 return Ok(Value::Int(secs));
1135 }
1136 }
1137 let secs = SystemTime::now().duration_since(UNIX_EPOCH)
1138 .map_err(|e| format!("time: {e}"))?.as_secs();
1139 Ok(Value::Int(secs as i64))
1140 }
1141 ("time", "now_ms") => {
1142 if let Ok(s) = std::env::var("LEX_TEST_NOW") {
1147 if let Ok(secs) = s.trim().parse::<i64>() {
1148 return Ok(Value::Int(secs.saturating_mul(1000)));
1149 }
1150 }
1151 let ms = SystemTime::now().duration_since(UNIX_EPOCH)
1152 .map_err(|e| format!("time: {e}"))?.as_millis();
1153 Ok(Value::Int(ms as i64))
1154 }
1155 ("time", "now_str") => {
1156 if let Ok(s) = std::env::var("LEX_TEST_NOW") {
1160 if let Ok(secs) = s.trim().parse::<i64>() {
1161 let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(secs, 0)
1162 .unwrap_or_else(chrono::Utc::now);
1163 return Ok(Value::Str(dt.to_rfc3339().into()));
1164 }
1165 }
1166 Ok(Value::Str(chrono::Utc::now().to_rfc3339().into()))
1167 }
1168 ("time", "mono_ns") => {
1169 static MONO_START: OnceLock<std::time::Instant> = OnceLock::new();
1177 let start = MONO_START.get_or_init(std::time::Instant::now);
1178 let dur = std::time::Instant::now().duration_since(*start);
1179 Ok(Value::Int(dur.as_nanos() as i64))
1180 }
1181 ("time", "sleep_ms") => {
1182 let n = expect_int(args.first())?;
1190 if n > 0 {
1191 let ms = (n as u64).min(60_000);
1192 std::thread::sleep(std::time::Duration::from_millis(ms));
1193 }
1194 Ok(Value::Unit)
1195 }
1196 ("time", "sleep") => {
1197 let nanos = expect_int(args.first())?;
1203 if nanos > 0 {
1204 let bounded_nanos = (nanos as u64).min(60_000 * 1_000_000);
1205 std::thread::sleep(std::time::Duration::from_nanos(bounded_nanos));
1206 }
1207 Ok(Value::Unit)
1208 }
1209 ("rand", "int_in") => {
1210 let lo = expect_int(args.first())?;
1214 let hi = expect_int(args.get(1))?;
1215 if hi < lo {
1216 return Err(format!("rand.int_in: empty range [{lo}, {hi}]"));
1217 }
1218 use rand::{rngs::SysRng, TryRng};
1219 let span = (hi as i128 - lo as i128 + 1) as u128;
1222 let mut buf = [0u8; 16];
1223 SysRng.try_fill_bytes(&mut buf)
1224 .map_err(|e| format!("rand.int_in: OS RNG: {e}"))?;
1225 let draw = (u128::from_le_bytes(buf) % span) as i128;
1226 Ok(Value::Int((lo as i128 + draw) as i64))
1227 }
1228 ("env", "get") => {
1233 let name = expect_str(args.first())?;
1234 Ok(match std::env::var(name) {
1235 Ok(v) => Value::Variant {
1236 name: "Some".into(),
1237 args: vec![Value::Str(v.into())],
1238 },
1239 Err(_) => Value::Variant { name: "None".into(), args: Vec::new() },
1240 })
1241 }
1242 ("budget", _) => {
1243 Ok(Value::Unit)
1246 }
1247 ("net", "get") => {
1248 let url = expect_str(args.first())?.to_string();
1249 self.ensure_host_allowed(&url)?;
1250 Ok(http_request("GET", &url, None))
1251 }
1252 ("net", "post") => {
1253 let url = expect_str(args.first())?.to_string();
1254 let body = expect_str(args.get(1))?.to_string();
1255 self.ensure_host_allowed(&url)?;
1256 Ok(http_request("POST", &url, Some(&body)))
1257 }
1258 ("net", "serve") => {
1259 let port = match args.first() {
1260 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1261 _ => return Err("net.serve(port, handler): port must be Int 0..=65535".into()),
1262 };
1263 let handler_name = expect_str(args.get(1))?.to_string();
1264 let program = self.program.clone()
1265 .ok_or_else(|| "net.serve requires a Program reference; use DefaultHandler::with_program".to_string())?;
1266 let policy = self.policy.clone();
1267 serve_http(port, handler_name, program, policy, None, ServeOpts::from_env())
1268 }
1269 ("net", "serve_fn") => {
1270 let port = match args.first() {
1271 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1272 _ => return Err("net.serve_fn(port, handler): port must be Int 0..=65535".into()),
1273 };
1274 let closure = match args.into_iter().nth(1) {
1275 Some(c @ Value::Closure { .. }) => c,
1276 _ => return Err("net.serve_fn(port, handler): handler must be a closure".into()),
1277 };
1278 let program = self.program.clone()
1279 .ok_or_else(|| "net.serve_fn requires a Program reference; use DefaultHandler::with_program".to_string())?;
1280 let policy = self.policy.clone();
1281 serve_http_fn(port, closure, program, policy, ServeOpts::from_env())
1282 }
1283 ("net", "serve_routed") => {
1284 let port = match args.first() {
1285 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1286 _ => return Err("net.serve_routed(port, routes, fallback): port must be Int 0..=65535".into()),
1287 };
1288 let routes_val = args.get(1).cloned()
1289 .ok_or_else(|| "net.serve_routed(port, routes, fallback): missing routes".to_string())?;
1290 let fallback = match args.into_iter().nth(2) {
1291 Some(c @ Value::Closure { .. }) => c,
1292 _ => return Err("net.serve_routed(port, routes, fallback): fallback must be a closure".into()),
1293 };
1294 let routes = decode_routes_arg(routes_val)?;
1295 let program = self.program.clone()
1296 .ok_or_else(|| "net.serve_routed requires a Program reference; use DefaultHandler::with_program".to_string())?;
1297 let policy = self.policy.clone();
1298 serve_http_routed(port, routes, fallback, program, policy, ServeOpts::from_env())
1299 }
1300 ("net", "serve_with") => {
1301 let port = match args.first() {
1303 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1304 _ => return Err("net.serve_with(port, handler, opts): port must be Int 0..=65535".into()),
1305 };
1306 let handler_name = expect_str(args.get(1))?.to_string();
1307 let opts = decode_serve_opts(args.get(2)
1308 .ok_or_else(|| "net.serve_with(port, handler, opts): missing opts".to_string())?)?;
1309 let program = self.program.clone()
1310 .ok_or_else(|| "net.serve_with requires a Program reference; use DefaultHandler::with_program".to_string())?;
1311 let policy = self.policy.clone();
1312 serve_http(port, handler_name, program, policy, None, opts)
1313 }
1314 ("net", "serve_fn_with") => {
1315 let port = match args.first() {
1317 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1318 _ => return Err("net.serve_fn_with(port, handler, opts): port must be Int 0..=65535".into()),
1319 };
1320 let opts = decode_serve_opts(args.get(2)
1321 .ok_or_else(|| "net.serve_fn_with(port, handler, opts): missing opts".to_string())?)?;
1322 let closure = match args.into_iter().nth(1) {
1323 Some(c @ Value::Closure { .. }) => c,
1324 _ => return Err("net.serve_fn_with(port, handler, opts): handler must be a closure".into()),
1325 };
1326 let program = self.program.clone()
1327 .ok_or_else(|| "net.serve_fn_with requires a Program reference; use DefaultHandler::with_program".to_string())?;
1328 let policy = self.policy.clone();
1329 serve_http_fn(port, closure, program, policy, opts)
1330 }
1331 ("net", "serve_routed_with") => {
1332 let port = match args.first() {
1334 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1335 _ => return Err("net.serve_routed_with(port, routes, fallback, opts): port must be Int 0..=65535".into()),
1336 };
1337 let routes_val = args.get(1).cloned()
1338 .ok_or_else(|| "net.serve_routed_with(port, routes, fallback, opts): missing routes".to_string())?;
1339 let opts = decode_serve_opts(args.get(3)
1340 .ok_or_else(|| "net.serve_routed_with(port, routes, fallback, opts): missing opts".to_string())?)?;
1341 let fallback = match args.into_iter().nth(2) {
1342 Some(c @ Value::Closure { .. }) => c,
1343 _ => return Err("net.serve_routed_with(port, routes, fallback, opts): fallback must be a closure".into()),
1344 };
1345 let routes = decode_routes_arg(routes_val)?;
1346 let program = self.program.clone()
1347 .ok_or_else(|| "net.serve_routed_with requires a Program reference; use DefaultHandler::with_program".to_string())?;
1348 let policy = self.policy.clone();
1349 serve_http_routed(port, routes, fallback, program, policy, opts)
1350 }
1351 ("net", "serve_quic") => self.dispatch_serve_quic_named(args),
1352 ("net", "serve_quic_fn") => self.dispatch_serve_quic_fn(args),
1353 ("net", "serve_quic_routed") => self.dispatch_serve_quic_routed(args),
1354 ("net", "serve_tls") => {
1355 let port = match args.first() {
1356 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1357 _ => return Err("net.serve_tls(port, cert, key, handler): port must be Int 0..=65535".into()),
1358 };
1359 let cert_path = expect_str(args.get(1))?.to_string();
1360 let key_path = expect_str(args.get(2))?.to_string();
1361 let handler_name = expect_str(args.get(3))?.to_string();
1362 let program = self.program.clone()
1363 .ok_or_else(|| "net.serve_tls requires a Program reference".to_string())?;
1364 let policy = self.policy.clone();
1365 let cert = std::fs::read(&cert_path)
1366 .map_err(|e| format!("net.serve_tls: read cert {cert_path}: {e}"))?;
1367 let key = std::fs::read(&key_path)
1368 .map_err(|e| format!("net.serve_tls: read key {key_path}: {e}"))?;
1369 serve_http(port, handler_name, program, policy, Some(TlsConfig { cert, key }), ServeOpts::from_env())
1370 }
1371 ("net", "serve_ws") => {
1372 let port = match args.first() {
1373 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1374 _ => return Err("net.serve_ws(port, on_message): port must be Int 0..=65535".into()),
1375 };
1376 let handler_name = expect_str(args.get(1))?.to_string();
1377 let program = self.program.clone()
1378 .ok_or_else(|| "net.serve_ws requires a Program reference".to_string())?;
1379 let policy = self.policy.clone();
1380 let registry = Arc::new(crate::ws::ChatRegistry::default());
1381 crate::ws::serve_ws(port, handler_name, program, policy, registry)
1382 }
1383 ("net", "serve_ws_fn") => {
1384 let port = match args.first() {
1385 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1386 _ => return Err("net.serve_ws_fn(port, subprotocol, handler): port must be Int 0..=65535".into()),
1387 };
1388 let subprotocol = expect_str(args.get(1))?.to_string();
1389 let closure = match args.into_iter().nth(2) {
1390 Some(c @ Value::Closure { .. }) => c,
1391 _ => return Err("net.serve_ws_fn(port, subprotocol, handler): handler must be a closure".into()),
1392 };
1393 let program = self.program.clone()
1394 .ok_or_else(|| "net.serve_ws_fn requires a Program reference; use DefaultHandler::with_program".to_string())?;
1395 let policy = self.policy.clone();
1396 let registry = Arc::new(crate::ws::ChatRegistry::default());
1397 crate::ws::serve_ws_fn(port, subprotocol, closure, program, policy, registry)
1398 }
1399 ("net", "serve_ws_fn_auth") => {
1400 let port = match args.first() {
1402 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1403 _ => return Err("net.serve_ws_fn_auth(port, subprotocol, auth, on_message): port must be Int 0..=65535".into()),
1404 };
1405 let subprotocol = expect_str(args.get(1))?.to_string();
1406 let mut it = args.into_iter().skip(2);
1407 let auth_closure = match it.next() {
1408 Some(c @ Value::Closure { .. }) => c,
1409 _ => return Err("net.serve_ws_fn_auth(port, subprotocol, auth, on_message): auth must be a closure".into()),
1410 };
1411 let handler_closure = match it.next() {
1412 Some(c @ Value::Closure { .. }) => c,
1413 _ => return Err("net.serve_ws_fn_auth(port, subprotocol, auth, on_message): on_message must be a closure".into()),
1414 };
1415 let program = self.program.clone()
1416 .ok_or_else(|| "net.serve_ws_fn_auth requires a Program reference; use DefaultHandler::with_program".to_string())?;
1417 let policy = self.policy.clone();
1418 let registry = Arc::new(crate::ws::ChatRegistry::default());
1419 crate::ws::serve_ws_fn_auth(
1420 port, subprotocol, auth_closure, handler_closure,
1421 program, policy, registry,
1422 )
1423 }
1424 ("net", "serve_ws_fn_actor") => {
1425 let port = match args.first() {
1427 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
1428 _ => return Err("net.serve_ws_fn_actor(port, subprotocol, name_of, on_message): port must be Int 0..=65535".into()),
1429 };
1430 let subprotocol = expect_str(args.get(1))?.to_string();
1431 let mut it = args.into_iter().skip(2);
1432 let name_of_closure = match it.next() {
1433 Some(c @ Value::Closure { .. }) => c,
1434 _ => return Err("net.serve_ws_fn_actor(port, subprotocol, name_of, on_message): name_of must be a closure".into()),
1435 };
1436 let on_message_closure = match it.next() {
1437 Some(c @ Value::Closure { .. }) => c,
1438 _ => return Err("net.serve_ws_fn_actor(port, subprotocol, name_of, on_message): on_message must be a closure".into()),
1439 };
1440 let program = self.program.clone()
1441 .ok_or_else(|| "net.serve_ws_fn_actor requires a Program reference; use DefaultHandler::with_program".to_string())?;
1442 let policy = self.policy.clone();
1443 let registry = Arc::new(crate::ws::ChatRegistry::default());
1444 crate::ws::serve_ws_fn_actor(
1445 port, subprotocol, name_of_closure, on_message_closure,
1446 program, policy, registry,
1447 )
1448 }
1449 ("net", "dial_ws") => {
1450 let url = expect_str(args.first())?.to_string();
1452 let subprotocol = expect_str(args.get(1))?.to_string();
1453 let on_open = match args.get(2).cloned() {
1454 Some(c @ Value::Closure { .. }) => c,
1455 _ => return Err(
1456 "net.dial_ws(url, subprotocol, on_open, on_message): on_open must be a closure".into(),
1457 ),
1458 };
1459 let on_message = match args.into_iter().nth(3) {
1460 Some(c @ Value::Closure { .. }) => c,
1461 _ => return Err(
1462 "net.dial_ws(url, subprotocol, on_open, on_message): on_message must be a closure".into(),
1463 ),
1464 };
1465 let program = self.program.clone().ok_or_else(|| {
1466 "net.dial_ws requires a Program reference; use DefaultHandler::with_program".to_string()
1467 })?;
1468 let policy = self.policy.clone();
1469 crate::ws::dial_ws(url, subprotocol, on_open, on_message, program, policy)
1470 }
1471 ("net", "dial_ws_actor") => {
1472 let url = expect_str(args.first())?.to_string();
1474 let subprotocol = expect_str(args.get(1))?.to_string();
1475 let name = expect_str(args.get(2))?.to_string();
1476 let on_open = match args.get(3).cloned() {
1477 Some(c @ Value::Closure { .. }) => c,
1478 _ => return Err(
1479 "net.dial_ws_actor(url, subprotocol, name, on_open, on_message): on_open must be a closure".into(),
1480 ),
1481 };
1482 let on_message = match args.into_iter().nth(4) {
1483 Some(c @ Value::Closure { .. }) => c,
1484 _ => return Err(
1485 "net.dial_ws_actor(url, subprotocol, name, on_open, on_message): on_message must be a closure".into(),
1486 ),
1487 };
1488 let program = self.program.clone().ok_or_else(|| {
1489 "net.dial_ws_actor requires a Program reference; use DefaultHandler::with_program".to_string()
1490 })?;
1491 let policy = self.policy.clone();
1492 crate::ws::dial_ws_actor(url, subprotocol, name, on_open, on_message, program, policy)
1493 }
1494 ("chat", "broadcast") => {
1495 let registry = self.chat_registry.as_ref()
1496 .ok_or_else(|| "chat.broadcast called outside a net.serve_ws handler".to_string())?;
1497 let room = expect_str(args.first())?;
1498 let body = expect_str(args.get(1))?;
1499 crate::ws::chat_broadcast(registry, room, body);
1500 Ok(Value::Unit)
1501 }
1502 ("chat", "send") => {
1503 let registry = self.chat_registry.as_ref()
1504 .ok_or_else(|| "chat.send called outside a net.serve_ws handler".to_string())?;
1505 let conn_id = match args.first() {
1506 Some(Value::Int(n)) if *n >= 0 => *n as u64,
1507 _ => return Err("chat.send: conn_id must be non-negative Int".into()),
1508 };
1509 let body = expect_str(args.get(1))?;
1510 Ok(Value::Bool(crate::ws::chat_send(registry, conn_id, body)))
1511 }
1512 ("kv", "open") => {
1513 let path = expect_str(args.first())?.to_string();
1514 if !self.policy.allow_fs_write.is_empty() {
1518 let p = std::path::Path::new(&path);
1519 if !self.policy.allow_fs_write.iter().any(|a| p.starts_with(a)) {
1520 return Ok(err(Value::Str(format!(
1521 "kv.open: `{path}` outside --allow-fs-write").into())));
1522 }
1523 }
1524 match sled::open(&path) {
1525 Ok(db) => {
1526 let handle = next_kv_handle();
1527 kv_registry().lock().unwrap().insert(handle, db);
1528 Ok(ok(Value::Int(handle as i64)))
1529 }
1530 Err(e) => Ok(err(Value::Str(format!("kv.open: {e}").into()))),
1531 }
1532 }
1533 ("kv", "close") => {
1534 let h = expect_kv_handle(args.first())?;
1535 kv_registry().lock().unwrap().remove(h);
1536 Ok(Value::Unit)
1537 }
1538 ("kv", "get") => {
1539 let h = expect_kv_handle(args.first())?;
1540 let key = expect_str(args.get(1))?;
1541 let mut reg = kv_registry().lock().unwrap();
1542 let db = reg.touch_get(h).ok_or_else(|| "kv.get: closed or unknown Kv handle".to_string())?;
1543 match db.get(key.as_bytes()) {
1544 Ok(Some(ivec)) => Ok(some(Value::Bytes(ivec.to_vec()))),
1545 Ok(None) => Ok(none()),
1546 Err(e) => Err(format!("kv.get: {e}")),
1547 }
1548 }
1549 ("kv", "put") => {
1550 let h = expect_kv_handle(args.first())?;
1551 let key = expect_str(args.get(1))?.to_string();
1552 let val = expect_bytes(args.get(2))?.clone();
1553 let mut reg = kv_registry().lock().unwrap();
1554 let db = reg.touch_get(h).ok_or_else(|| "kv.put: closed or unknown Kv handle".to_string())?;
1555 match db.insert(key.as_bytes(), val) {
1556 Ok(_) => Ok(ok(Value::Unit)),
1557 Err(e) => Ok(err(Value::Str(format!("kv.put: {e}").into()))),
1558 }
1559 }
1560 ("kv", "delete") => {
1561 let h = expect_kv_handle(args.first())?;
1562 let key = expect_str(args.get(1))?;
1563 let mut reg = kv_registry().lock().unwrap();
1564 let db = reg.touch_get(h).ok_or_else(|| "kv.delete: closed or unknown Kv handle".to_string())?;
1565 match db.remove(key.as_bytes()) {
1566 Ok(_) => Ok(ok(Value::Unit)),
1567 Err(e) => Ok(err(Value::Str(format!("kv.delete: {e}").into()))),
1568 }
1569 }
1570 ("kv", "contains") => {
1571 let h = expect_kv_handle(args.first())?;
1572 let key = expect_str(args.get(1))?;
1573 let mut reg = kv_registry().lock().unwrap();
1574 let db = reg.touch_get(h).ok_or_else(|| "kv.contains: closed or unknown Kv handle".to_string())?;
1575 match db.contains_key(key.as_bytes()) {
1576 Ok(present) => Ok(Value::Bool(present)),
1577 Err(e) => Err(format!("kv.contains: {e}")),
1578 }
1579 }
1580 ("kv", "list_prefix") => {
1581 let h = expect_kv_handle(args.first())?;
1582 let prefix = expect_str(args.get(1))?;
1583 let mut reg = kv_registry().lock().unwrap();
1584 let db = reg.touch_get(h).ok_or_else(|| "kv.list_prefix: closed or unknown Kv handle".to_string())?;
1585 let mut keys: Vec<Value> = Vec::new();
1586 for kv in db.scan_prefix(prefix.as_bytes()) {
1587 let (k, _) = kv.map_err(|e| format!("kv.list_prefix: {e}"))?;
1588 let s = String::from_utf8_lossy(&k).to_string();
1589 keys.push(Value::Str(s.into()));
1590 }
1591 Ok(Value::List(keys.into()))
1592 }
1593 ("vcs", "put_blob") => {
1601 let content = expect_str(args.first())?.to_string();
1602 match lex_store::Store::open(vcs_store_root())
1603 .and_then(|s| s.put_blob(&content)) {
1604 Ok(sha) => Ok(ok(Value::Str(sha.into()))),
1605 Err(e) => Ok(err(Value::Str(format!("vcs.put_blob: {e}").into()))),
1606 }
1607 }
1608 ("vcs", "get_blob") => {
1609 let sha = expect_str(args.first())?.to_string();
1610 match lex_store::Store::open(vcs_store_root())
1611 .and_then(|s| s.get_blob(&sha)) {
1612 Ok(content) => Ok(ok(Value::Str(content.into()))),
1613 Err(e) => Ok(err(Value::Str(format!("vcs.get_blob: {e}").into()))),
1614 }
1615 }
1616 ("vcs", "has_blob") => {
1617 let sha = expect_str(args.first())?.to_string();
1618 let has = lex_store::Store::open(vcs_store_root())
1619 .map(|s| s.has_blob(&sha)).unwrap_or(false);
1620 Ok(Value::Bool(has))
1621 }
1622 ("vcs", "ref_set") => {
1623 let ns = expect_str(args.first())?.to_string();
1624 let key = expect_str(args.get(1))?.to_string();
1625 let sha = expect_str(args.get(2))?.to_string();
1626 match lex_store::Store::open(vcs_store_root())
1627 .and_then(|s| s.set_blob_ref(&ns, &key, &sha)) {
1628 Ok(()) => Ok(ok(Value::Unit)),
1629 Err(e) => Ok(err(Value::Str(format!("vcs.ref_set: {e}").into()))),
1630 }
1631 }
1632 ("vcs", "ref_get") => {
1633 let ns = expect_str(args.first())?.to_string();
1634 let key = expect_str(args.get(1))?.to_string();
1635 match lex_store::Store::open(vcs_store_root())
1636 .and_then(|s| s.get_blob_ref(&ns, &key)) {
1637 Ok(sha) => Ok(ok(Value::Str(sha.into()))),
1638 Err(e) => Ok(err(Value::Str(format!("vcs.ref_get: {e}").into()))),
1639 }
1640 }
1641 ("sql", "open") => {
1642 let path = expect_str(args.first())?.to_string();
1643 if path.starts_with("postgres://") || path.starts_with("postgresql://") {
1644 match postgres::Client::connect(&path, postgres::NoTls) {
1646 Ok(client) => {
1647 let handle = next_sql_handle();
1648 sql_registry().lock().unwrap().insert(handle, SqlConn::Postgres(client));
1649 Ok(ok(Value::Int(handle as i64)))
1650 }
1651 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.open"))),
1652 }
1653 } else {
1654 if path != ":memory:" && !self.policy.allow_fs_write.is_empty() {
1657 let p = std::path::Path::new(&path);
1658 if !self.policy.allow_fs_write.iter().any(|a| p.starts_with(a)) {
1659 return Ok(err(sql_error(
1660 format!("sql.open: `{path}` outside --allow-fs-write"),
1661 None, None,
1662 )));
1663 }
1664 }
1665 match rusqlite::Connection::open(&path) {
1666 Ok(conn) => {
1667 let handle = next_sql_handle();
1668 sql_registry().lock().unwrap().insert(handle, SqlConn::Sqlite(conn));
1669 Ok(ok(Value::Int(handle as i64)))
1670 }
1671 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.open"))),
1672 }
1673 }
1674 }
1675 ("sql", "close") => {
1676 let h = expect_sql_handle(args.first())?;
1677 sql_registry().lock().unwrap().remove(h);
1678 Ok(Value::Unit)
1679 }
1680 ("sql", "exec") => {
1681 let h = expect_sql_handle(args.first())?;
1682 let stmt = expect_str(args.get(1))?.to_string();
1683 let params = expect_sql_params(args.get(2))?;
1684 let arc = sql_registry().lock().unwrap()
1685 .touch_get(h)
1686 .ok_or_else(|| "sql.exec: closed or unknown Db handle".to_string())?;
1687 let mut conn = arc.lock().unwrap();
1688 match &mut *conn {
1689 SqlConn::Sqlite(c) => {
1690 let bound = sqlite_params(¶ms);
1691 let bind: Vec<&dyn rusqlite::ToSql> =
1692 bound.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
1693 match c.execute(&stmt, rusqlite::params_from_iter(bind.iter())) {
1694 Ok(n) => Ok(ok(Value::Int(n as i64))),
1695 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.exec"))),
1696 }
1697 }
1698 SqlConn::Postgres(c) => {
1699 let pg = pg_param_refs(¶ms);
1700 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
1701 pg.iter().map(|b| b.as_ref()).collect();
1702 match c.execute(pg_rewrite_placeholders(stmt.as_str()).as_str(), &refs) {
1703 Ok(n) => Ok(ok(Value::Int(n as i64))),
1704 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.exec"))),
1705 }
1706 }
1707 }
1708 }
1709 ("sql", "query") => {
1710 let h = expect_sql_handle(args.first())?;
1711 let stmt_str = expect_str(args.get(1))?.to_string();
1712 let params = expect_sql_params(args.get(2))?;
1713 let arc = sql_registry().lock().unwrap()
1714 .touch_get(h)
1715 .ok_or_else(|| "sql.query: closed or unknown Db handle".to_string())?;
1716 let mut conn = arc.lock().unwrap();
1717 Ok(match &mut *conn {
1718 SqlConn::Sqlite(c) => sql_run_query_sqlite(c, &stmt_str, ¶ms),
1719 SqlConn::Postgres(c) => sql_run_query_pg(c, &stmt_str, ¶ms),
1720 })
1721 }
1722 ("sql", "query_iter") => {
1728 let h = expect_sql_handle(args.first())?;
1729 let stmt_str = expect_str(args.get(1))?.to_string();
1730 let params = expect_sql_params(args.get(2))?;
1731 let arc = sql_registry().lock().unwrap()
1732 .touch_get(h)
1733 .ok_or_else(|| "sql.query_iter: closed or unknown Db handle".to_string())?;
1734
1735 let (sender, receiver) = std::sync::mpsc::sync_channel::<Result<Value, String>>(
1739 CURSOR_CHANNEL_CAPACITY,
1740 );
1741 let cursor_h = next_cursor_handle();
1742 cursor_registry().lock().unwrap().insert(cursor_h, receiver);
1743
1744 let arc_for_thread = Arc::clone(&arc);
1745 let is_sqlite = matches!(*arc.lock().unwrap(), SqlConn::Sqlite(_));
1751 std::thread::spawn(move || {
1752 if is_sqlite {
1753 sqlite_cursor_producer(arc_for_thread, stmt_str, params, sender);
1754 } else {
1755 pg_cursor_producer(arc_for_thread, stmt_str, params, sender);
1756 }
1757 });
1758
1759 Ok(ok(Value::Variant {
1760 name: "__IterCursor".into(),
1761 args: vec![Value::Int(cursor_h as i64)],
1762 }))
1763 }
1764 ("sql", "cursor_next") => {
1770 let h = match args.first() {
1771 Some(Value::Int(n)) if *n >= 0 => *n as u64,
1772 _ => return Err("sql.cursor_next: expected cursor handle (Int)".into()),
1773 };
1774 let rx_arc = match cursor_registry().lock().unwrap().touch_get(h) {
1775 Some(a) => a,
1776 None => return Ok(Value::Variant { name: "None".into(), args: vec![] }),
1777 };
1778 let recv_result = {
1783 let rx = match rx_arc.lock() {
1784 Ok(g) => g,
1785 Err(p) => p.into_inner(),
1786 };
1787 rx.recv()
1788 };
1789 match recv_result {
1790 Ok(Ok(row)) => Ok(Value::Variant {
1791 name: "Some".into(),
1792 args: vec![row],
1793 }),
1794 Ok(Err(_)) | Err(_) => {
1795 cursor_registry().lock().unwrap().remove(h);
1799 Ok(Value::Variant { name: "None".into(), args: vec![] })
1800 }
1801 }
1802 }
1803 ("sql", "begin") => {
1808 let h = expect_sql_handle(args.first())?;
1809 let arc = sql_registry().lock().unwrap()
1810 .touch_get(h)
1811 .ok_or_else(|| "sql.begin: closed or unknown Db handle".to_string())?;
1812 let mut conn = arc.lock().unwrap();
1813 match &mut *conn {
1814 SqlConn::Sqlite(c) => match c.execute_batch("BEGIN") {
1815 Ok(()) => Ok(ok(Value::Int(h as i64))),
1816 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.begin"))),
1817 },
1818 SqlConn::Postgres(c) => match c.batch_execute("BEGIN") {
1819 Ok(()) => Ok(ok(Value::Int(h as i64))),
1820 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.begin"))),
1821 },
1822 }
1823 }
1824 ("sql", "commit") => {
1825 let h = expect_sql_handle(args.first())?;
1826 let arc = sql_registry().lock().unwrap()
1827 .touch_get(h)
1828 .ok_or_else(|| "sql.commit: closed or unknown SqlTx handle".to_string())?;
1829 let mut conn = arc.lock().unwrap();
1830 match &mut *conn {
1831 SqlConn::Sqlite(c) => match c.execute_batch("COMMIT") {
1832 Ok(()) => Ok(ok(Value::Unit)),
1833 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.commit"))),
1834 },
1835 SqlConn::Postgres(c) => match c.batch_execute("COMMIT") {
1836 Ok(()) => Ok(ok(Value::Unit)),
1837 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.commit"))),
1838 },
1839 }
1840 }
1841 ("sql", "rollback") => {
1842 let h = expect_sql_handle(args.first())?;
1843 let arc = sql_registry().lock().unwrap()
1844 .touch_get(h)
1845 .ok_or_else(|| "sql.rollback: closed or unknown SqlTx handle".to_string())?;
1846 let mut conn = arc.lock().unwrap();
1847 match &mut *conn {
1848 SqlConn::Sqlite(c) => match c.execute_batch("ROLLBACK") {
1849 Ok(()) => Ok(ok(Value::Unit)),
1850 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.rollback"))),
1851 },
1852 SqlConn::Postgres(c) => match c.batch_execute("ROLLBACK") {
1853 Ok(()) => Ok(ok(Value::Unit)),
1854 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.rollback"))),
1855 },
1856 }
1857 }
1858 ("sql", "exec_tx") => {
1859 let h = expect_sql_handle(args.first())?;
1860 let stmt = expect_str(args.get(1))?.to_string();
1861 let params = expect_sql_params(args.get(2))?;
1862 let arc = sql_registry().lock().unwrap()
1863 .touch_get(h)
1864 .ok_or_else(|| "sql.exec_tx: closed or unknown SqlTx handle".to_string())?;
1865 let mut conn = arc.lock().unwrap();
1866 match &mut *conn {
1867 SqlConn::Sqlite(c) => {
1868 let bound = sqlite_params(¶ms);
1869 let bind: Vec<&dyn rusqlite::ToSql> =
1870 bound.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
1871 match c.execute(&stmt, rusqlite::params_from_iter(bind.iter())) {
1872 Ok(n) => Ok(ok(Value::Int(n as i64))),
1873 Err(e) => Ok(err(sqlite_err_to_sql_error(e, "sql.exec_tx"))),
1874 }
1875 }
1876 SqlConn::Postgres(c) => {
1877 let pg = pg_param_refs(¶ms);
1878 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
1879 pg.iter().map(|b| b.as_ref()).collect();
1880 match c.execute(pg_rewrite_placeholders(stmt.as_str()).as_str(), &refs) {
1881 Ok(n) => Ok(ok(Value::Int(n as i64))),
1882 Err(e) => Ok(err(pg_err_to_sql_error(e, "sql.exec_tx"))),
1883 }
1884 }
1885 }
1886 }
1887 ("sql", "query_tx") => {
1888 let h = expect_sql_handle(args.first())?;
1889 let stmt_str = expect_str(args.get(1))?.to_string();
1890 let params = expect_sql_params(args.get(2))?;
1891 let arc = sql_registry().lock().unwrap()
1892 .touch_get(h)
1893 .ok_or_else(|| "sql.query_tx: closed or unknown SqlTx handle".to_string())?;
1894 let mut conn = arc.lock().unwrap();
1895 Ok(match &mut *conn {
1896 SqlConn::Sqlite(c) => sql_run_query_sqlite(c, &stmt_str, ¶ms),
1897 SqlConn::Postgres(c) => sql_run_query_pg(c, &stmt_str, ¶ms),
1898 })
1899 }
1900 ("sql", "get_str") => Ok(sql_get_col(&args, |v| match v {
1901 Value::Str(s) => Some(Value::Str(s.clone())),
1902 Value::Int(n) => Some(Value::Str(n.to_string().into())),
1903 _ => None,
1904 })?),
1905 ("sql", "get_int") => Ok(sql_get_col(&args, |v| match v {
1906 Value::Int(n) => Some(Value::Int(*n)),
1907 Value::Float(f) => Some(Value::Int(*f as i64)),
1908 _ => None,
1909 })?),
1910 ("sql", "get_float") => Ok(sql_get_col(&args, |v| match v {
1911 Value::Float(f) => Some(Value::Float(*f)),
1912 Value::Int(n) => Some(Value::Float(*n as f64)),
1913 _ => None,
1914 })?),
1915 ("sql", "get_bool") => Ok(sql_get_col(&args, |v| match v {
1916 Value::Bool(b) => Some(Value::Bool(*b)),
1917 Value::Int(n) => Some(Value::Bool(*n != 0)),
1918 _ => None,
1919 })?),
1920
1921 ("redis", "connect") => {
1930 let url = expect_str(args.first())?.to_string();
1931 self.ensure_host_allowed(&url)?;
1932 match redis::Client::open(url.as_str()) {
1933 Ok(client) => match client.get_connection() {
1934 Ok(conn) => {
1935 let handle = next_redis_handle();
1936 redis_registry().lock().unwrap().insert(handle, RedisEntry { url, conn });
1937 Ok(ok(Value::Int(handle as i64)))
1938 }
1939 Err(e) => Ok(err(Value::Str(format!("redis.connect: {e}").into()))),
1940 },
1941 Err(e) => Ok(err(Value::Str(format!("redis.connect: {e}").into()))),
1942 }
1943 }
1944 ("redis", "close") => {
1945 let h = expect_redis_handle(args.first())?;
1946 redis_registry().lock().unwrap().remove(h);
1947 Ok(Value::Unit)
1948 }
1949 ("redis", "get") => {
1950 let h = expect_redis_handle(args.first())?;
1951 let key = expect_str(args.get(1))?.to_string();
1952 let mut reg = redis_registry().lock().unwrap();
1953 let entry = reg.touch_get_mut(h)
1954 .ok_or_else(|| "redis.get: closed or unknown ConnRedis handle".to_string())?;
1955 use redis::Commands;
1956 match entry.conn.get::<_, Option<String>>(&key) {
1957 Ok(Some(v)) => Ok(some(Value::Str(v.into()))),
1958 Ok(None) => Ok(none()),
1959 Err(e) => Err(format!("redis.get: {e}")),
1960 }
1961 }
1962 ("redis", "set") => {
1963 let h = expect_redis_handle(args.first())?;
1964 let key = expect_str(args.get(1))?.to_string();
1965 let val = expect_str(args.get(2))?.to_string();
1966 let mut reg = redis_registry().lock().unwrap();
1967 let entry = reg.touch_get_mut(h)
1968 .ok_or_else(|| "redis.set: closed or unknown ConnRedis handle".to_string())?;
1969 use redis::Commands;
1970 entry.conn.set::<_, _, ()>(&key, &val)
1971 .map_err(|e| format!("redis.set: {e}"))?;
1972 Ok(Value::Unit)
1973 }
1974 ("redis", "set_ex") => {
1975 let h = expect_redis_handle(args.first())?;
1976 let key = expect_str(args.get(1))?.to_string();
1977 let val = expect_str(args.get(2))?.to_string();
1978 let ttl = expect_int(args.get(3))?;
1979 let mut reg = redis_registry().lock().unwrap();
1980 let entry = reg.touch_get_mut(h)
1981 .ok_or_else(|| "redis.set_ex: closed or unknown ConnRedis handle".to_string())?;
1982 use redis::Commands;
1983 entry.conn.set_ex::<_, _, ()>(&key, &val, ttl as u64)
1984 .map_err(|e| format!("redis.set_ex: {e}"))?;
1985 Ok(Value::Unit)
1986 }
1987 ("redis", "del") => {
1988 let h = expect_redis_handle(args.first())?;
1989 let key = expect_str(args.get(1))?.to_string();
1990 let mut reg = redis_registry().lock().unwrap();
1991 let entry = reg.touch_get_mut(h)
1992 .ok_or_else(|| "redis.del: closed or unknown ConnRedis handle".to_string())?;
1993 use redis::Commands;
1994 entry.conn.del::<_, ()>(&key)
1995 .map_err(|e| format!("redis.del: {e}"))?;
1996 Ok(Value::Unit)
1997 }
1998 ("redis", "exists") => {
1999 let h = expect_redis_handle(args.first())?;
2000 let key = expect_str(args.get(1))?.to_string();
2001 let mut reg = redis_registry().lock().unwrap();
2002 let entry = reg.touch_get_mut(h)
2003 .ok_or_else(|| "redis.exists: closed or unknown ConnRedis handle".to_string())?;
2004 use redis::Commands;
2005 let present: bool = entry.conn.exists(&key)
2006 .map_err(|e| format!("redis.exists: {e}"))?;
2007 Ok(Value::Bool(present))
2008 }
2009 ("redis", "expire") => {
2010 let h = expect_redis_handle(args.first())?;
2011 let key = expect_str(args.get(1))?.to_string();
2012 let ttl = expect_int(args.get(2))?;
2013 let mut reg = redis_registry().lock().unwrap();
2014 let entry = reg.touch_get_mut(h)
2015 .ok_or_else(|| "redis.expire: closed or unknown ConnRedis handle".to_string())?;
2016 use redis::Commands;
2017 entry.conn.expire::<_, ()>(&key, ttl)
2018 .map_err(|e| format!("redis.expire: {e}"))?;
2019 Ok(Value::Unit)
2020 }
2021 ("redis", "publish") => {
2022 let h = expect_redis_handle(args.first())?;
2023 let channel = expect_str(args.get(1))?.to_string();
2024 let msg = expect_str(args.get(2))?.to_string();
2025 let mut reg = redis_registry().lock().unwrap();
2026 let entry = reg.touch_get_mut(h)
2027 .ok_or_else(|| "redis.publish: closed or unknown ConnRedis handle".to_string())?;
2028 use redis::Commands;
2029 let n: i64 = entry.conn.publish(&channel, &msg)
2030 .map_err(|e| format!("redis.publish: {e}"))?;
2031 Ok(Value::Int(n))
2032 }
2033 ("redis", "subscribe") => {
2038 let h = expect_redis_handle(args.first())?;
2039 let channel = expect_str(args.get(1))?.to_string();
2040 let closure = match args.into_iter().nth(2) {
2041 Some(c @ Value::Closure { .. }) => c,
2042 _ => return Err("redis.subscribe: handler must be a Closure".into()),
2043 };
2044 let program = self.program.clone()
2045 .ok_or("redis.subscribe: no program; call DefaultHandler::with_program")?;
2046 let policy = self.policy.clone();
2047 let url = redis_registry().lock().unwrap()
2048 .get_url(h)
2049 .ok_or("redis.subscribe: closed or unknown ConnRedis handle")?;
2050 let client = redis::Client::open(url.as_str())
2051 .map_err(|e| format!("redis.subscribe: {e}"))?;
2052 let mut conn = client.get_connection()
2053 .map_err(|e| format!("redis.subscribe: {e}"))?;
2054 let mut pubsub = conn.as_pubsub();
2055 pubsub.subscribe(&channel)
2056 .map_err(|e| format!("redis.subscribe: {e}"))?;
2057 loop {
2058 let msg = pubsub.get_message()
2059 .map_err(|e| format!("redis.subscribe: {e}"))?;
2060 let ch: String = msg.get_channel_name().to_string();
2061 let payload: String = msg.get_payload()
2062 .map_err(|e| format!("redis.subscribe: payload: {e}"))?;
2063 let handler = DefaultHandler::new(policy.clone())
2064 .with_program(Arc::clone(&program));
2065 let mut vm = Vm::with_handler(&program, Box::new(handler));
2066 vm.invoke_closure_value(closure.clone(), vec![
2067 Value::Str(ch.into()),
2068 Value::Str(payload.into()),
2069 ]).map_err(|e| format!("redis.subscribe: handler: {e:?}"))?;
2070 }
2071 }
2072 ("redis", "psubscribe") => {
2073 let h = expect_redis_handle(args.first())?;
2074 let pattern = expect_str(args.get(1))?.to_string();
2075 let closure = match args.into_iter().nth(2) {
2076 Some(c @ Value::Closure { .. }) => c,
2077 _ => return Err("redis.psubscribe: handler must be a Closure".into()),
2078 };
2079 let program = self.program.clone()
2080 .ok_or("redis.psubscribe: no program; call DefaultHandler::with_program")?;
2081 let policy = self.policy.clone();
2082 let url = redis_registry().lock().unwrap()
2083 .get_url(h)
2084 .ok_or("redis.psubscribe: closed or unknown ConnRedis handle")?;
2085 let client = redis::Client::open(url.as_str())
2086 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2087 let mut conn = client.get_connection()
2088 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2089 let mut pubsub = conn.as_pubsub();
2090 pubsub.psubscribe(&pattern)
2091 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2092 loop {
2093 let msg = pubsub.get_message()
2094 .map_err(|e| format!("redis.psubscribe: {e}"))?;
2095 let pat: String = msg.get_pattern()
2096 .ok()
2097 .and_then(|v: Option<String>| v)
2098 .unwrap_or_else(|| pattern.clone());
2099 let ch: String = msg.get_channel_name().to_string();
2100 let payload: String = msg.get_payload()
2101 .map_err(|e| format!("redis.psubscribe: payload: {e}"))?;
2102 let handler = DefaultHandler::new(policy.clone())
2103 .with_program(Arc::clone(&program));
2104 let mut vm = Vm::with_handler(&program, Box::new(handler));
2105 vm.invoke_closure_value(closure.clone(), vec![
2106 Value::Str(pat.into()),
2107 Value::Str(ch.into()),
2108 Value::Str(payload.into()),
2109 ]).map_err(|e| format!("redis.psubscribe: handler: {e:?}"))?;
2110 }
2111 }
2112 ("redis", "lpush") => {
2113 let h = expect_redis_handle(args.first())?;
2114 let key = expect_str(args.get(1))?.to_string();
2115 let val = expect_str(args.get(2))?.to_string();
2116 let mut reg = redis_registry().lock().unwrap();
2117 let entry = reg.touch_get_mut(h)
2118 .ok_or_else(|| "redis.lpush: closed or unknown ConnRedis handle".to_string())?;
2119 use redis::Commands;
2120 let n: i64 = entry.conn.lpush(&key, &val)
2121 .map_err(|e| format!("redis.lpush: {e}"))?;
2122 Ok(Value::Int(n))
2123 }
2124 ("redis", "rpush") => {
2125 let h = expect_redis_handle(args.first())?;
2126 let key = expect_str(args.get(1))?.to_string();
2127 let val = expect_str(args.get(2))?.to_string();
2128 let mut reg = redis_registry().lock().unwrap();
2129 let entry = reg.touch_get_mut(h)
2130 .ok_or_else(|| "redis.rpush: closed or unknown ConnRedis handle".to_string())?;
2131 use redis::Commands;
2132 let n: i64 = entry.conn.rpush(&key, &val)
2133 .map_err(|e| format!("redis.rpush: {e}"))?;
2134 Ok(Value::Int(n))
2135 }
2136 ("redis", "brpop") => {
2137 let h = expect_redis_handle(args.first())?;
2140 let key = expect_str(args.get(1))?.to_string();
2141 let timeout = expect_int(args.get(2))?;
2142 let mut reg = redis_registry().lock().unwrap();
2143 let entry = reg.touch_get_mut(h)
2144 .ok_or_else(|| "redis.brpop: closed or unknown ConnRedis handle".to_string())?;
2145 use redis::Commands;
2146 let result: Option<(String, String)> = entry.conn
2149 .brpop(&key, timeout as f64)
2150 .map_err(|e| format!("redis.brpop: {e}"))?;
2151 match result {
2152 Some((_, v)) => Ok(some(Value::Str(v.into()))),
2153 None => Ok(none()),
2154 }
2155 }
2156 ("redis", "llen") => {
2157 let h = expect_redis_handle(args.first())?;
2158 let key = expect_str(args.get(1))?.to_string();
2159 let mut reg = redis_registry().lock().unwrap();
2160 let entry = reg.touch_get_mut(h)
2161 .ok_or_else(|| "redis.llen: closed or unknown ConnRedis handle".to_string())?;
2162 use redis::Commands;
2163 let n: i64 = entry.conn.llen(&key)
2164 .map_err(|e| format!("redis.llen: {e}"))?;
2165 Ok(Value::Int(n))
2166 }
2167 ("redis", "hset") => {
2168 let h = expect_redis_handle(args.first())?;
2169 let key = expect_str(args.get(1))?.to_string();
2170 let field = expect_str(args.get(2))?.to_string();
2171 let val = expect_str(args.get(3))?.to_string();
2172 let mut reg = redis_registry().lock().unwrap();
2173 let entry = reg.touch_get_mut(h)
2174 .ok_or_else(|| "redis.hset: closed or unknown ConnRedis handle".to_string())?;
2175 use redis::Commands;
2176 entry.conn.hset::<_, _, _, ()>(&key, &field, &val)
2177 .map_err(|e| format!("redis.hset: {e}"))?;
2178 Ok(Value::Unit)
2179 }
2180 ("redis", "hget") => {
2181 let h = expect_redis_handle(args.first())?;
2182 let key = expect_str(args.get(1))?.to_string();
2183 let field = expect_str(args.get(2))?.to_string();
2184 let mut reg = redis_registry().lock().unwrap();
2185 let entry = reg.touch_get_mut(h)
2186 .ok_or_else(|| "redis.hget: closed or unknown ConnRedis handle".to_string())?;
2187 use redis::Commands;
2188 match entry.conn.hget::<_, _, Option<String>>(&key, &field) {
2189 Ok(Some(v)) => Ok(some(Value::Str(v.into()))),
2190 Ok(None) => Ok(none()),
2191 Err(e) => Err(format!("redis.hget: {e}")),
2192 }
2193 }
2194 ("redis", "hdel") => {
2195 let h = expect_redis_handle(args.first())?;
2196 let key = expect_str(args.get(1))?.to_string();
2197 let field = expect_str(args.get(2))?.to_string();
2198 let mut reg = redis_registry().lock().unwrap();
2199 let entry = reg.touch_get_mut(h)
2200 .ok_or_else(|| "redis.hdel: closed or unknown ConnRedis handle".to_string())?;
2201 use redis::Commands;
2202 entry.conn.hdel::<_, _, ()>(&key, &field)
2203 .map_err(|e| format!("redis.hdel: {e}"))?;
2204 Ok(Value::Unit)
2205 }
2206 ("redis", "hgetall") => {
2207 let h = expect_redis_handle(args.first())?;
2208 let key = expect_str(args.get(1))?.to_string();
2209 let mut reg = redis_registry().lock().unwrap();
2210 let entry = reg.touch_get_mut(h)
2211 .ok_or_else(|| "redis.hgetall: closed or unknown ConnRedis handle".to_string())?;
2212 use redis::Commands;
2213 let map: std::collections::HashMap<String, String> = entry.conn
2214 .hgetall(&key)
2215 .map_err(|e| format!("redis.hgetall: {e}"))?;
2216 let pairs: Vec<Value> = map.into_iter()
2217 .map(|(k, v)| Value::Tuple(vec![Value::Str(k.into()), Value::Str(v.into())]))
2218 .collect();
2219 Ok(Value::List(pairs.into()))
2220 }
2221
2222 other => Err(format!("unsupported effect {}.{}", other.0, other.1)),
2226 }
2227 }
2228
2229 fn spawn_for_worker(&self) -> Option<Box<dyn lex_bytecode::vm::EffectHandler + Send>> {
2252 let mut fresh = DefaultHandler::new(self.policy.clone());
2253 fresh.budget_remaining = std::sync::Arc::clone(&self.budget_remaining);
2256 fresh.budget_ceiling = self.budget_ceiling;
2257 fresh.read_root = self.read_root.clone();
2258 fresh.program = self.program.clone();
2259 fresh.chat_registry = self.chat_registry.clone();
2260 fresh.streams = std::sync::Arc::clone(&self.streams);
2265 fresh.next_stream_id = std::sync::Arc::clone(&self.next_stream_id);
2266 fresh.program_args = self.program_args.clone();
2267 Some(Box::new(fresh))
2268 }
2269}
2270
2271pub struct TlsConfig {
2281 pub cert: Vec<u8>,
2282 pub key: Vec<u8>,
2283}
2284
2285fn serve_http(
2286 port: u16,
2287 handler_name: String,
2288 program: Arc<Program>,
2289 policy: Policy,
2290 tls: Option<TlsConfig>,
2291 opts: ServeOpts,
2292) -> Result<Value, String> {
2293 match tls {
2294 None => serve_http_plain(port, handler_name, program, policy, opts),
2295 Some(cfg) => serve_http_tls_legacy(port, handler_name, program, policy, cfg),
2296 }
2297}
2298
2299fn serve_http_plain(
2309 port: u16,
2310 handler_name: String,
2311 program: Arc<Program>,
2312 policy: Policy,
2313 opts: ServeOpts,
2314) -> Result<Value, String> {
2315 use http_body_util::BodyExt as _;
2316 use hyper::server::conn::http1;
2317 use hyper::service::service_fn;
2318 use hyper_util::rt::{TokioExecutor, TokioIo};
2319 use hyper_util::server::conn::auto;
2320 use tokio::net::TcpListener as TokioTcpListener;
2321
2322 let inline_vm = opts.inline_vm;
2323 let http2 = opts.http2;
2324 let host = opts.host.clone();
2325 let rt = tokio::runtime::Builder::new_multi_thread()
2326 .enable_all()
2327 .build()
2328 .map_err(|e| format!("net.serve: tokio runtime: {e}"))?;
2329 rt.block_on(async move {
2330 let listener = TokioTcpListener::bind((host.as_str(), port))
2331 .await
2332 .map_err(|e| format!("net.serve bind {host}:{port}: {e}"))?;
2333 eprintln!(
2334 "net.serve: listening on http://{host}:{port}{}{}",
2335 if inline_vm { " (inline-vm)" } else { "" },
2336 if http2 { " (http1+http2)" } else { "" }
2337 );
2338 loop {
2339 let (stream, _) = listener
2340 .accept()
2341 .await
2342 .map_err(|e| format!("net.serve accept: {e}"))?;
2343 let io = TokioIo::new(stream);
2344 let program = Arc::clone(&program);
2345 let policy = policy.clone();
2346 let handler_name = handler_name.clone();
2347 tokio::spawn(async move {
2348 let program2 = Arc::clone(&program);
2349 let policy2 = policy.clone();
2350 let handler_name2 = handler_name.clone();
2351 let svc = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
2352 let program = Arc::clone(&program2);
2353 let policy = policy2.clone();
2354 let handler_name = handler_name2.clone();
2355 async move {
2356 let (parts, body) = req.into_parts();
2357 let body_bytes = body
2358 .collect()
2359 .await
2360 .map(|c| c.to_bytes())
2361 .unwrap_or_default();
2362 let result = if inline_vm {
2363 let lex_req = build_request_value_parts(&parts, &body_bytes);
2367 let handler = DefaultHandler::new(policy)
2368 .with_program(Arc::clone(&program));
2369 let mut vm = Vm::with_handler(&program, Box::new(handler));
2370 let r = vm.call(&handler_name, vec![lex_req]);
2371 Ok(r.map(|v| unpack_response(&mut vm, &v)))
2374 } else {
2375 tokio::task::spawn_blocking(move || {
2376 let lex_req = build_request_value_parts(&parts, &body_bytes);
2377 let handler = DefaultHandler::new(policy)
2378 .with_program(Arc::clone(&program));
2379 let mut vm = Vm::with_handler(&program, Box::new(handler));
2380 let r = vm.call(&handler_name, vec![lex_req]);
2381 r.map(|v| unpack_response(&mut vm, &v))
2382 })
2383 .await
2384 };
2385 Ok::<_, std::convert::Infallible>(match result {
2386 Ok(Ok(unpacked)) => build_hyper_response(unpacked),
2387 Ok(Err(e)) => error_response(500, &format!("internal error: {e}")),
2388 Err(e) => error_response(500, &format!("task panicked: {e}")),
2389 })
2390 }
2391 });
2392 let result = if http2 {
2393 auto::Builder::new(TokioExecutor::new())
2394 .serve_connection(io, svc)
2395 .await
2396 .map_err(|e| e.to_string())
2397 } else {
2398 http1::Builder::new()
2399 .serve_connection(io, svc)
2400 .await
2401 .map_err(|e| e.to_string())
2402 };
2403 if let Err(e) = result {
2404 eprintln!("net.serve: connection error: {e}");
2405 }
2406 });
2407 }
2408 })
2409}
2410
2411fn serve_http_tls_legacy(
2413 port: u16,
2414 handler_name: String,
2415 program: Arc<Program>,
2416 policy: Policy,
2417 cfg: TlsConfig,
2418) -> Result<Value, String> {
2419 let ssl = tiny_http::SslConfig {
2420 certificate: cfg.cert,
2421 private_key: cfg.key,
2422 };
2423 let server = tiny_http::Server::https(("0.0.0.0", port), ssl)
2424 .map_err(|e| format!("net.serve_tls bind {port}: {e}"))?;
2425 eprintln!("net.serve: listening on https://0.0.0.0:{port}");
2426 for req in server.incoming_requests() {
2427 let program = Arc::clone(&program);
2428 let policy = policy.clone();
2429 let handler_name = handler_name.clone();
2430 std::thread::spawn(move || handle_request_tls(req, program, policy, handler_name));
2431 }
2432 Ok(Value::Unit)
2433}
2434
2435fn handle_request_tls(
2436 mut req: tiny_http::Request,
2437 program: Arc<Program>,
2438 policy: Policy,
2439 handler_name: String,
2440) {
2441 let lex_req = build_request_value_tiny(&mut req);
2442 let handler = DefaultHandler::new(policy).with_program(Arc::clone(&program));
2443 let mut vm = Vm::with_handler(&program, Box::new(handler));
2444 match vm.call(&handler_name, vec![lex_req]) {
2445 Ok(resp) => {
2446 let (status, body, headers) = unpack_response(&mut vm, &resp);
2452 respond_with_body_tls(req, status, body, headers);
2453 }
2454 Err(e) => {
2455 let response = tiny_http::Response::from_string(format!("internal error: {e}"))
2456 .with_status_code(500);
2457 let _ = req.respond(response);
2458 }
2459 }
2460}
2461
2462fn serve_http_fn(
2467 port: u16,
2468 closure: Value,
2469 program: Arc<Program>,
2470 policy: Policy,
2471 opts: ServeOpts,
2472) -> Result<Value, String> {
2473 use http_body_util::BodyExt as _;
2474 use hyper::server::conn::http1;
2475 use hyper::service::service_fn;
2476 use hyper_util::rt::{TokioExecutor, TokioIo};
2477 use hyper_util::server::conn::auto;
2478 use tokio::net::TcpListener as TokioTcpListener;
2479
2480 let inline_vm = opts.inline_vm;
2481 let http2 = opts.http2;
2482 let host = opts.host.clone();
2483 let rt = tokio::runtime::Builder::new_multi_thread()
2484 .enable_all()
2485 .build()
2486 .map_err(|e| format!("net.serve_fn: tokio runtime: {e}"))?;
2487 rt.block_on(async move {
2488 let listener = TokioTcpListener::bind((host.as_str(), port))
2489 .await
2490 .map_err(|e| format!("net.serve_fn bind {host}:{port}: {e}"))?;
2491 eprintln!(
2492 "net.serve_fn: listening on http://{host}:{port}{}{}",
2493 if inline_vm { " (inline-vm)" } else { "" },
2494 if http2 { " (http1+http2)" } else { "" }
2495 );
2496 loop {
2497 let (stream, _) = listener
2498 .accept()
2499 .await
2500 .map_err(|e| format!("net.serve_fn accept: {e}"))?;
2501 let io = TokioIo::new(stream);
2502 let program = Arc::clone(&program);
2503 let policy = policy.clone();
2504 let closure = closure.clone();
2505 tokio::spawn(async move {
2506 let program2 = Arc::clone(&program);
2507 let policy2 = policy.clone();
2508 let closure2 = closure.clone();
2509 let svc = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
2510 let program = Arc::clone(&program2);
2511 let policy = policy2.clone();
2512 let closure = closure2.clone();
2513 async move {
2514 let (parts, body) = req.into_parts();
2515 let body_bytes = body
2516 .collect()
2517 .await
2518 .map(|c| c.to_bytes())
2519 .unwrap_or_default();
2520 let result = if inline_vm {
2521 let lex_req = build_request_value_parts(&parts, &body_bytes);
2522 let handler = DefaultHandler::new(policy)
2523 .with_program(Arc::clone(&program));
2524 let mut vm = Vm::with_handler(&program, Box::new(handler));
2525 let scope = vm.enter_request_scope();
2532 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2533 let r = r.map(|v| unpack_response(&mut vm, &v));
2537 vm.exit_request_scope(scope);
2538 Ok(r)
2539 } else {
2540 tokio::task::spawn_blocking(move || {
2541 let lex_req = build_request_value_parts(&parts, &body_bytes);
2542 let handler = DefaultHandler::new(policy)
2543 .with_program(Arc::clone(&program));
2544 let mut vm = Vm::with_handler(&program, Box::new(handler));
2545 let scope = vm.enter_request_scope();
2546 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2547 let r = r.map(|v| unpack_response(&mut vm, &v));
2548 vm.exit_request_scope(scope);
2549 r
2550 })
2551 .await
2552 };
2553 Ok::<_, std::convert::Infallible>(match result {
2554 Ok(Ok(unpacked)) => build_hyper_response(unpacked),
2555 Ok(Err(e)) => error_response(500, &format!("internal error: {e}")),
2556 Err(e) => error_response(500, &format!("task panicked: {e}")),
2557 })
2558 }
2559 });
2560 let result = if http2 {
2561 auto::Builder::new(TokioExecutor::new())
2562 .serve_connection(io, svc)
2563 .await
2564 .map_err(|e| e.to_string())
2565 } else {
2566 http1::Builder::new()
2567 .serve_connection(io, svc)
2568 .await
2569 .map_err(|e| e.to_string())
2570 };
2571 if let Err(e) = result {
2572 eprintln!("net.serve_fn: connection error: {e}");
2573 }
2574 });
2575 }
2576 })
2577}
2578
2579#[derive(Clone, Debug)]
2583pub(crate) enum RouteSeg {
2584 Literal(String),
2585 Param(String),
2588}
2589
2590fn compile_path_pattern(pat: &str) -> Result<Vec<RouteSeg>, String> {
2594 if pat.is_empty() {
2595 return Err("path pattern must be non-empty (use \"/\" for the root)".into());
2596 }
2597 if !pat.starts_with('/') {
2598 return Err(format!("path pattern must start with '/' (got {pat:?})"));
2599 }
2600 let mut segs = Vec::new();
2601 for raw in pat.split('/') {
2602 if let Some(name) = raw.strip_prefix(':') {
2603 if name.is_empty() {
2604 return Err(format!(
2605 ":-segment in pattern {pat:?} must have a name (e.g. :id)"
2606 ));
2607 }
2608 segs.push(RouteSeg::Param(name.to_string()));
2609 } else {
2610 segs.push(RouteSeg::Literal(raw.to_string()));
2611 }
2612 }
2613 Ok(segs)
2614}
2615
2616fn match_path_pattern(
2622 segs: &[RouteSeg],
2623 path: &str,
2624) -> Option<std::collections::BTreeMap<lex_bytecode::MapKey, Value>> {
2625 let path_segs: Vec<&str> = path.split('/').collect();
2626 if path_segs.len() != segs.len() {
2627 return None;
2628 }
2629 let mut params = std::collections::BTreeMap::new();
2630 for (pat, p) in segs.iter().zip(path_segs.iter()) {
2631 match pat {
2632 RouteSeg::Literal(lit) => {
2633 if lit != p {
2634 return None;
2635 }
2636 }
2637 RouteSeg::Param(name) => {
2638 params.insert(
2639 lex_bytecode::MapKey::Str(name.clone()),
2640 Value::Str((*p).into()),
2641 );
2642 }
2643 }
2644 }
2645 Some(params)
2646}
2647
2648fn decode_routes_arg(
2653 v: Value,
2654) -> Result<Vec<(String, Vec<RouteSeg>, Value)>, String> {
2655 let list = match v {
2656 Value::List(xs) => xs,
2657 _ => return Err("net.serve_routed: routes must be a List".into()),
2658 };
2659 let mut out = Vec::with_capacity(list.len());
2660 for (i, item) in list.into_iter().enumerate() {
2661 let tup = match item {
2662 Value::Tuple(xs) if xs.len() == 3 => xs,
2663 other => return Err(format!(
2664 "net.serve_routed: route #{i} must be a (method, pattern, handler) 3-tuple, got {other:?}"
2665 )),
2666 };
2667 let mut it = tup.into_iter();
2668 let method_raw = match it.next() {
2669 Some(Value::Str(s)) => s.to_string(),
2670 _ => return Err(format!("net.serve_routed: route #{i} method must be Str")),
2671 };
2672 let method = if method_raw == "*" { method_raw } else { method_raw.to_uppercase() };
2674 let pattern = match it.next() {
2675 Some(Value::Str(s)) => s.to_string(),
2676 _ => return Err(format!("net.serve_routed: route #{i} path-pattern must be Str")),
2677 };
2678 let segs = compile_path_pattern(&pattern)
2679 .map_err(|e| format!("net.serve_routed: route #{i} ({pattern:?}): {e}"))?;
2680 let closure = match it.next() {
2681 Some(c @ Value::Closure { .. }) => c,
2682 _ => return Err(format!("net.serve_routed: route #{i} handler must be a closure")),
2683 };
2684 out.push((method, segs, closure));
2685 }
2686 Ok(out)
2687}
2688
2689pub(crate) fn dispatch_route<'a>(
2694 routes: &'a [(String, Vec<RouteSeg>, Value)],
2695 req_method: &str,
2696 req_path: &str,
2697) -> Option<(&'a Value, std::collections::BTreeMap<lex_bytecode::MapKey, Value>)> {
2698 let req_method_upper = req_method.to_ascii_uppercase();
2699 for (m, segs, closure) in routes {
2700 if m != "*" && m != &req_method_upper {
2701 continue;
2702 }
2703 if let Some(params) = match_path_pattern(segs, req_path) {
2704 return Some((closure, params));
2705 }
2706 }
2707 None
2708}
2709
2710pub(crate) fn stamp_path_params(
2714 req: &mut Value,
2715 params: std::collections::BTreeMap<lex_bytecode::MapKey, Value>,
2716) {
2717 if let Value::Record { fields: rec, .. } = req {
2718 rec.insert("path_params".into(), Value::Map(params));
2719 }
2720}
2721
2722fn serve_http_routed(
2728 port: u16,
2729 routes: Vec<(String, Vec<RouteSeg>, Value)>,
2730 fallback: Value,
2731 program: Arc<Program>,
2732 policy: Policy,
2733 opts: ServeOpts,
2734) -> Result<Value, String> {
2735 use http_body_util::BodyExt as _;
2736 use hyper::server::conn::http1;
2737 use hyper::service::service_fn;
2738 use hyper_util::rt::{TokioExecutor, TokioIo};
2739 use hyper_util::server::conn::auto;
2740 use tokio::net::TcpListener as TokioTcpListener;
2741
2742 let inline_vm = opts.inline_vm;
2743 let http2 = opts.http2;
2744 let host = opts.host.clone();
2745 let routes = Arc::new(routes);
2746 let rt = tokio::runtime::Builder::new_multi_thread()
2747 .enable_all()
2748 .build()
2749 .map_err(|e| format!("net.serve_routed: tokio runtime: {e}"))?;
2750 rt.block_on(async move {
2751 let listener = TokioTcpListener::bind((host.as_str(), port))
2752 .await
2753 .map_err(|e| format!("net.serve_routed bind {host}:{port}: {e}"))?;
2754 eprintln!(
2755 "net.serve_routed: listening on http://{host}:{port} ({} routes{}{})",
2756 routes.len(),
2757 if inline_vm { ", inline-vm" } else { "" },
2758 if http2 { ", http1+http2" } else { "" }
2759 );
2760 loop {
2761 let (stream, _) = listener
2762 .accept()
2763 .await
2764 .map_err(|e| format!("net.serve_routed accept: {e}"))?;
2765 let io = TokioIo::new(stream);
2766 let program = Arc::clone(&program);
2767 let policy = policy.clone();
2768 let routes = Arc::clone(&routes);
2769 let fallback = fallback.clone();
2770 tokio::spawn(async move {
2771 let program2 = Arc::clone(&program);
2772 let policy2 = policy.clone();
2773 let routes2 = Arc::clone(&routes);
2774 let fallback2 = fallback.clone();
2775 let svc = service_fn(move |req: hyper::Request<hyper::body::Incoming>| {
2776 let program = Arc::clone(&program2);
2777 let policy = policy2.clone();
2778 let routes = Arc::clone(&routes2);
2779 let fallback = fallback2.clone();
2780 async move {
2781 let (parts, body) = req.into_parts();
2782 let body_bytes = body
2783 .collect()
2784 .await
2785 .map(|c| c.to_bytes())
2786 .unwrap_or_default();
2787 let method = parts.method.as_str().to_string();
2788 let path = match parts.uri.path() {
2789 "" => "/".to_string(),
2790 p => p.to_string(),
2791 };
2792 let result = if inline_vm {
2793 let mut lex_req = build_request_value_parts(&parts, &body_bytes);
2794 let (closure, params) = match dispatch_route(&routes, &method, &path) {
2795 Some((c, p)) => (c.clone(), p),
2796 None => (fallback.clone(), std::collections::BTreeMap::new()),
2797 };
2798 stamp_path_params(&mut lex_req, params);
2799 let handler = DefaultHandler::new(policy)
2800 .with_program(Arc::clone(&program));
2801 let mut vm = Vm::with_handler(&program, Box::new(handler));
2802 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2803 Ok(r.map(|v| unpack_response(&mut vm, &v)))
2806 } else {
2807 tokio::task::spawn_blocking(move || {
2808 let mut lex_req = build_request_value_parts(&parts, &body_bytes);
2809 let (closure, params) = match dispatch_route(&routes, &method, &path) {
2810 Some((c, p)) => (c.clone(), p),
2811 None => (fallback.clone(), std::collections::BTreeMap::new()),
2812 };
2813 stamp_path_params(&mut lex_req, params);
2814 let handler = DefaultHandler::new(policy)
2815 .with_program(Arc::clone(&program));
2816 let mut vm = Vm::with_handler(&program, Box::new(handler));
2817 let r = vm.invoke_closure_value(closure, vec![lex_req]);
2818 r.map(|v| unpack_response(&mut vm, &v))
2819 })
2820 .await
2821 };
2822 Ok::<_, std::convert::Infallible>(match result {
2823 Ok(Ok(unpacked)) => build_hyper_response(unpacked),
2824 Ok(Err(e)) => error_response(500, &format!("internal error: {e}")),
2825 Err(e) => error_response(500, &format!("task panicked: {e}")),
2826 })
2827 }
2828 });
2829 let result = if http2 {
2830 auto::Builder::new(TokioExecutor::new())
2831 .serve_connection(io, svc)
2832 .await
2833 .map_err(|e| e.to_string())
2834 } else {
2835 http1::Builder::new()
2836 .serve_connection(io, svc)
2837 .await
2838 .map_err(|e| e.to_string())
2839 };
2840 if let Err(e) = result {
2841 eprintln!("net.serve_routed: connection error: {e}");
2842 }
2843 });
2844 }
2845 })
2846}
2847
2848fn env_inline_vm() -> bool {
2853 match std::env::var("LEX_NET_INLINE_VM") {
2854 Ok(v) => {
2855 let s = v.trim().to_ascii_lowercase();
2856 s == "1" || s == "true"
2857 }
2858 Err(_) => false,
2859 }
2860}
2861
2862#[derive(Debug, Clone)]
2868pub(crate) struct ServeOpts {
2869 pub(crate) http2: bool,
2870 pub(crate) inline_vm: bool,
2871 pub(crate) host: String,
2872}
2873
2874impl ServeOpts {
2875 fn from_env() -> Self {
2879 Self {
2880 http2: env_http2(),
2881 inline_vm: env_inline_vm(),
2882 host: "0.0.0.0".to_string(),
2883 }
2884 }
2885
2886 fn lex_defaults() -> Self {
2891 Self {
2892 http2: false,
2893 inline_vm: false,
2894 host: "0.0.0.0".to_string(),
2895 }
2896 }
2897
2898 fn to_value(&self) -> Value {
2900 let mut rec = indexmap::IndexMap::new();
2901 rec.insert("http2".to_string(), Value::Bool(self.http2));
2902 rec.insert("inline_vm".to_string(), Value::Bool(self.inline_vm));
2903 rec.insert("host".to_string(), Value::Str(self.host.clone().into()));
2904 Value::record_dynamic(rec)
2905 }
2906}
2907
2908fn decode_serve_opts(v: &Value) -> Result<ServeOpts, String> {
2913 let rec = match v {
2914 Value::Record { fields: r, .. } => r,
2915 other => return Err(format!("opts must be a Record, got {other:?}")),
2916 };
2917 let http2 = match rec.get("http2") {
2918 Some(Value::Bool(b)) => *b,
2919 _ => return Err("opts.http2 must be Bool".into()),
2920 };
2921 let inline_vm = match rec.get("inline_vm") {
2922 Some(Value::Bool(b)) => *b,
2923 _ => return Err("opts.inline_vm must be Bool".into()),
2924 };
2925 let host = match rec.get("host") {
2926 Some(Value::Str(s)) => s.to_string(),
2927 _ => return Err("opts.host must be Str".into()),
2928 };
2929 Ok(ServeOpts { http2, inline_vm, host })
2930}
2931
2932fn make_tls_config_value(cert_pem: Vec<u8>, key_pem: Vec<u8>) -> Value {
2942 let mut rec = indexmap::IndexMap::new();
2943 rec.insert("cert".into(), Value::Bytes(cert_pem));
2944 rec.insert("key".into(), Value::Bytes(key_pem));
2945 Value::record_dynamic(rec)
2946}
2947
2948#[cfg(feature = "quic")]
2949fn decode_tls_config(v: &Value) -> Result<crate::quic::QuicTls, String> {
2950 let rec = match v {
2951 Value::Record { fields: r, .. } => r,
2952 other => return Err(format!("TlsConfig: expected Record, got {other:?}")),
2953 };
2954 let cert = match rec.get("cert") {
2955 Some(Value::Bytes(b)) => b.to_vec(),
2956 _ => return Err("TlsConfig.cert: must be Bytes".into()),
2957 };
2958 let key = match rec.get("key") {
2959 Some(Value::Bytes(b)) => b.to_vec(),
2960 _ => return Err("TlsConfig.key: must be Bytes".into()),
2961 };
2962 Ok(crate::quic::QuicTls { cert_pem: cert, key_pem: key })
2963}
2964
2965fn dispatch_tls_from_pem_files(
2966 handler: &DefaultHandler,
2967 args: Vec<Value>,
2968) -> Result<Value, String> {
2969 let cert_path = expect_str(args.first())?.to_string();
2970 let key_path = expect_str(args.get(1))?.to_string();
2971 let cert_resolved = handler.resolve_read_path(&cert_path);
2972 let key_resolved = handler.resolve_read_path(&key_path);
2973 if !handler.policy.allow_fs_read.is_empty() {
2974 let allowed = |p: &std::path::Path| -> bool {
2975 handler.policy.allow_fs_read.iter().any(|a| p.starts_with(a))
2976 };
2977 if !allowed(&cert_resolved) {
2978 return Ok(err(Value::Str(
2979 format!("tls.from_pem_files: cert `{cert_path}` outside --allow-fs-read").into(),
2980 )));
2981 }
2982 if !allowed(&key_resolved) {
2983 return Ok(err(Value::Str(
2984 format!("tls.from_pem_files: key `{key_path}` outside --allow-fs-read").into(),
2985 )));
2986 }
2987 }
2988 let cert = match std::fs::read(&cert_resolved) {
2989 Ok(b) => b,
2990 Err(e) => return Ok(err(Value::Str(format!("read cert {cert_path}: {e}").into()))),
2991 };
2992 let key = match std::fs::read(&key_resolved) {
2993 Ok(b) => b,
2994 Err(e) => return Ok(err(Value::Str(format!("read key {key_path}: {e}").into()))),
2995 };
2996 Ok(ok(make_tls_config_value(cert, key)))
2997}
2998
2999#[cfg(feature = "quic")]
3000fn dispatch_tls_self_signed(args: Vec<Value>) -> Result<Value, String> {
3001 let hostname = expect_str(args.first())?.to_string();
3002 match crate::quic::self_signed_pem(&hostname) {
3003 Ok((cert, key)) => Ok(ok(make_tls_config_value(cert, key))),
3004 Err(e) => Ok(err(Value::Str(format!("tls.self_signed: {e}").into()))),
3005 }
3006}
3007
3008#[cfg(not(feature = "quic"))]
3009fn dispatch_tls_self_signed(_args: Vec<Value>) -> Result<Value, String> {
3010 Ok(err(Value::Str(
3011 "tls.self_signed: lex-runtime was compiled without the `quic` feature (needed for rcgen)".into(),
3012 )))
3013}
3014
3015impl DefaultHandler {
3016 #[cfg(feature = "quic")]
3017 fn dispatch_serve_quic_named(&self, args: Vec<Value>) -> Result<Value, String> {
3018 let port = match args.first() {
3019 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
3020 _ => return Err("net.serve_quic(port, tls, handler): port must be Int 0..=65535".into()),
3021 };
3022 let tls = decode_tls_config(args.get(1)
3023 .ok_or_else(|| "net.serve_quic(port, tls, handler): missing tls".to_string())?)?;
3024 let handler_name = expect_str(args.get(2))?.to_string();
3025 let program = self.program.clone()
3026 .ok_or_else(|| "net.serve_quic requires a Program reference; use DefaultHandler::with_program".to_string())?;
3027 let policy = self.policy.clone();
3028 crate::quic::serve_http3_named(port, handler_name, tls, program, policy, ServeOpts::from_env())
3029 }
3030
3031 #[cfg(feature = "quic")]
3032 fn dispatch_serve_quic_fn(&self, args: Vec<Value>) -> Result<Value, String> {
3033 let port = match args.first() {
3034 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
3035 _ => return Err("net.serve_quic_fn(port, tls, handler): port must be Int 0..=65535".into()),
3036 };
3037 let tls = decode_tls_config(args.get(1)
3038 .ok_or_else(|| "net.serve_quic_fn(port, tls, handler): missing tls".to_string())?)?;
3039 let closure = match args.into_iter().nth(2) {
3040 Some(c @ Value::Closure { .. }) => c,
3041 _ => return Err("net.serve_quic_fn(port, tls, handler): handler must be a closure".into()),
3042 };
3043 let program = self.program.clone()
3044 .ok_or_else(|| "net.serve_quic_fn requires a Program reference; use DefaultHandler::with_program".to_string())?;
3045 let policy = self.policy.clone();
3046 crate::quic::serve_http3_fn(port, closure, tls, program, policy, ServeOpts::from_env())
3047 }
3048
3049 #[cfg(feature = "quic")]
3050 fn dispatch_serve_quic_routed(&self, args: Vec<Value>) -> Result<Value, String> {
3051 let port = match args.first() {
3052 Some(Value::Int(n)) if (0..=65535).contains(n) => *n as u16,
3053 _ => return Err("net.serve_quic_routed(port, tls, routes, fallback): port must be Int 0..=65535".into()),
3054 };
3055 let tls = decode_tls_config(args.get(1)
3056 .ok_or_else(|| "net.serve_quic_routed(port, tls, routes, fallback): missing tls".to_string())?)?;
3057 let routes_val = args.get(2).cloned()
3058 .ok_or_else(|| "net.serve_quic_routed(port, tls, routes, fallback): missing routes".to_string())?;
3059 let fallback = match args.into_iter().nth(3) {
3060 Some(c @ Value::Closure { .. }) => c,
3061 _ => return Err("net.serve_quic_routed(port, tls, routes, fallback): fallback must be a closure".into()),
3062 };
3063 let routes = decode_routes_arg(routes_val)?;
3064 let program = self.program.clone()
3065 .ok_or_else(|| "net.serve_quic_routed requires a Program reference; use DefaultHandler::with_program".to_string())?;
3066 let policy = self.policy.clone();
3067 crate::quic::serve_http3_routed(port, routes, fallback, tls, program, policy, ServeOpts::from_env())
3068 }
3069
3070 #[cfg(not(feature = "quic"))]
3071 fn dispatch_serve_quic_named(&self, _args: Vec<Value>) -> Result<Value, String> {
3072 Err("net.serve_quic: lex-runtime was compiled without the `quic` feature (needed for quinn + h3)".into())
3073 }
3074 #[cfg(not(feature = "quic"))]
3075 fn dispatch_serve_quic_fn(&self, _args: Vec<Value>) -> Result<Value, String> {
3076 Err("net.serve_quic_fn: lex-runtime was compiled without the `quic` feature (needed for quinn + h3)".into())
3077 }
3078 #[cfg(not(feature = "quic"))]
3079 fn dispatch_serve_quic_routed(&self, _args: Vec<Value>) -> Result<Value, String> {
3080 Err("net.serve_quic_routed: lex-runtime was compiled without the `quic` feature (needed for quinn + h3)".into())
3081 }
3082}
3083
3084fn env_http2() -> bool {
3094 match std::env::var("LEX_NET_HTTP2") {
3095 Ok(v) => {
3096 let s = v.trim().to_ascii_lowercase();
3097 s == "1" || s == "true"
3098 }
3099 Err(_) => false,
3100 }
3101}
3102
3103pub(crate) fn build_request_value_parts(
3105 parts: &hyper::http::request::Parts,
3106 body: &bytes::Bytes,
3107) -> Value {
3108 let method = parts.method.as_str().to_string();
3109 let path = parts.uri.path().to_string();
3117 let query = parts.uri.query().map(str::to_string).unwrap_or_default();
3118 let mut headers_map = std::collections::BTreeMap::new();
3119 for (name, val) in &parts.headers {
3120 if let Ok(v) = val.to_str() {
3121 headers_map.insert(
3122 lex_bytecode::MapKey::Str(name.as_str().to_ascii_lowercase()),
3123 Value::Str(v.to_string().into()),
3124 );
3125 }
3126 }
3127 let body_str = String::from_utf8_lossy(body).into_owned();
3128 let mut rec = indexmap::IndexMap::new();
3129 rec.insert("method".into(), Value::Str(method.into()));
3130 rec.insert("path".into(), Value::Str(path.into()));
3131 rec.insert("query".into(), Value::Str(query.into()));
3132 rec.insert("body".into(), Value::Str(body_str.into()));
3133 rec.insert("headers".into(), Value::Map(headers_map));
3134 rec.insert("path_params".into(), Value::Map(std::collections::BTreeMap::new()));
3135 Value::record_dynamic(rec)
3136}
3137
3138fn build_request_value_tiny(req: &mut tiny_http::Request) -> Value {
3140 let method = format!("{:?}", req.method()).to_uppercase();
3141 let url = req.url().to_string();
3142 let (path, query) = match url.split_once('?') {
3143 Some((p, q)) => (p.to_string(), q.to_string()),
3144 None => (url, String::new()),
3145 };
3146 let mut headers_map = std::collections::BTreeMap::new();
3147 for h in req.headers() {
3148 headers_map.insert(
3149 lex_bytecode::MapKey::Str(h.field.as_str().as_str().to_ascii_lowercase()),
3150 Value::Str(h.value.as_str().to_string().into()),
3151 );
3152 }
3153 let mut body = String::new();
3154 let _ = req.as_reader().read_to_string(&mut body);
3155 let mut rec = indexmap::IndexMap::new();
3156 rec.insert("method".into(), Value::Str(method.into()));
3157 rec.insert("path".into(), Value::Str(path.into()));
3158 rec.insert("query".into(), Value::Str(query.into()));
3159 rec.insert("body".into(), Value::Str(body.into()));
3160 rec.insert("headers".into(), Value::Map(headers_map));
3161 rec.insert("path_params".into(), Value::Map(std::collections::BTreeMap::new()));
3162 Value::record_dynamic(rec)
3163}
3164
3165pub(crate) fn unpack_response(vm: &mut Vm, v: &Value) -> UnpackedResponse {
3166 if !matches!(v, Value::Record { .. } | Value::ArenaRecord { .. }) {
3172 return (
3173 500,
3174 ResponseBodyOut::Str(format!("handler returned non-record: {v:?}")),
3175 vec![],
3176 );
3177 }
3178
3179 let status = vm.get_record_field(v, "status").and_then(|s| match s {
3180 Value::Int(n) => Some(n as u16),
3181 _ => None,
3182 }).unwrap_or(200);
3183
3184 let body = match vm.get_record_field(v, "body") {
3188 Some(Value::Variant { name, mut args }) if args.len() == 1 => {
3189 let inner = args.pop().unwrap();
3190 match (name.as_str(), inner) {
3191 ("BodyStr", Value::Str(s)) => ResponseBodyOut::Str(s.to_string()),
3193 ("BodyStream", iter_v) => {
3194 let drained = materialize_lazy_iter(vm, iter_v);
3195 ResponseBodyOut::TextChunks(drain_iter_str(&drained))
3196 }
3197 ("BodyBytes", iter_v) => {
3198 let drained = materialize_lazy_iter(vm, iter_v);
3199 ResponseBodyOut::BytesChunks(drain_iter_bytes(&drained))
3200 }
3201 _ => ResponseBodyOut::Str(String::new()),
3202 }
3203 }
3204 Some(Value::Str(s)) => ResponseBodyOut::Str(s.to_string()),
3210 _ => ResponseBodyOut::Str(String::new()),
3211 };
3212
3213 let headers: Vec<(String, String)> = match vm.get_record_field(v, "headers") {
3214 Some(Value::Map(hmap)) => hmap.iter().filter_map(|(k, val)| {
3215 if let (lex_bytecode::MapKey::Str(name), Value::Str(s)) = (k, val) {
3216 Some((name.clone(), s.to_string()))
3217 } else {
3218 None
3219 }
3220 }).collect(),
3221 _ => vec![],
3222 };
3223
3224 (status, body, headers)
3225}
3226
3227type HyperRespBody =
3228 http_body_util::combinators::BoxBody<bytes::Bytes, std::convert::Infallible>;
3229
3230fn build_hyper_response(
3240 (status, body, headers): UnpackedResponse,
3241) -> hyper::Response<HyperRespBody> {
3242 use http_body_util::BodyExt as _;
3243 let boxed_body: HyperRespBody = match body {
3244 ResponseBodyOut::Str(s) => {
3245 http_body_util::Full::new(bytes::Bytes::from(s.into_bytes())).boxed()
3246 }
3247 ResponseBodyOut::TextChunks(chunks) | ResponseBodyOut::BytesChunks(chunks) => {
3248 HyperChunkedBody::from(chunks).boxed()
3249 }
3250 };
3251 let mut builder = hyper::Response::builder().status(status);
3252 for (name, val) in headers {
3253 builder = builder.header(name, val);
3254 }
3255 builder
3256 .body(boxed_body)
3257 .unwrap_or_else(|_| error_response(500, "response build error"))
3258}
3259
3260fn error_response(status: u16, msg: &str) -> hyper::Response<HyperRespBody> {
3261 use http_body_util::BodyExt as _;
3262 hyper::Response::builder()
3263 .status(status)
3264 .body(
3265 http_body_util::Full::new(bytes::Bytes::from(msg.to_owned()))
3266 .boxed(),
3267 )
3268 .unwrap_or_else(|_| {
3269 use http_body_util::BodyExt as _;
3270 hyper::Response::new(http_body_util::Empty::new().map_err(|e| match e {}).boxed())
3271 })
3272}
3273
3274struct HyperChunkedBody {
3277 chunks: std::collections::VecDeque<Vec<u8>>,
3278}
3279
3280impl From<Vec<Vec<u8>>> for HyperChunkedBody {
3281 fn from(chunks: Vec<Vec<u8>>) -> Self {
3282 Self {
3283 chunks: chunks.into_iter().filter(|c| !c.is_empty()).collect(),
3284 }
3285 }
3286}
3287
3288impl hyper::body::Body for HyperChunkedBody {
3289 type Data = bytes::Bytes;
3290 type Error = std::convert::Infallible;
3291
3292 fn poll_frame(
3293 mut self: std::pin::Pin<&mut Self>,
3294 _cx: &mut std::task::Context<'_>,
3295 ) -> std::task::Poll<Option<Result<hyper::body::Frame<Self::Data>, Self::Error>>> {
3296 match self.chunks.pop_front() {
3297 Some(chunk) => std::task::Poll::Ready(Some(Ok(hyper::body::Frame::data(
3298 bytes::Bytes::from(chunk),
3299 )))),
3300 None => std::task::Poll::Ready(None),
3301 }
3302 }
3303}
3304
3305fn respond_with_body_tls(
3309 req: tiny_http::Request,
3310 status: u16,
3311 body: ResponseBodyOut,
3312 headers: Vec<(String, String)>,
3313) {
3314 let tiny_headers: Vec<tiny_http::Header> = headers
3315 .into_iter()
3316 .filter_map(|(name, val)| format!("{name}: {val}").parse::<tiny_http::Header>().ok())
3317 .collect();
3318 match body {
3319 ResponseBodyOut::Str(s) => {
3320 let mut response = tiny_http::Response::from_string(s).with_status_code(status);
3321 for h in tiny_headers {
3322 response.add_header(h);
3323 }
3324 let _ = req.respond(response);
3325 }
3326 ResponseBodyOut::TextChunks(chunks) | ResponseBodyOut::BytesChunks(chunks) => {
3327 let reader = ChunkReader::new(chunks);
3328 let response = tiny_http::Response::new(
3329 tiny_http::StatusCode(status),
3330 tiny_headers,
3331 reader,
3332 None,
3333 None,
3334 );
3335 let _ = req.respond(response);
3336 }
3337 }
3338}
3339
3340pub(crate) type UnpackedResponse = (u16, ResponseBodyOut, Vec<(String, String)>);
3350
3351pub(crate) enum ResponseBodyOut {
3352 Str(String),
3353 TextChunks(Vec<Vec<u8>>),
3357 BytesChunks(Vec<Vec<u8>>),
3360}
3361
3362fn drain_iter_str(v: &Value) -> Vec<Vec<u8>> {
3375 match v {
3376 Value::Variant { name, args }
3377 if name == "__IterEager" && args.len() == 2 =>
3378 {
3379 if let (Value::List(items), Value::Int(idx)) = (&args[0], &args[1]) {
3380 items.iter().skip(*idx as usize).filter_map(|item| {
3381 if let Value::Str(s) = item { Some(s.as_bytes().to_vec()) } else { None }
3382 }).collect()
3383 } else {
3384 Vec::new()
3385 }
3386 }
3387 _ => Vec::new(),
3388 }
3389}
3390
3391fn drain_iter_bytes(v: &Value) -> Vec<Vec<u8>> {
3395 match v {
3396 Value::Variant { name, args }
3397 if name == "__IterEager" && args.len() == 2 =>
3398 {
3399 if let (Value::List(items), Value::Int(idx)) = (&args[0], &args[1]) {
3400 items.iter().skip(*idx as usize).filter_map(|item| {
3401 if let Value::List(ints) = item {
3402 Some(ints.iter().filter_map(|i| match i {
3403 Value::Int(n) => Some((*n & 0xff) as u8),
3404 _ => None,
3405 }).collect::<Vec<u8>>())
3406 } else {
3407 None
3408 }
3409 }).collect()
3410 } else {
3411 Vec::new()
3412 }
3413 }
3414 _ => Vec::new(),
3415 }
3416}
3417
3418fn materialize_lazy_iter(vm: &mut Vm, v: Value) -> Value {
3431 let mut current = v;
3432 let mut items: Vec<Value> = Vec::new();
3433 loop {
3434 match current {
3435 Value::Variant { name, args } if name == "__IterLazy" && args.len() == 2 => {
3436 let seed = args[0].clone();
3437 let step = args[1].clone();
3438 match vm.invoke_closure_value(step.clone(), vec![seed]) {
3439 Ok(Value::Variant { name: opt, args: opt_args })
3440 if opt == "None" =>
3441 {
3442 let _ = opt_args;
3443 break;
3444 }
3445 Ok(Value::Variant { name: opt, args: opt_args })
3446 if opt == "Some" && opt_args.len() == 1 =>
3447 {
3448 if let Value::Tuple(pair) = &opt_args[0] {
3449 if pair.len() == 2 {
3450 items.push(pair[0].clone());
3451 current = Value::Variant {
3452 name: "__IterLazy".to_string(),
3453 args: vec![pair[1].clone(), step],
3454 };
3455 continue;
3456 }
3457 }
3458 break;
3460 }
3461 _ => break,
3462 }
3463 }
3464 other => {
3467 if items.is_empty() {
3468 return other;
3469 }
3470 let _ = other;
3473 break;
3474 }
3475 }
3476 }
3477 Value::Variant {
3478 name: "__IterEager".to_string(),
3479 args: vec![
3480 Value::List(items.into_iter().collect()),
3481 Value::Int(0),
3482 ],
3483 }
3484}
3485
3486
3487struct ChunkReader {
3493 chunks: std::collections::VecDeque<Vec<u8>>,
3494 cursor: usize,
3495}
3496
3497impl ChunkReader {
3498 fn new(chunks: Vec<Vec<u8>>) -> Self {
3499 Self {
3500 chunks: chunks.into_iter().filter(|c| !c.is_empty()).collect(),
3501 cursor: 0,
3502 }
3503 }
3504}
3505
3506impl std::io::Read for ChunkReader {
3507 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
3508 loop {
3509 let Some(front) = self.chunks.front() else {
3510 return Ok(0);
3511 };
3512 let remaining = &front[self.cursor..];
3513 if remaining.is_empty() {
3514 self.chunks.pop_front();
3515 self.cursor = 0;
3516 continue;
3517 }
3518 let n = remaining.len().min(buf.len());
3519 buf[..n].copy_from_slice(&remaining[..n]);
3520 self.cursor += n;
3521 if self.cursor >= front.len() {
3522 self.chunks.pop_front();
3523 self.cursor = 0;
3524 }
3525 return Ok(n);
3526 }
3527 }
3528}
3529
3530fn http_request(method: &str, url: &str, body: Option<&str>) -> Value {
3536 use std::time::Duration;
3537 let agent: ureq::Agent = ureq::Agent::config_builder()
3542 .timeout_connect(Some(Duration::from_secs(10)))
3543 .timeout_recv_body(Some(Duration::from_secs(30)))
3544 .timeout_send_body(Some(Duration::from_secs(10)))
3545 .http_status_as_error(false)
3546 .build()
3547 .into();
3548 let resp = match (method, body) {
3549 ("GET", _) => agent.get(url).call(),
3550 ("POST", Some(b)) => agent.post(url).send(b),
3551 ("POST", None) => agent.post(url).send(""),
3552 (m, _) => return err_value(format!("unsupported method: {m}")),
3553 };
3554 match resp {
3555 Ok(mut r) => {
3556 let status = r.status().as_u16();
3557 let body = r.body_mut().read_to_string().unwrap_or_default();
3558 if (200..300).contains(&status) {
3559 Value::Variant { name: "Ok".into(), args: vec![Value::Str(body.into())] }
3560 } else {
3561 err_value(format!("status {status}: {body}"))
3562 }
3563 }
3564 Err(e) => err_value(format!("transport: {e}")),
3565 }
3566}
3567
3568fn http_stream_agent() -> ureq::Agent {
3575 use std::time::Duration;
3576 ureq::Agent::config_builder()
3577 .timeout_global(Some(Duration::from_secs(600)))
3578 .http_status_as_error(false)
3579 .build()
3580 .into()
3581}
3582
3583fn http_agent(timeout_ms: Option<u64>) -> ureq::Agent {
3596 use std::time::Duration;
3597 match timeout_ms {
3598 Some(ms) => ureq::Agent::config_builder()
3599 .timeout_global(Some(Duration::from_millis(ms)))
3600 .http_status_as_error(false)
3601 .build()
3602 .into(),
3603 None => ureq::Agent::config_builder()
3604 .timeout_connect(Some(Duration::from_secs(10)))
3605 .timeout_recv_body(Some(Duration::from_secs(30)))
3606 .timeout_send_body(Some(Duration::from_secs(10)))
3607 .http_status_as_error(false)
3608 .build()
3609 .into(),
3610 }
3611}
3612
3613fn http_error_value(e: ureq::Error) -> Value {
3617 let (ctor, payload): (&str, Option<String>) = match &e {
3618 ureq::Error::Timeout(_) => ("TimeoutError", None),
3619 ureq::Error::Tls(s) => ("TlsError", Some((*s).into())),
3620 ureq::Error::Pem(p) => ("TlsError", Some(format!("{p}"))),
3621 ureq::Error::Rustls(r) => ("TlsError", Some(format!("{r}"))),
3622 _ => ("NetworkError", Some(format!("{e}"))),
3623 };
3624 let args = match payload { Some(s) => vec![Value::Str(s.into())], None => vec![] };
3625 let inner = Value::Variant { name: ctor.into(), args };
3626 Value::Variant { name: "Err".into(), args: vec![inner] }
3627}
3628
3629fn http_decode_err(msg: String) -> Value {
3630 let inner = Value::Variant {
3631 name: "DecodeError".into(),
3632 args: vec![Value::Str(msg.into())],
3633 };
3634 Value::Variant { name: "Err".into(), args: vec![inner] }
3635}
3636
3637fn http_send_simple(
3642 method: &str,
3643 url: &str,
3644 body: Option<Vec<u8>>,
3645 content_type: &str,
3646 timeout_ms: Option<u64>,
3647) -> Value {
3648 http_send_full(method, url, body, content_type, &[], timeout_ms)
3649}
3650
3651fn http_send_full(
3652 method: &str,
3653 url: &str,
3654 body: Option<Vec<u8>>,
3655 content_type: &str,
3656 headers: &[(String, String)],
3657 timeout_ms: Option<u64>,
3658) -> Value {
3659 let agent = http_agent(timeout_ms);
3660 let method_upper = method.to_ascii_uppercase();
3666 let body_bytes: Vec<u8> = body.unwrap_or_default();
3667 let resp = match method_upper.as_str() {
3668 "GET" => {
3673 let mut req = agent.get(url);
3674 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3675 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3676 req.call()
3677 }
3678 "HEAD" => {
3679 let mut req = agent.head(url);
3680 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3681 req.call()
3682 }
3683 "DELETE" => {
3684 let mut req = agent.delete(url);
3685 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3686 req.call()
3687 }
3688 "POST" => {
3693 let mut req = agent.post(url);
3694 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3695 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3696 req.send(&body_bytes[..])
3697 }
3698 "PUT" => {
3699 let mut req = agent.put(url);
3700 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3701 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3702 req.send(&body_bytes[..])
3703 }
3704 "PATCH" => {
3705 let mut req = agent.patch(url);
3706 if !content_type.is_empty() { req = req.header("content-type", content_type); }
3707 for (k, v) in headers { req = req.header(k.as_str(), v.as_str()); }
3708 req.send(&body_bytes[..])
3709 }
3710 m => {
3711 return http_decode_err(format!("unsupported method: {m}"));
3712 }
3713 };
3714 match resp {
3715 Ok(mut r) => {
3716 let status = r.status().as_u16() as i64;
3717 let headers_map = collect_response_headers(r.headers());
3718 let body_bytes = match r.body_mut().with_config().limit(10 * 1024 * 1024).read_to_vec() {
3719 Ok(b) => b,
3720 Err(e) => return http_decode_err(format!("body read: {e}")),
3721 };
3722 let mut rec = indexmap::IndexMap::new();
3723 rec.insert("status".into(), Value::Int(status));
3724 rec.insert("headers".into(), Value::Map(headers_map));
3725 rec.insert("body".into(), Value::Bytes(body_bytes));
3726 Value::Variant { name: "Ok".into(), args: vec![Value::record_dynamic(rec)] }
3727 }
3728 Err(e) => http_error_value(e),
3729 }
3730}
3731
3732fn collect_response_headers(
3733 headers: &ureq::http::HeaderMap,
3734) -> std::collections::BTreeMap<lex_bytecode::MapKey, Value> {
3735 let mut out = std::collections::BTreeMap::new();
3736 for (name, value) in headers.iter() {
3737 let v = value.to_str().unwrap_or("").to_string();
3738 out.insert(lex_bytecode::MapKey::Str(name.as_str().to_string()), Value::Str(v.into()));
3739 }
3740 out
3741}
3742
3743fn http_send_record(handler: &DefaultHandler, req: &indexmap::IndexMap<smol_str::SmolStr, Value>) -> Value {
3747 let method = match req.get("method") {
3748 Some(Value::Str(s)) => s.to_string(),
3749 _ => return http_decode_err("HttpRequest.method must be Str".into()),
3750 };
3751 let url = match req.get("url") {
3752 Some(Value::Str(s)) => s.to_string(),
3753 _ => return http_decode_err("HttpRequest.url must be Str".into()),
3754 };
3755 if let Err(e) = handler.ensure_host_allowed(&url) {
3756 return http_decode_err(e);
3757 }
3758 let body = match req.get("body") {
3759 Some(Value::Variant { name, args }) if name == "None" => None,
3760 Some(Value::Variant { name, args }) if name == "Some" => match args.as_slice() {
3761 [Value::Bytes(b)] => Some(b.clone()),
3762 _ => return http_decode_err("HttpRequest.body Some payload must be Bytes".into()),
3763 },
3764 _ => return http_decode_err("HttpRequest.body must be Option[Bytes]".into()),
3765 };
3766 let timeout_ms = match req.get("timeout_ms") {
3767 Some(Value::Variant { name, .. }) if name == "None" => None,
3768 Some(Value::Variant { name, args }) if name == "Some" => match args.as_slice() {
3769 [Value::Int(n)] if *n >= 0 => Some(*n as u64),
3770 _ => return http_decode_err(
3771 "HttpRequest.timeout_ms Some payload must be a non-negative Int".into()),
3772 },
3773 _ => return http_decode_err("HttpRequest.timeout_ms must be Option[Int]".into()),
3774 };
3775 let headers: Vec<(String, String)> = match req.get("headers") {
3776 Some(Value::Map(m)) => m.iter().filter_map(|(k, v)| {
3777 let kk = match k { lex_bytecode::MapKey::Str(s) => s.clone(), _ => return None };
3778 let vv = match v { Value::Str(s) => s.to_string(), _ => return None };
3779 Some((kk, vv))
3780 }).collect(),
3781 _ => return http_decode_err("HttpRequest.headers must be Map[Str, Str]".into()),
3782 };
3783 http_send_full(&method, &url, body, "", &headers, timeout_ms)
3784}
3785
3786fn expect_record(v: Option<&Value>) -> Result<&indexmap::IndexMap<smol_str::SmolStr, Value>, String> {
3787 match v {
3788 Some(Value::Record { fields: r, .. }) => Ok(r),
3789 Some(other) => Err(format!("expected Record, got {other:?}")),
3790 None => Err("missing Record argument".into()),
3791 }
3792}
3793
3794fn err_value(msg: String) -> Value {
3795 Value::Variant { name: "Err".into(), args: vec![Value::Str(msg.into())] }
3796}
3797
3798fn expect_str(v: Option<&Value>) -> Result<&str, String> {
3799 match v {
3800 Some(Value::Str(s)) => Ok(s),
3801 Some(other) => Err(format!("expected Str arg, got {other:?}")),
3802 None => Err("missing argument".into()),
3803 }
3804}
3805
3806fn expect_int(v: Option<&Value>) -> Result<i64, String> {
3807 match v {
3808 Some(Value::Int(n)) => Ok(*n),
3809 Some(other) => Err(format!("expected Int arg, got {other:?}")),
3810 None => Err("missing argument".into()),
3811 }
3812}
3813
3814fn ok(v: Value) -> Value {
3815 Value::Variant { name: "Ok".into(), args: vec![v] }
3816}
3817fn err(v: Value) -> Value {
3818 Value::Variant { name: "Err".into(), args: vec![v] }
3819}
3820
3821fn vcs_store_root() -> std::path::PathBuf {
3824 if let Ok(p) = std::env::var("LEX_STORE_ROOT") {
3825 return std::path::PathBuf::from(p);
3826 }
3827 let home = std::env::var("HOME")
3828 .map(std::path::PathBuf::from)
3829 .unwrap_or_else(|_| std::path::PathBuf::from("."));
3830 home.join(".lex/store")
3831}
3832
3833fn http_stream_lines_impl(handler: &DefaultHandler, url: &str, headers_val: &Value, body: &str) -> Value {
3846 let body_bytes = body.as_bytes().to_vec();
3847 let agent = http_stream_agent();
3850 let mut req = agent.post(url);
3851 if let Value::Map(headers) = headers_val {
3852 for (k, v) in headers {
3853 let key_str = match k {
3854 lex_bytecode::MapKey::Str(s) => s.as_str(),
3855 _ => continue,
3856 };
3857 if let Value::Str(val) = v {
3858 req = req.header(key_str, val.as_str());
3859 }
3860 }
3861 }
3862 match req.send(&body_bytes[..]) {
3863 Ok(resp) => {
3864 use std::io::BufRead;
3865 let reader = std::io::BufReader::new(resp.into_body().into_reader());
3866 let lines = reader
3869 .lines()
3870 .map_while(Result::ok)
3871 .map(|l| decode_unicode_escapes(&l));
3872 let handle = handler.register_stream(lines);
3873 ok(stream_handle_value(handle))
3874 }
3875 Err(e) => err(Value::Str(format!("http.stream_lines: {e}").into())),
3876 }
3877}
3878
3879fn decode_unicode_escapes(s: &str) -> String {
3880 let mut result = String::with_capacity(s.len());
3881 let mut chars = s.chars().peekable();
3882 while let Some(c) = chars.next() {
3883 if c != '\\' {
3884 result.push(c);
3885 continue;
3886 }
3887 match chars.peek() {
3888 Some('u') => {
3889 chars.next();
3890 let hex: String = (0..4).filter_map(|_| chars.next()).collect();
3891 if hex.len() == 4 {
3892 if let Ok(n) = u32::from_str_radix(&hex, 16) {
3893 if let Some(ch) = char::from_u32(n) {
3894 result.push(ch);
3895 continue;
3896 }
3897 }
3898 }
3899 result.push('\\');
3900 result.push('u');
3901 result.push_str(&hex);
3902 }
3903 _ => result.push(c),
3904 }
3905 }
3906 result
3907}
3908
3909fn sql_error(message: impl Into<String>, code: Option<String>, detail: Option<String>) -> Value {
3913 let some = |s: String| Value::Variant { name: "Some".into(), args: vec![Value::Str(s.into())] };
3914 let none = || Value::Variant { name: "None".into(), args: vec![] };
3915 let mut rec = indexmap::IndexMap::new();
3916 let msg: String = message.into();
3917 rec.insert("message".into(), Value::Str(msg.into()));
3918 rec.insert("code".into(), match code {
3919 Some(c) => some(c),
3920 None => none(),
3921 });
3922 rec.insert("detail".into(), match detail {
3923 Some(d) => some(d),
3924 None => none(),
3925 });
3926 Value::record_dynamic(rec)
3927}
3928
3929fn sqlite_err_to_sql_error(e: rusqlite::Error, op: &str) -> Value {
3939 let message = format!("{op}: {e}");
3940 match &e {
3941 rusqlite::Error::SqliteFailure(ffi, detail_opt) => {
3942 sql_error(
3943 message,
3944 Some(sqlite_extended_code_name(ffi.extended_code)),
3945 detail_opt.clone(),
3946 )
3947 }
3948 rusqlite::Error::SqlInputError { error, msg, .. } => {
3949 sql_error(
3950 message,
3951 Some(sqlite_extended_code_name(error.extended_code)),
3952 Some(msg.clone()),
3953 )
3954 }
3955 _ => sql_error(message, None, None),
3956 }
3957}
3958
3959fn sqlite_extended_code_name(code: i32) -> String {
3965 use rusqlite::ffi::*;
3966 let s = match code {
3967 SQLITE_BUSY => "SQLITE_BUSY",
3968 SQLITE_LOCKED => "SQLITE_LOCKED",
3969 SQLITE_READONLY => "SQLITE_READONLY",
3970 SQLITE_IOERR => "SQLITE_IOERR",
3971 SQLITE_CORRUPT => "SQLITE_CORRUPT",
3972 SQLITE_NOTFOUND => "SQLITE_NOTFOUND",
3973 SQLITE_FULL => "SQLITE_FULL",
3974 SQLITE_CANTOPEN => "SQLITE_CANTOPEN",
3975 SQLITE_PROTOCOL => "SQLITE_PROTOCOL",
3976 SQLITE_SCHEMA => "SQLITE_SCHEMA",
3977 SQLITE_TOOBIG => "SQLITE_TOOBIG",
3978 SQLITE_CONSTRAINT => "SQLITE_CONSTRAINT",
3979 SQLITE_CONSTRAINT_CHECK => "SQLITE_CONSTRAINT_CHECK",
3980 SQLITE_CONSTRAINT_FOREIGNKEY => "SQLITE_CONSTRAINT_FOREIGNKEY",
3981 SQLITE_CONSTRAINT_NOTNULL => "SQLITE_CONSTRAINT_NOTNULL",
3982 SQLITE_CONSTRAINT_PRIMARYKEY => "SQLITE_CONSTRAINT_PRIMARYKEY",
3983 SQLITE_CONSTRAINT_TRIGGER => "SQLITE_CONSTRAINT_TRIGGER",
3984 SQLITE_CONSTRAINT_UNIQUE => "SQLITE_CONSTRAINT_UNIQUE",
3985 SQLITE_CONSTRAINT_VTAB => "SQLITE_CONSTRAINT_VTAB",
3986 SQLITE_CONSTRAINT_ROWID => "SQLITE_CONSTRAINT_ROWID",
3987 SQLITE_MISMATCH => "SQLITE_MISMATCH",
3988 SQLITE_RANGE => "SQLITE_RANGE",
3989 SQLITE_NOTADB => "SQLITE_NOTADB",
3990 SQLITE_AUTH => "SQLITE_AUTH",
3991 _ => return format!("SQLITE_ERROR_{code}"),
3992 };
3993 s.to_string()
3994}
3995
3996fn pg_err_to_sql_error(e: postgres::Error, op: &str) -> Value {
4000 let message = format!("{op}: {e}");
4001 let code = e.as_db_error().map(|db| db.code().code().to_string());
4002 let detail = e.as_db_error().and_then(|db| db.detail().map(|s| s.to_string()));
4003 sql_error(message, code, detail)
4004}
4005
4006impl DefaultHandler {
4007 fn dispatch_call_mcp(&mut self, args: Vec<Value>) -> Value {
4013 let server = match args.first() {
4014 Some(Value::Str(s)) => s.clone(),
4015 _ => return err(Value::Str(
4016 "agent.call_mcp(server, tool, args_json): server must be Str".into())),
4017 };
4018 let tool = match args.get(1) {
4019 Some(Value::Str(s)) => s.clone(),
4020 _ => return err(Value::Str(
4021 "agent.call_mcp(server, tool, args_json): tool must be Str".into())),
4022 };
4023 let args_json = match args.get(2) {
4024 Some(Value::Str(s)) => s.clone(),
4025 _ => return err(Value::Str(
4026 "agent.call_mcp(server, tool, args_json): args_json must be Str".into())),
4027 };
4028 let parsed: serde_json::Value = match serde_json::from_str(&args_json) {
4029 Ok(v) => v,
4030 Err(e) => return err(Value::Str(format!(
4031 "agent.call_mcp: args_json is not valid JSON: {e}").into())),
4032 };
4033 match self.mcp_clients.call(&server, &tool, parsed) {
4034 Ok(result) => ok(Value::Str(
4035 serde_json::to_string(&result).unwrap_or_else(|_| "null".into()).into())),
4036 Err(e) => err(Value::Str(e.into())),
4037 }
4038 }
4039
4040 fn dispatch_cloud_stream(&mut self, args: Vec<Value>) -> Value {
4046 let _prompt = match args.first() {
4047 Some(Value::Str(s)) => s.clone(),
4048 _ => return err(Value::Str(
4049 "agent.cloud_stream(prompt): prompt must be Str".into())),
4050 };
4051 let chunks: Vec<String> = match std::env::var("LEX_LLM_STREAM_FIXTURE") {
4052 Ok(v) => v.split('|').map(|s| s.to_string()).collect(),
4053 Err(_) => return err(Value::Str(
4054 "agent.cloud_stream: live streaming not yet implemented; \
4055 set LEX_LLM_STREAM_FIXTURE='chunk1|chunk2|…' for tests".into())),
4056 };
4057 let handle = self.register_stream(chunks.into_iter());
4058 ok(stream_handle_value(handle))
4059 }
4060
4061 fn dispatch_stream_next(&mut self, args: Vec<Value>) -> Value {
4067 let handle = match args.first().and_then(stream_handle_id) {
4068 Some(h) => h,
4069 None => return Value::Variant { name: "None".into(), args: vec![] },
4070 };
4071 let mut streams = match self.streams.lock() {
4072 Ok(g) => g,
4073 Err(_) => return Value::Variant { name: "None".into(), args: vec![] },
4074 };
4075 match streams.get_mut(&handle).and_then(|it| it.next()) {
4076 Some(chunk) => some(Value::Str(chunk.into())),
4077 None => {
4078 streams.remove(&handle);
4079 Value::Variant { name: "None".into(), args: vec![] }
4080 }
4081 }
4082 }
4083
4084 fn dispatch_stream_collect(&mut self, args: Vec<Value>) -> Value {
4089 let handle = match args.first().and_then(stream_handle_id) {
4090 Some(h) => h,
4091 None => return Value::List(std::collections::VecDeque::new()),
4092 };
4093 let mut iter = {
4094 let mut streams = match self.streams.lock() {
4095 Ok(g) => g,
4096 Err(_) => return Value::List(std::collections::VecDeque::new()),
4097 };
4098 match streams.remove(&handle) {
4099 Some(it) => it,
4100 None => return Value::List(std::collections::VecDeque::new()),
4101 }
4102 };
4103 let mut out: std::collections::VecDeque<Value> = std::collections::VecDeque::new();
4104 for chunk in iter.by_ref() {
4105 out.push_back(Value::Str(chunk.into()));
4106 }
4107 Value::List(out)
4108 }
4109
4110 fn register_stream<I>(&self, iter: I) -> String
4114 where
4115 I: Iterator<Item = String> + Send + 'static,
4116 {
4117 let id = self
4118 .next_stream_id
4119 .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
4120 let handle = format!("stream_{id}");
4121 if let Ok(mut streams) = self.streams.lock() {
4122 streams.insert(handle.clone(), Box::new(iter));
4123 }
4124 handle
4125 }
4126}
4127
4128fn stream_handle_value(handle: String) -> Value {
4133 Value::Variant {
4134 name: "__StreamHandle".into(),
4135 args: vec![Value::Str(handle.into())],
4136 }
4137}
4138
4139fn stream_handle_id(v: &Value) -> Option<String> {
4143 match v {
4144 Value::Variant { name, args } if name == "__StreamHandle" => match args.first() {
4145 Some(Value::Str(h)) => Some(h.to_string()),
4146 _ => None,
4147 },
4148 _ => None,
4149 }
4150}
4151
4152fn dispatch_llm_local(args: Vec<Value>) -> Value {
4157 let prompt = match args.first() {
4158 Some(Value::Str(s)) => s.clone(),
4159 _ => return err(Value::Str(
4160 "agent.local_complete(prompt): prompt must be Str".into())),
4161 };
4162 match crate::llm::local_complete(&prompt) {
4163 Ok(text) => ok(Value::Str(text.into())),
4164 Err(e) => err(Value::Str(e.into())),
4165 }
4166}
4167
4168fn dispatch_llm_cloud(args: Vec<Value>) -> Value {
4175 let prompt = match args.first() {
4176 Some(Value::Str(s)) => s.clone(),
4177 _ => return err(Value::Str(
4178 "agent.cloud_complete(prompt): prompt must be Str".into())),
4179 };
4180 match crate::llm::cloud_complete(&prompt) {
4181 Ok(text) => ok(Value::Str(text.into())),
4182 Err(e) => err(Value::Str(e.into())),
4183 }
4184}
4185
4186fn some(v: Value) -> Value {
4187 Value::Variant { name: "Some".into(), args: vec![v] }
4188}
4189fn none() -> Value {
4190 Value::Variant { name: "None".into(), args: vec![] }
4191}
4192
4193fn expect_bytes(v: Option<&Value>) -> Result<&Vec<u8>, String> {
4194 match v {
4195 Some(Value::Bytes(b)) => Ok(b),
4196 Some(other) => Err(format!("expected Bytes arg, got {other:?}")),
4197 None => Err("missing argument".into()),
4198 }
4199}
4200
4201fn expect_kv_handle(v: Option<&Value>) -> Result<u64, String> {
4202 match v {
4203 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4204 Some(other) => Err(format!("expected Kv handle (Int), got {other:?}")),
4205 None => Err("missing Kv argument".into()),
4206 }
4207}
4208
4209fn expect_sql_handle(v: Option<&Value>) -> Result<u64, String> {
4210 match v {
4211 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4212 Some(other) => Err(format!("expected Db handle (Int), got {other:?}")),
4213 None => Err("missing Db argument".into()),
4214 }
4215}
4216
4217#[allow(dead_code)]
4218fn expect_str_list(v: Option<&Value>) -> Result<Vec<String>, String> {
4219 match v {
4220 Some(Value::List(items)) => items.iter().map(|x| match x {
4221 Value::Str(s) => Ok(s.to_string()),
4222 other => Err(format!("expected List[Str] element, got {other:?}")),
4223 }).collect(),
4224 Some(other) => Err(format!("expected List[Str], got {other:?}")),
4225 None => Err("missing List[Str] argument".into()),
4226 }
4227}
4228
4229fn expect_sql_params(v: Option<&Value>) -> Result<Vec<SqlParamValue>, String> {
4232 let items = match v {
4233 Some(Value::List(xs)) => xs,
4234 Some(other) => return Err(format!("expected List[SqlParam], got {other:?}")),
4235 None => return Err("missing params argument".into()),
4236 };
4237 items.iter().map(|item| {
4238 match item {
4239 Value::Variant { name, args } => match name.as_str() {
4240 "PStr" => match args.first() {
4241 Some(Value::Str(s)) => Ok(SqlParamValue::Text(s.to_string())),
4242 _ => Err("PStr requires a Str argument".into()),
4243 },
4244 "PInt" => match args.first() {
4245 Some(Value::Int(n)) => Ok(SqlParamValue::Integer(*n)),
4246 _ => Err("PInt requires an Int argument".into()),
4247 },
4248 "PFloat" => match args.first() {
4249 Some(Value::Float(f)) => Ok(SqlParamValue::Real(*f)),
4250 _ => Err("PFloat requires a Float argument".into()),
4251 },
4252 "PBool" => match args.first() {
4253 Some(Value::Bool(b)) => Ok(SqlParamValue::Bool(*b)),
4254 _ => Err("PBool requires a Bool argument".into()),
4255 },
4256 "PNull" => Ok(SqlParamValue::Null),
4257 other => Err(format!("unknown SqlParam constructor `{other}`")),
4258 },
4259 Value::Str(s) => Ok(SqlParamValue::Text(s.to_string())),
4261 other => Err(format!("expected SqlParam variant, got {other:?}")),
4262 }
4263 }).collect()
4264}
4265
4266fn sqlite_params(params: &[SqlParamValue]) -> Vec<rusqlite::types::Value> {
4268 params.iter().map(|p| match p {
4269 SqlParamValue::Text(s) => rusqlite::types::Value::Text(s.clone()),
4270 SqlParamValue::Integer(n) => rusqlite::types::Value::Integer(*n),
4271 SqlParamValue::Real(f) => rusqlite::types::Value::Real(*f),
4272 SqlParamValue::Bool(b) => rusqlite::types::Value::Integer(*b as i64),
4273 SqlParamValue::Null => rusqlite::types::Value::Null,
4274 }).collect()
4275}
4276
4277fn pg_rewrite_placeholders(sql: &str) -> String {
4283 let mut out = String::with_capacity(sql.len() + 8);
4284 let mut n: u32 = 0;
4285 let mut in_str = false;
4286 let mut chars = sql.chars().peekable();
4287 while let Some(c) = chars.next() {
4288 match c {
4289 '\'' => {
4290 out.push(c);
4291 if in_str {
4292 if chars.peek() == Some(&'\'') {
4294 out.push(chars.next().unwrap());
4295 } else {
4296 in_str = false;
4297 }
4298 } else {
4299 in_str = true;
4300 }
4301 }
4302 '?' if !in_str => {
4303 n += 1;
4304 out.push('$');
4305 out.push_str(&n.to_string());
4306 }
4307 _ => out.push(c),
4308 }
4309 }
4310 out
4311}
4312
4313#[cfg(test)]
4314mod pg_placeholder_tests {
4315 use super::pg_rewrite_placeholders;
4316
4317 #[test]
4318 fn rewrites_positional_placeholders() {
4319 assert_eq!(
4320 pg_rewrite_placeholders(
4321 "INSERT INTO events(id, kind, parent, payload_json, ts_ms) VALUES (?, ?, ?, ?, ?) ON CONFLICT(id) DO NOTHING"
4322 ),
4323 "INSERT INTO events(id, kind, parent, payload_json, ts_ms) VALUES ($1, $2, $3, $4, $5) ON CONFLICT(id) DO NOTHING"
4324 );
4325 assert_eq!(
4326 pg_rewrite_placeholders("SELECT * FROM t WHERE a=? AND b=?"),
4327 "SELECT * FROM t WHERE a=$1 AND b=$2"
4328 );
4329 }
4330
4331 #[test]
4332 fn leaves_question_marks_inside_string_literals() {
4333 assert_eq!(
4334 pg_rewrite_placeholders("INSERT INTO t VALUES (?, 'lit?', ?)"),
4335 "INSERT INTO t VALUES ($1, 'lit?', $2)"
4336 );
4337 }
4338
4339 #[test]
4340 fn handles_escaped_quotes_in_literals() {
4341 assert_eq!(
4342 pg_rewrite_placeholders("UPDATE t SET note='it''s ok?' WHERE id=?"),
4343 "UPDATE t SET note='it''s ok?' WHERE id=$1"
4344 );
4345 }
4346
4347 #[test]
4348 fn no_placeholders_is_unchanged() {
4349 assert_eq!(pg_rewrite_placeholders("SELECT 1"), "SELECT 1");
4350 }
4351}
4352
4353#[derive(Debug)]
4360struct PgFloatParam(f64);
4361
4362impl postgres::types::ToSql for PgFloatParam {
4363 fn to_sql(
4364 &self,
4365 ty: &postgres::types::Type,
4366 out: &mut bytes::BytesMut,
4367 ) -> Result<postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>> {
4368 use bytes::BufMut;
4369 match *ty {
4370 postgres::types::Type::FLOAT4 => out.put_f32(self.0 as f32),
4371 _ => out.put_f64(self.0),
4372 }
4373 Ok(postgres::types::IsNull::No)
4374 }
4375
4376 fn accepts(ty: &postgres::types::Type) -> bool {
4377 matches!(*ty, postgres::types::Type::FLOAT4 | postgres::types::Type::FLOAT8)
4378 }
4379
4380 postgres::types::to_sql_checked!();
4381}
4382
4383fn pg_param_refs(params: &[SqlParamValue]) -> Vec<Box<dyn postgres::types::ToSql + Sync>> {
4385 params.iter().map(|p| -> Box<dyn postgres::types::ToSql + Sync> {
4386 match p {
4387 SqlParamValue::Text(s) => Box::new(s.clone()),
4388 SqlParamValue::Integer(n) => Box::new(*n),
4389 SqlParamValue::Real(f) => Box::new(PgFloatParam(*f)),
4390 SqlParamValue::Bool(b) => Box::new(*b),
4391 SqlParamValue::Null => Box::new(Option::<String>::None),
4392 }
4393 }).collect()
4394}
4395
4396#[cfg(test)]
4397mod pg_float_param_tests {
4398 use super::PgFloatParam;
4399 use bytes::{Buf, BytesMut};
4400 use postgres::types::{ToSql, Type};
4401
4402 #[test]
4403 fn encodes_float4_as_4_bytes_matching_the_value() {
4404 let mut out = BytesMut::new();
4405 PgFloatParam(6.5).to_sql(&Type::FLOAT4, &mut out).unwrap();
4406 assert_eq!(out.len(), 4);
4407 assert_eq!(out.get_f32(), 6.5f32);
4408 }
4409
4410 #[test]
4411 fn encodes_float8_as_8_bytes_matching_the_value() {
4412 let mut out = BytesMut::new();
4413 PgFloatParam(6.5).to_sql(&Type::FLOAT8, &mut out).unwrap();
4414 assert_eq!(out.len(), 8);
4415 assert_eq!(out.get_f64(), 6.5f64);
4416 }
4417
4418 #[test]
4419 fn accepts_only_float4_and_float8() {
4420 assert!(PgFloatParam::accepts(&Type::FLOAT4));
4421 assert!(PgFloatParam::accepts(&Type::FLOAT8));
4422 assert!(!PgFloatParam::accepts(&Type::TEXT));
4423 assert!(!PgFloatParam::accepts(&Type::INT8));
4424 }
4425}
4426
4427fn sql_run_query_sqlite(
4429 conn: &rusqlite::Connection,
4430 stmt_str: &str,
4431 params: &[SqlParamValue],
4432) -> Value {
4433 let mut stmt = match conn.prepare(stmt_str) {
4434 Ok(s) => s,
4435 Err(e) => return err(sqlite_err_to_sql_error(e, "sql.query")),
4436 };
4437 let column_count = stmt.column_count();
4438 let column_names: Vec<String> = (0..column_count)
4439 .map(|i| stmt.column_name(i).unwrap_or("").to_string())
4440 .collect();
4441 let bound = sqlite_params(params);
4442 let bind: Vec<&dyn rusqlite::ToSql> = bound.iter()
4443 .map(|p| p as &dyn rusqlite::ToSql)
4444 .collect();
4445 let mut rows = match stmt.query(rusqlite::params_from_iter(bind.iter())) {
4446 Ok(r) => r,
4447 Err(e) => return err(sqlite_err_to_sql_error(e, "sql.query")),
4448 };
4449 let mut out: Vec<Value> = Vec::new();
4450 loop {
4451 let row = match rows.next() {
4452 Ok(Some(r)) => r,
4453 Ok(None) => break,
4454 Err(e) => return err(sqlite_err_to_sql_error(e, "sql.query")),
4455 };
4456 let mut rec = indexmap::IndexMap::new();
4457 for (i, name) in column_names.iter().enumerate() {
4458 let cell = match row.get_ref(i) {
4459 Ok(c) => sql_value_ref_to_lex(c),
4460 Err(e) => return err(sqlite_err_to_sql_error(e, &format!("sql.query: column {i}"))),
4461 };
4462 rec.insert(name.clone(), cell);
4463 }
4464 out.push(Value::record_dynamic(rec));
4465 }
4466 ok(Value::List(out.into()))
4467}
4468
4469fn sql_run_query_pg(
4471 client: &mut postgres::Client,
4472 stmt_str: &str,
4473 params: &[SqlParamValue],
4474) -> Value {
4475 let pg = pg_param_refs(params);
4476 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
4477 pg.iter().map(|b| b.as_ref()).collect();
4478 let stmt_pg = pg_rewrite_placeholders(stmt_str);
4479 let rows = match client.query(stmt_pg.as_str(), &refs) {
4480 Ok(r) => r,
4481 Err(e) => return err(pg_err_to_sql_error(e, "sql.query")),
4482 };
4483 let out: std::collections::VecDeque<Value> = rows.iter().map(|row| {
4484 Value::record_dynamic(pg_row_to_lex_record(row))
4485 }).collect();
4486 ok(Value::List(out))
4487}
4488
4489fn pg_row_to_lex_record(row: &postgres::Row) -> indexmap::IndexMap<String, Value> {
4491 use postgres::types::Type;
4492 let mut rec = indexmap::IndexMap::new();
4493 for (i, col) in row.columns().iter().enumerate() {
4494 let ty = col.type_();
4495 let val = if *ty == Type::INT2 || *ty == Type::INT4 || *ty == Type::INT8 {
4496 row.get::<_, Option<i64>>(i).map(Value::Int).unwrap_or(Value::Unit)
4497 } else if *ty == Type::FLOAT4 {
4498 row.get::<_, Option<f32>>(i).map(|f| Value::Float(f as f64)).unwrap_or(Value::Unit)
4499 } else if *ty == Type::FLOAT8 {
4500 row.get::<_, Option<f64>>(i).map(Value::Float).unwrap_or(Value::Unit)
4501 } else if *ty == Type::BOOL {
4502 row.get::<_, Option<bool>>(i).map(Value::Bool).unwrap_or(Value::Unit)
4503 } else if *ty == Type::BYTEA {
4504 row.get::<_, Option<Vec<u8>>>(i).map(Value::Bytes).unwrap_or(Value::Unit)
4505 } else {
4506 row.get::<_, Option<String>>(i).map(|s| Value::Str(s.into())).unwrap_or(Value::Unit)
4507 };
4508 rec.insert(col.name().to_string(), val);
4509 }
4510 rec
4511}
4512
4513fn sql_get_col<F>(args: &[Value], convert: F) -> Result<Value, String>
4515where
4516 F: Fn(&Value) -> Option<Value>,
4517{
4518 let row = args.first().ok_or("sql.get_*: missing row argument")?;
4519 let col = match args.get(1) {
4520 Some(Value::Str(s)) => s.as_str(),
4521 Some(other) => return Err(format!("sql.get_*: column name must be Str, got {other:?}")),
4522 None => return Err("sql.get_*: missing column name argument".into()),
4523 };
4524 let cell = match row {
4525 Value::Record { fields: rec, .. } => rec.get(col).cloned(),
4526 other => return Err(format!("sql.get_*: row must be a Record, got {other:?}")),
4527 };
4528 Ok(match cell.and_then(|v| convert(&v)) {
4529 Some(v) => Value::Variant { name: "Some".into(), args: vec![v] },
4530 None => Value::Variant { name: "None".into(), args: vec![] },
4531 })
4532}
4533
4534fn sql_value_ref_to_lex(v: rusqlite::types::ValueRef<'_>) -> Value {
4535 use rusqlite::types::ValueRef;
4536 match v {
4537 ValueRef::Null => Value::Unit,
4538 ValueRef::Integer(n) => Value::Int(n),
4539 ValueRef::Real(f) => Value::Float(f),
4540 ValueRef::Text(s) => Value::Str(String::from_utf8_lossy(s).into_owned().into()),
4541 ValueRef::Blob(b) => Value::Bytes(b.to_vec()),
4542 }
4543}
4544
4545#[derive(Clone, Copy, PartialEq, PartialOrd)]
4548enum LogLevel { Debug, Info, Warn, Error }
4549
4550#[derive(Clone, Copy, PartialEq)]
4551enum LogFormat { Text, Json }
4552
4553#[derive(Clone)]
4554enum LogSink {
4555 Stderr,
4556 File(std::sync::Arc<Mutex<std::fs::File>>),
4557}
4558
4559struct LogState {
4560 level: LogLevel,
4561 format: LogFormat,
4562 sink: LogSink,
4563}
4564
4565fn log_state() -> &'static Mutex<LogState> {
4566 static STATE: OnceLock<Mutex<LogState>> = OnceLock::new();
4567 STATE.get_or_init(|| Mutex::new(LogState {
4568 level: LogLevel::Info,
4569 format: LogFormat::Text,
4570 sink: LogSink::Stderr,
4571 }))
4572}
4573
4574fn parse_log_level(s: &str) -> Option<LogLevel> {
4575 match s {
4576 "debug" => Some(LogLevel::Debug),
4577 "info" => Some(LogLevel::Info),
4578 "warn" => Some(LogLevel::Warn),
4579 "error" => Some(LogLevel::Error),
4580 _ => None,
4581 }
4582}
4583
4584fn level_label(l: LogLevel) -> &'static str {
4585 match l {
4586 LogLevel::Debug => "debug",
4587 LogLevel::Info => "info",
4588 LogLevel::Warn => "warn",
4589 LogLevel::Error => "error",
4590 }
4591}
4592
4593fn emit_log(level: LogLevel, msg: &str) {
4594 let state = log_state().lock().unwrap();
4595 if level < state.level {
4596 return;
4597 }
4598 let ts = chrono::Utc::now().to_rfc3339();
4599 let line = match state.format {
4600 LogFormat::Text => format!("[{}] {}: {}\n", ts, level_label(level), msg),
4601 LogFormat::Json => {
4602 let escaped = msg
4606 .replace('\\', "\\\\")
4607 .replace('"', "\\\"")
4608 .replace('\n', "\\n")
4609 .replace('\r', "\\r");
4610 format!(
4611 "{{\"ts\":\"{ts}\",\"level\":\"{}\",\"msg\":\"{escaped}\"}}\n",
4612 level_label(level),
4613 )
4614 }
4615 };
4616 let sink = state.sink.clone();
4617 drop(state);
4618 match sink {
4619 LogSink::Stderr => {
4620 use std::io::Write;
4621 let _ = std::io::stderr().write_all(line.as_bytes());
4622 }
4623 LogSink::File(f) => {
4624 use std::io::Write;
4625 if let Ok(mut g) = f.lock() {
4626 let _ = g.write_all(line.as_bytes());
4627 }
4628 }
4629 }
4630}
4631
4632pub(crate) struct ProcessState {
4633 child: std::process::Child,
4634 stdout: Option<std::io::BufReader<std::process::ChildStdout>>,
4635 stderr: Option<std::io::BufReader<std::process::ChildStderr>>,
4636}
4637
4638fn process_registry() -> &'static Mutex<ProcessRegistry> {
4652 static REGISTRY: OnceLock<Mutex<ProcessRegistry>> = OnceLock::new();
4653 REGISTRY.get_or_init(|| Mutex::new(ProcessRegistry::with_capacity(MAX_PROCESS_HANDLES)))
4654}
4655
4656const MAX_PROCESS_HANDLES: usize = 256;
4657
4658type SharedProcessState = Arc<Mutex<ProcessState>>;
4659
4660pub(crate) struct ProcessRegistry {
4661 entries: indexmap::IndexMap<u64, SharedProcessState>,
4662 cap: usize,
4663}
4664
4665impl ProcessRegistry {
4666 pub(crate) fn with_capacity(cap: usize) -> Self {
4667 Self { entries: indexmap::IndexMap::new(), cap }
4668 }
4669
4670 pub(crate) fn insert(&mut self, handle: u64, state: ProcessState) {
4674 if self.entries.len() >= self.cap {
4675 self.entries.shift_remove_index(0);
4676 }
4677 self.entries.insert(handle, Arc::new(Mutex::new(state)));
4678 }
4679
4680 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<SharedProcessState> {
4684 let idx = self.entries.get_index_of(&handle)?;
4685 self.entries.move_index(idx, self.entries.len() - 1);
4686 self.entries.get(&handle).cloned()
4687 }
4688
4689 pub(crate) fn remove(&mut self, handle: u64) {
4694 self.entries.shift_remove(&handle);
4695 }
4696
4697 #[cfg(test)]
4698 pub(crate) fn len(&self) -> usize { self.entries.len() }
4699}
4700
4701fn next_process_handle() -> u64 {
4702 static COUNTER: AtomicU64 = AtomicU64::new(1);
4703 COUNTER.fetch_add(1, Ordering::SeqCst)
4704}
4705
4706#[cfg(all(test, unix))]
4707mod process_registry_tests {
4708 use super::{ProcessRegistry, ProcessState};
4709
4710 fn fresh_state() -> ProcessState {
4714 let child = std::process::Command::new("true")
4715 .stdout(std::process::Stdio::null())
4716 .stderr(std::process::Stdio::null())
4717 .spawn()
4718 .expect("spawn `true`");
4719 ProcessState { child, stdout: None, stderr: None }
4720 }
4721
4722 #[test]
4723 fn insert_and_get_round_trip() {
4724 let mut r = ProcessRegistry::with_capacity(4);
4725 r.insert(1, fresh_state());
4726 assert!(r.touch_get(1).is_some());
4727 assert!(r.touch_get(2).is_none());
4728 }
4729
4730 #[test]
4731 fn touch_get_returns_distinct_arcs_for_distinct_handles() {
4732 let mut r = ProcessRegistry::with_capacity(4);
4733 r.insert(1, fresh_state());
4734 r.insert(2, fresh_state());
4735 let a = r.touch_get(1).unwrap();
4736 let b = r.touch_get(2).unwrap();
4737 assert!(!std::sync::Arc::ptr_eq(&a, &b));
4739 }
4740
4741 #[test]
4742 fn cap_evicts_lru_on_overflow() {
4743 let mut r = ProcessRegistry::with_capacity(2);
4744 r.insert(1, fresh_state());
4745 r.insert(2, fresh_state());
4746 let _ = r.touch_get(1);
4747 r.insert(3, fresh_state());
4748 assert!(r.touch_get(1).is_some(), "1 was MRU, should survive");
4749 assert!(r.touch_get(2).is_none(), "2 was LRU, should be evicted");
4750 assert!(r.touch_get(3).is_some(), "3 just inserted, should survive");
4751 assert_eq!(r.len(), 2);
4752 }
4753
4754 #[test]
4755 fn cap_with_no_touches_evicts_in_insertion_order() {
4756 let mut r = ProcessRegistry::with_capacity(2);
4757 r.insert(10, fresh_state());
4758 r.insert(20, fresh_state());
4759 r.insert(30, fresh_state());
4760 assert!(r.touch_get(10).is_none());
4761 assert!(r.touch_get(20).is_some());
4762 assert!(r.touch_get(30).is_some());
4763 }
4764
4765 #[test]
4766 fn remove_drops_entry() {
4767 let mut r = ProcessRegistry::with_capacity(4);
4768 r.insert(1, fresh_state());
4769 r.remove(1);
4770 assert!(r.touch_get(1).is_none());
4771 assert_eq!(r.len(), 0);
4772 }
4773
4774 #[test]
4775 fn many_inserts_stay_bounded_at_cap() {
4776 let cap = 8;
4777 let mut r = ProcessRegistry::with_capacity(cap);
4778 for i in 0..(cap as u64 * 3) {
4779 r.insert(i, fresh_state());
4780 assert!(r.len() <= cap);
4781 }
4782 assert_eq!(r.len(), cap);
4783 }
4784
4785 #[test]
4786 fn outstanding_arc_outlives_remove() {
4787 let mut r = ProcessRegistry::with_capacity(4);
4791 r.insert(1, fresh_state());
4792 let arc = r.touch_get(1).expect("entry exists");
4793 r.remove(1);
4794 assert!(r.touch_get(1).is_none());
4796 let _state = arc.lock().unwrap();
4797 }
4798}
4799
4800fn expect_process_handle(v: Option<&Value>) -> Result<u64, String> {
4801 match v {
4802 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4803 Some(other) => Err(format!("expected ProcessHandle (Int), got {other:?}")),
4804 None => Err("missing ProcessHandle argument".into()),
4805 }
4806}
4807
4808fn kv_registry() -> &'static Mutex<KvRegistry> {
4820 static REGISTRY: OnceLock<Mutex<KvRegistry>> = OnceLock::new();
4821 REGISTRY.get_or_init(|| Mutex::new(KvRegistry::with_capacity(MAX_KV_HANDLES)))
4822}
4823
4824const MAX_KV_HANDLES: usize = 256;
4830
4831pub(crate) struct KvRegistry {
4836 entries: indexmap::IndexMap<u64, sled::Db>,
4837 cap: usize,
4838}
4839
4840impl KvRegistry {
4841 pub(crate) fn with_capacity(cap: usize) -> Self {
4842 Self { entries: indexmap::IndexMap::new(), cap }
4843 }
4844
4845 pub(crate) fn insert(&mut self, handle: u64, db: sled::Db) {
4848 if self.entries.len() >= self.cap {
4849 self.entries.shift_remove_index(0);
4850 }
4851 self.entries.insert(handle, db);
4852 }
4853
4854 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<&sled::Db> {
4856 let idx = self.entries.get_index_of(&handle)?;
4857 self.entries.move_index(idx, self.entries.len() - 1);
4858 self.entries.get(&handle)
4859 }
4860
4861 pub(crate) fn remove(&mut self, handle: u64) {
4863 self.entries.shift_remove(&handle);
4864 }
4865
4866 #[cfg(test)]
4867 pub(crate) fn len(&self) -> usize { self.entries.len() }
4868}
4869
4870fn next_kv_handle() -> u64 {
4871 static COUNTER: AtomicU64 = AtomicU64::new(1);
4872 COUNTER.fetch_add(1, Ordering::SeqCst)
4873}
4874
4875struct RedisEntry {
4890 url: String,
4891 conn: redis::Connection,
4892}
4893
4894struct RedisRegistry {
4895 entries: indexmap::IndexMap<u64, RedisEntry>,
4896 cap: usize,
4897}
4898
4899impl RedisRegistry {
4900 fn with_capacity(cap: usize) -> Self {
4901 Self { entries: indexmap::IndexMap::new(), cap }
4902 }
4903
4904 fn insert(&mut self, handle: u64, entry: RedisEntry) {
4905 if self.entries.len() >= self.cap {
4906 self.entries.shift_remove_index(0);
4907 }
4908 self.entries.insert(handle, entry);
4909 }
4910
4911 fn touch_get_mut(&mut self, handle: u64) -> Option<&mut RedisEntry> {
4912 let idx = self.entries.get_index_of(&handle)?;
4913 self.entries.move_index(idx, self.entries.len() - 1);
4914 self.entries.get_mut(&handle)
4915 }
4916
4917 fn get_url(&self, handle: u64) -> Option<String> {
4920 self.entries.get(&handle).map(|e| e.url.clone())
4921 }
4922
4923 fn remove(&mut self, handle: u64) {
4924 self.entries.shift_remove(&handle);
4925 }
4926}
4927
4928fn redis_registry() -> &'static Mutex<RedisRegistry> {
4929 static REGISTRY: OnceLock<Mutex<RedisRegistry>> = OnceLock::new();
4930 REGISTRY.get_or_init(|| Mutex::new(RedisRegistry::with_capacity(MAX_REDIS_HANDLES)))
4931}
4932
4933const MAX_REDIS_HANDLES: usize = 256;
4934
4935fn next_redis_handle() -> u64 {
4936 static COUNTER: AtomicU64 = AtomicU64::new(1);
4937 COUNTER.fetch_add(1, Ordering::SeqCst)
4938}
4939
4940fn expect_redis_handle(v: Option<&Value>) -> Result<u64, String> {
4941 match v {
4942 Some(Value::Int(n)) if *n >= 0 => Ok(*n as u64),
4943 Some(other) => Err(format!("expected ConnRedis (Int), got {other:?}")),
4944 None => Err("missing ConnRedis argument".into()),
4945 }
4946}
4947
4948fn sql_registry() -> &'static Mutex<SqlRegistry> {
4955 static REGISTRY: OnceLock<Mutex<SqlRegistry>> = OnceLock::new();
4956 REGISTRY.get_or_init(|| Mutex::new(SqlRegistry::with_capacity(MAX_SQL_HANDLES)))
4957}
4958
4959const MAX_SQL_HANDLES: usize = 256;
4960
4961const CURSOR_CHANNEL_CAPACITY: usize = 64;
4984const MAX_CURSOR_HANDLES: usize = 256;
4985
4986type CursorReceiver = std::sync::mpsc::Receiver<Result<Value, String>>;
4987
4988pub(crate) struct CursorRegistry {
4989 entries: indexmap::IndexMap<u64, Arc<Mutex<CursorReceiver>>>,
4994 cap: usize,
4995}
4996
4997impl CursorRegistry {
4998 pub(crate) fn with_capacity(cap: usize) -> Self {
4999 Self { entries: indexmap::IndexMap::new(), cap }
5000 }
5001
5002 pub(crate) fn insert(&mut self, handle: u64, rx: CursorReceiver) {
5003 if self.entries.len() >= self.cap {
5004 self.entries.shift_remove_index(0);
5005 }
5006 self.entries.insert(handle, Arc::new(Mutex::new(rx)));
5007 }
5008
5009 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<Arc<Mutex<CursorReceiver>>> {
5010 let idx = self.entries.get_index_of(&handle)?;
5011 self.entries.move_index(idx, self.entries.len() - 1);
5012 self.entries.get(&handle).cloned()
5013 }
5014
5015 pub(crate) fn remove(&mut self, handle: u64) {
5016 self.entries.shift_remove(&handle);
5017 }
5018}
5019
5020fn cursor_registry() -> &'static Mutex<CursorRegistry> {
5021 static REGISTRY: OnceLock<Mutex<CursorRegistry>> = OnceLock::new();
5022 REGISTRY.get_or_init(|| Mutex::new(CursorRegistry::with_capacity(MAX_CURSOR_HANDLES)))
5023}
5024
5025fn next_cursor_handle() -> u64 {
5026 static COUNTER: AtomicU64 = AtomicU64::new(1);
5027 COUNTER.fetch_add(1, Ordering::SeqCst)
5028}
5029
5030fn sqlite_cursor_producer(
5036 conn_arc: Arc<Mutex<SqlConn>>,
5037 stmt_str: String,
5038 params: Vec<SqlParamValue>,
5039 sender: std::sync::mpsc::SyncSender<Result<Value, String>>,
5040) {
5041 let mut conn_guard = match conn_arc.lock() {
5042 Ok(g) => g,
5043 Err(p) => p.into_inner(),
5044 };
5045 let SqlConn::Sqlite(c) = &mut *conn_guard else {
5046 let _ = sender.send(Err("sqlite_cursor_producer called on non-sqlite conn".into()));
5047 return;
5048 };
5049 let mut stmt = match c.prepare(&stmt_str) {
5050 Ok(s) => s,
5051 Err(e) => { let _ = sender.send(Err(format!("prepare: {e}"))); return; }
5052 };
5053 let column_count = stmt.column_count();
5054 let column_names: Vec<String> = (0..column_count)
5055 .map(|i| stmt.column_name(i).unwrap_or("").to_string())
5056 .collect();
5057 let bound = sqlite_params(¶ms);
5058 let bind: Vec<&dyn rusqlite::ToSql> =
5059 bound.iter().map(|p| p as &dyn rusqlite::ToSql).collect();
5060 let mut rows = match stmt.query(rusqlite::params_from_iter(bind.iter())) {
5061 Ok(r) => r,
5062 Err(e) => { let _ = sender.send(Err(format!("query: {e}"))); return; }
5063 };
5064 loop {
5065 match rows.next() {
5066 Ok(None) => break,
5067 Err(e) => {
5068 let _ = sender.send(Err(format!("row: {e}")));
5069 break;
5070 }
5071 Ok(Some(row)) => {
5072 let mut rec = indexmap::IndexMap::new();
5073 for (i, name) in column_names.iter().enumerate() {
5074 let val = match row.get_ref(i) {
5075 Ok(vr) => sql_value_ref_to_lex(vr),
5076 Err(_) => Value::Unit,
5077 };
5078 rec.insert(name.clone(), val);
5079 }
5080 if sender.send(Ok(Value::record_dynamic(rec))).is_err() {
5081 break;
5082 }
5083 }
5084 }
5085 }
5086}
5087
5088fn pg_cursor_producer(
5092 conn_arc: Arc<Mutex<SqlConn>>,
5093 stmt_str: String,
5094 params: Vec<SqlParamValue>,
5095 sender: std::sync::mpsc::SyncSender<Result<Value, String>>,
5096) {
5097 let mut conn_guard = match conn_arc.lock() {
5098 Ok(g) => g,
5099 Err(p) => p.into_inner(),
5100 };
5101 let SqlConn::Postgres(c) = &mut *conn_guard else {
5102 let _ = sender.send(Err("pg_cursor_producer called on non-postgres conn".into()));
5103 return;
5104 };
5105 let pg = pg_param_refs(¶ms);
5106 let refs: Vec<&(dyn postgres::types::ToSql + Sync)> =
5107 pg.iter().map(|b| b.as_ref()).collect();
5108 let mut tx = match c.transaction() {
5109 Ok(t) => t,
5110 Err(e) => { let _ = sender.send(Err(format!("begin: {e}"))); return; }
5111 };
5112 let stmt_str = pg_rewrite_placeholders(&stmt_str);
5115 let cur_name = format!("__lex_cur_{}", next_cursor_handle());
5116 if let Err(e) = tx.execute(
5117 &format!("DECLARE \"{cur_name}\" NO SCROLL CURSOR FOR {stmt_str}"),
5118 &refs,
5119 ) {
5120 let _ = sender.send(Err(format!("declare: {e}")));
5121 return;
5122 }
5123 let fetch_sql = format!("FETCH 64 FROM \"{cur_name}\"");
5124 'outer: loop {
5125 let batch = match tx.query(&fetch_sql, &[]) {
5126 Ok(r) => r,
5127 Err(e) => { let _ = sender.send(Err(format!("fetch: {e}"))); break; }
5128 };
5129 if batch.is_empty() {
5130 break;
5131 }
5132 for row in batch.iter() {
5133 let rec = pg_row_to_lex_record(row);
5134 if sender.send(Ok(Value::record_dynamic(rec))).is_err() {
5135 break 'outer;
5136 }
5137 }
5138 }
5139 let _ = tx.execute(&format!("CLOSE \"{cur_name}\""), &[]);
5140 let _ = tx.commit();
5141}
5142
5143#[derive(Debug, Clone)]
5145enum SqlParamValue {
5146 Text(String),
5147 Integer(i64),
5148 Real(f64),
5149 Bool(bool),
5150 Null,
5151}
5152
5153pub(crate) enum SqlConn {
5155 Sqlite(rusqlite::Connection),
5156 Postgres(postgres::Client),
5157}
5158
5159type SharedConn = Arc<Mutex<SqlConn>>;
5160
5161pub(crate) struct SqlRegistry {
5162 entries: indexmap::IndexMap<u64, SharedConn>,
5163 cap: usize,
5164}
5165
5166impl SqlRegistry {
5167 pub(crate) fn with_capacity(cap: usize) -> Self {
5168 Self { entries: indexmap::IndexMap::new(), cap }
5169 }
5170
5171 pub(crate) fn insert(&mut self, handle: u64, conn: SqlConn) {
5172 if self.entries.len() >= self.cap {
5173 self.entries.shift_remove_index(0);
5174 }
5175 self.entries.insert(handle, Arc::new(Mutex::new(conn)));
5176 }
5177
5178 pub(crate) fn touch_get(&mut self, handle: u64) -> Option<SharedConn> {
5182 let idx = self.entries.get_index_of(&handle)?;
5183 self.entries.move_index(idx, self.entries.len() - 1);
5184 self.entries.get(&handle).cloned()
5185 }
5186
5187 pub(crate) fn remove(&mut self, handle: u64) {
5188 self.entries.shift_remove(&handle);
5189 }
5190
5191 #[cfg(test)]
5192 pub(crate) fn len(&self) -> usize { self.entries.len() }
5193}
5194
5195fn next_sql_handle() -> u64 {
5196 static COUNTER: AtomicU64 = AtomicU64::new(1);
5197 COUNTER.fetch_add(1, Ordering::SeqCst)
5198}
5199
5200#[cfg(test)]
5201mod sql_registry_tests {
5202 use super::{SqlConn, SqlRegistry};
5203
5204 fn fresh() -> SqlConn {
5205 SqlConn::Sqlite(rusqlite::Connection::open_in_memory().expect("open in-memory sqlite"))
5206 }
5207
5208 #[test]
5209 fn insert_and_get_round_trip() {
5210 let mut r = SqlRegistry::with_capacity(4);
5211 r.insert(1, fresh());
5212 assert!(r.touch_get(1).is_some());
5213 assert!(r.touch_get(2).is_none());
5214 }
5215
5216 #[test]
5217 fn cap_evicts_lru_on_overflow() {
5218 let mut r = SqlRegistry::with_capacity(2);
5219 r.insert(1, fresh());
5220 r.insert(2, fresh());
5221 let _ = r.touch_get(1);
5222 r.insert(3, fresh());
5223 assert!(r.touch_get(1).is_some(), "1 was MRU, should survive");
5224 assert!(r.touch_get(2).is_none(), "2 was LRU, should be evicted");
5225 assert!(r.touch_get(3).is_some(), "3 just inserted");
5226 assert_eq!(r.len(), 2);
5227 }
5228
5229 #[test]
5230 fn remove_drops_entry() {
5231 let mut r = SqlRegistry::with_capacity(4);
5232 r.insert(1, fresh());
5233 r.remove(1);
5234 assert!(r.touch_get(1).is_none());
5235 assert_eq!(r.len(), 0);
5236 }
5237
5238 #[test]
5239 fn many_inserts_stay_bounded_at_cap() {
5240 let cap = 8;
5241 let mut r = SqlRegistry::with_capacity(cap);
5242 for i in 0..(cap as u64 * 3) {
5243 r.insert(i, fresh());
5244 assert!(r.len() <= cap);
5245 }
5246 assert_eq!(r.len(), cap);
5247 }
5248}
5249
5250#[cfg(test)]
5251mod kv_registry_tests {
5252 use super::KvRegistry;
5253
5254 fn fresh_db(tag: &str) -> sled::Db {
5257 let dir = std::env::temp_dir().join(format!(
5258 "lex-kv-reg-{}-{}-{}",
5259 std::process::id(),
5260 tag,
5261 std::time::SystemTime::now()
5262 .duration_since(std::time::UNIX_EPOCH)
5263 .unwrap()
5264 .as_nanos()
5265 ));
5266 sled::open(&dir).expect("sled open")
5267 }
5268
5269 #[test]
5270 fn insert_and_get_round_trip() {
5271 let mut r = KvRegistry::with_capacity(4);
5272 r.insert(1, fresh_db("a"));
5273 assert!(r.touch_get(1).is_some());
5274 assert!(r.touch_get(2).is_none());
5275 }
5276
5277 #[test]
5278 fn cap_evicts_lru_on_overflow() {
5279 let mut r = KvRegistry::with_capacity(2);
5281 r.insert(1, fresh_db("c1"));
5282 r.insert(2, fresh_db("c2"));
5283 let _ = r.touch_get(1);
5284 r.insert(3, fresh_db("c3"));
5285 assert!(r.touch_get(1).is_some(), "1 was MRU, should survive");
5286 assert!(r.touch_get(2).is_none(), "2 was LRU, should be evicted");
5287 assert!(r.touch_get(3).is_some(), "3 just inserted, should survive");
5288 assert_eq!(r.len(), 2);
5289 }
5290
5291 #[test]
5292 fn cap_with_no_touches_evicts_in_insertion_order() {
5293 let mut r = KvRegistry::with_capacity(2);
5295 r.insert(10, fresh_db("f1"));
5296 r.insert(20, fresh_db("f2"));
5297 r.insert(30, fresh_db("f3"));
5298 assert!(r.touch_get(10).is_none());
5299 assert!(r.touch_get(20).is_some());
5300 assert!(r.touch_get(30).is_some());
5301 }
5302
5303 #[test]
5304 fn remove_drops_entry() {
5305 let mut r = KvRegistry::with_capacity(4);
5306 r.insert(1, fresh_db("r1"));
5307 r.remove(1);
5308 assert!(r.touch_get(1).is_none());
5309 assert_eq!(r.len(), 0);
5310 }
5311
5312 #[test]
5313 fn remove_unknown_handle_is_noop() {
5314 let mut r = KvRegistry::with_capacity(4);
5315 r.insert(1, fresh_db("u1"));
5316 r.remove(999);
5317 assert!(r.touch_get(1).is_some());
5318 }
5319
5320 #[test]
5321 fn many_inserts_stay_bounded_at_cap() {
5322 let cap = 8;
5325 let mut r = KvRegistry::with_capacity(cap);
5326 for i in 0..(cap as u64 * 3) {
5327 r.insert(i, fresh_db(&format!("b{i}")));
5328 assert!(r.len() <= cap);
5329 }
5330 assert_eq!(r.len(), cap);
5331 }
5332}
5333
5334#[cfg(test)]
5341mod unpack_response_tests {
5342 use super::*;
5343 use std::sync::Arc;
5344 use indexmap::IndexMap;
5345 use lex_bytecode::{Const, Op, Program, Value};
5346 use lex_bytecode::program::{Function, ZERO_BODY_HASH};
5347 use lex_bytecode::vm::Vm;
5348
5349 fn build_arena_response_program() -> Arc<Program> {
5354 let constants = vec![
5355 Const::FieldName("status".into()), Const::FieldName("body".into()), Const::Int(200), Const::VariantName("BodyStr".into()), Const::Str("hello".into()), ];
5361 let mut function_names = IndexMap::new();
5362 function_names.insert("handler".to_string(), 0);
5363 Arc::new(Program {
5364 constants,
5365 functions: vec![Function {
5366 name: "handler".into(),
5367 arity: 0,
5368 locals_count: 0,
5369 code: vec![
5370 Op::PushConst(2), Op::PushConst(4), Op::MakeVariant { name_idx: 3, arity: 1 }, Op::AllocArenaRecord { shape_idx: 0, field_count: 2 }, Op::Return,
5375 ],
5376 effects: vec![],
5377 body_hash: ZERO_BODY_HASH,
5378 refinements: vec![],
5379 field_ic_sites: 0,
5380 }],
5381 function_names,
5382 module_aliases: IndexMap::new(),
5383 entry: Some(0),
5384 record_shapes: vec![vec![0, 1]], })
5386 }
5387
5388 #[test]
5394 fn unpack_response_reads_arena_record_via_slab() {
5395 let p = build_arena_response_program();
5396 let mut vm = Vm::new(&p);
5397 let scope = vm.enter_request_scope();
5398
5399 let resp = vm.invoke(p.function_names["handler"], vec![]).unwrap();
5400 assert!(matches!(resp, Value::ArenaRecord { .. }),
5403 "expected ArenaRecord (slab path), got {resp:?}");
5404
5405 let (status, body, headers) = unpack_response(&mut vm, &resp);
5406 vm.exit_request_scope(scope);
5407
5408 assert_eq!(status, 200);
5409 assert!(headers.is_empty());
5410 match body {
5411 ResponseBodyOut::Str(s) => assert_eq!(s, "hello"),
5412 _ => panic!("expected BodyStr"),
5413 }
5414 }
5415
5416 #[test]
5421 fn unpack_response_reads_heap_record() {
5422 let p = build_arena_response_program();
5423 let mut vm = Vm::new(&p);
5424
5425 let resp = vm.invoke(p.function_names["handler"], vec![]).unwrap();
5427 assert!(matches!(resp, Value::Record { .. }),
5428 "expected heap Record (fallback path), got {resp:?}");
5429
5430 let (status, body, headers) = unpack_response(&mut vm, &resp);
5431 assert_eq!(status, 200);
5432 assert!(headers.is_empty());
5433 match body {
5434 ResponseBodyOut::Str(s) => assert_eq!(s, "hello"),
5435 _ => panic!("expected BodyStr"),
5436 }
5437 }
5438
5439 #[test]
5442 fn unpack_response_falls_back_to_500_on_non_record() {
5443 let p = build_arena_response_program();
5444 let mut vm = Vm::new(&p);
5445 let v = Value::Int(7);
5446 let (status, _body, _headers) = unpack_response(&mut vm, &v);
5447 assert_eq!(status, 500);
5448 }
5449}